implement attach and launch
This commit is contained in:
@@ -10,7 +10,7 @@ include(CTest)
|
|||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(tools)
|
add_subdirectory(tools)
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
if (BUILD_TESTING)
|
||||||
find_package(Catch2 CONFIG REQUIRED)
|
find_package(Catch2 CONFIG REQUIRED)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
endif()
|
endif ()
|
||||||
|
|||||||
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2025 Evan Burkey <evan@burkey.co>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# edbg
|
||||||
|
|
||||||
|
Linux debugger and debugging library. Implementation is based on Sy Brand's book [Building A Debugger](https://nostarch.com/building-a-debugger)
|
||||||
26
include/libedbg/error.hpp
Normal file
26
include/libedbg/error.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef LIBEDBG_INCLUDE_ERROR_H
|
||||||
|
#define LIBEDBG_INCLUDE_ERROR_H
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace edbg {
|
||||||
|
class error final : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
[[noreturn]]
|
||||||
|
static void send(const std::string &msg) {
|
||||||
|
throw error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]]
|
||||||
|
static void send_errno(const std::string &pfx) {
|
||||||
|
throw error(pfx + ": " + std::strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit error(const std::string &msg) : std::runtime_error(msg) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //LIBEDBG_INCLUDE_ERROR_H
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#ifndef LIBEDBG_INCLUDE_LIBEDBG_H
|
|
||||||
#define LIBEDBG_INCLUDE_LIBEDBG_H
|
|
||||||
|
|
||||||
namespace edbg {
|
|
||||||
void hello();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LIBEDBG_INCLUDE_LIBEDBG_H
|
|
||||||
55
include/libedbg/process.hpp
Normal file
55
include/libedbg/process.hpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef LIBEDBG_INCLUDE_PROCESS_H
|
||||||
|
#define LIBEDBG_INCLUDE_PROCESS_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
namespace edbg {
|
||||||
|
enum class process_state {
|
||||||
|
stopped,
|
||||||
|
running,
|
||||||
|
exited,
|
||||||
|
terminated
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stop_reason {
|
||||||
|
stop_reason(int wait_status);
|
||||||
|
|
||||||
|
process_state reason;
|
||||||
|
std::uint8_t info;
|
||||||
|
};
|
||||||
|
|
||||||
|
class process {
|
||||||
|
public:
|
||||||
|
process() = delete;
|
||||||
|
|
||||||
|
process(const process &) = delete;
|
||||||
|
|
||||||
|
process &operator=(const process &) = delete;
|
||||||
|
|
||||||
|
~process();
|
||||||
|
|
||||||
|
static std::unique_ptr<process> launch(std::filesystem::path path);
|
||||||
|
|
||||||
|
static std::unique_ptr<process> attach(pid_t pid);
|
||||||
|
|
||||||
|
void resume();
|
||||||
|
|
||||||
|
stop_reason wait_on_signal();
|
||||||
|
|
||||||
|
[[nodiscard]] pid_t pid() const { return pid_; }
|
||||||
|
[[nodiscard]] process_state state() const { return state_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
process(pid_t pid, bool terminate_on_end)
|
||||||
|
: pid_(pid), terminate_on_end_(terminate_on_end) {
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid_ = 0;
|
||||||
|
bool terminate_on_end_ = true;
|
||||||
|
process_state state_ = process_state::stopped;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //LIBEDBG_INCLUDE_PROCESS_H
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
add_library(libedbg libedbg.cpp)
|
SET(SRC
|
||||||
|
process.cpp
|
||||||
|
)
|
||||||
|
add_library(libedbg ${SRC})
|
||||||
add_library(edbg::libedbg ALIAS libedbg)
|
add_library(edbg::libedbg ALIAS libedbg)
|
||||||
|
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <libedbg/libedbg.hpp>
|
|
||||||
|
|
||||||
void edbg::hello() {
|
|
||||||
std::cout << "Hello edbg!\n";
|
|
||||||
}
|
|
||||||
90
src/process.cpp
Normal file
90
src/process.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include <sys/ptrace.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <libedbg/process.hpp>
|
||||||
|
#include <libedbg/error.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
edbg::stop_reason::stop_reason(const int wait_status) {
|
||||||
|
if (WIFEXITED(wait_status)) {
|
||||||
|
reason = process_state::exited;
|
||||||
|
info = WEXITSTATUS(wait_status);
|
||||||
|
} else if (WIFSIGNALED(wait_status)) {
|
||||||
|
reason = process_state::terminated;
|
||||||
|
info = WTERMSIG(wait_status);
|
||||||
|
} else if (WIFSTOPPED(wait_status)) {
|
||||||
|
reason = process_state::stopped;
|
||||||
|
info = WSTOPSIG(wait_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<edbg::process> edbg::process::launch(std::filesystem::path path) {
|
||||||
|
pid_t pid;
|
||||||
|
if ((pid = fork()) < 0) {
|
||||||
|
error::send_errno("fork failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) < 0) {
|
||||||
|
error::send_errno("ptrace failed");
|
||||||
|
}
|
||||||
|
if (execlp(path.c_str(), path.c_str(), nullptr) < 0) {
|
||||||
|
error::send_errno("execlp failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<edbg::process> proc(new process(pid, true));
|
||||||
|
proc->wait_on_signal();
|
||||||
|
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<edbg::process> edbg::process::attach(const pid_t pid) {
|
||||||
|
if (pid == 0) {
|
||||||
|
error::send("invalid pid of 0");
|
||||||
|
}
|
||||||
|
if (ptrace(PTRACE_ATTACH, pid, 0, nullptr) < 0) {
|
||||||
|
error::send_errno("ptrace attach failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<edbg::process> proc(new process(pid, false));
|
||||||
|
proc->wait_on_signal();
|
||||||
|
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void edbg::process::resume() {
|
||||||
|
if (ptrace(PTRACE_CONT, pid_, 0, nullptr) < 0) {
|
||||||
|
error::send_errno("ptrace resume failed");
|
||||||
|
}
|
||||||
|
state_ = process_state::running;
|
||||||
|
}
|
||||||
|
|
||||||
|
edbg::stop_reason edbg::process::wait_on_signal() {
|
||||||
|
int wait_status;
|
||||||
|
if (waitpid(pid_, &wait_status, 0) < 0) {
|
||||||
|
error::send_errno("waitpid failed");
|
||||||
|
}
|
||||||
|
const stop_reason reason(wait_status);
|
||||||
|
state_ = reason.reason;
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
edbg::process::~process() {
|
||||||
|
if (pid_ != 0) {
|
||||||
|
int status;
|
||||||
|
if (state_ == process_state::running) {
|
||||||
|
kill(pid_, SIGSTOP);
|
||||||
|
waitpid(pid_, &status, 0);
|
||||||
|
}
|
||||||
|
ptrace(PTRACE_DETACH, pid_);
|
||||||
|
kill(pid_, SIGCONT);
|
||||||
|
|
||||||
|
if (terminate_on_end_) {
|
||||||
|
kill(pid_, SIGKILL);
|
||||||
|
waitpid(pid_, &status, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
TEST_CASE("validate environment") {
|
TEST_CASE (
|
||||||
|
"validate environment"
|
||||||
|
)
|
||||||
|
{
|
||||||
REQUIRE(true);
|
REQUIRE(true);
|
||||||
}
|
}
|
||||||
115
tools/edbg.cpp
115
tools/edbg.cpp
@@ -1,5 +1,114 @@
|
|||||||
#include <libedbg/libedbg.hpp>
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <editline/readline.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include "libedbg/error.hpp"
|
||||||
|
#include "libedbg/process.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::vector<std::string> split(const std::string_view str, char delimiter) {
|
||||||
|
std::vector<std::string> out{};
|
||||||
|
std::stringstream ss{std::string{str}};
|
||||||
|
std::string item;
|
||||||
|
|
||||||
|
while (std::getline(ss, item, delimiter)) {
|
||||||
|
out.push_back(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_prefix(std::string_view str, std::string_view of) {
|
||||||
|
if (str.size() < of.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::equal(str.begin(), str.end(), of.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_stop_reason(const edbg::process& process, edbg::stop_reason reason) {
|
||||||
|
std::cout << "Process " << process.pid() << ' ';
|
||||||
|
|
||||||
|
switch (reason.reason) {
|
||||||
|
case edbg::process_state::exited:
|
||||||
|
std::cout << "exited with status " << static_cast<int>(reason.info);
|
||||||
|
break;
|
||||||
|
case edbg::process_state::terminated:
|
||||||
|
std::cout << "terminated with signal " << sigabbrev_np(reason.info);
|
||||||
|
case edbg::process_state::stopped:
|
||||||
|
std::cout << "stopped with signal " << sigabbrev_np(reason.info);
|
||||||
|
default: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_command(const std::unique_ptr<edbg::process>& process, const std::string_view line) {
|
||||||
|
const auto args = split(line, ' ');
|
||||||
|
const auto& command = args[0];
|
||||||
|
if (is_prefix(command, "continue")) {
|
||||||
|
process->resume();
|
||||||
|
const auto reason = process->wait_on_signal();
|
||||||
|
print_stop_reason(*process, reason);
|
||||||
|
} else {
|
||||||
|
std::cerr << "Unknown command: " << command << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<edbg::process> attach(const int argc, const char **argv) {
|
||||||
|
if (argc == 3 && argv[1] == std::string_view("-p")) {
|
||||||
|
const auto pid = static_cast<pid_t>(std::strtol(argv[2], nullptr, 10));
|
||||||
|
return edbg::process::attach(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *program_path = argv[1];
|
||||||
|
return edbg::process::launch(program_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_loop(std::unique_ptr<edbg::process>& process) {
|
||||||
|
char *line = nullptr;
|
||||||
|
while ((line = readline("edbg> ")) != nullptr) {
|
||||||
|
std::string line_str;
|
||||||
|
|
||||||
|
if (line == std::string_view("")) {
|
||||||
|
free(line);
|
||||||
|
if (history_length > 0) {
|
||||||
|
line_str = history_list()[history_length - 1]->line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line_str = line;
|
||||||
|
add_history(line_str.c_str());
|
||||||
|
free(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line_str.empty()) {
|
||||||
|
try {
|
||||||
|
handle_command(process, line_str);
|
||||||
|
} catch (const edbg::error& err) {
|
||||||
|
std::cout << err.what() << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const int argc, const char **argv) {
|
||||||
|
if (argc == 1) {
|
||||||
|
std::cerr << "No arguments given\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto process = attach(argc, argv);
|
||||||
|
main_loop(process);
|
||||||
|
} catch (const edbg::error& err) {
|
||||||
|
std::cout << err.what() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
|
||||||
edbg::hello();
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user