implement attach and launch
This commit is contained in:
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)
|
||||
|
||||
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>
|
||||
|
||||
TEST_CASE("validate environment") {
|
||||
TEST_CASE (
|
||||
"validate environment"
|
||||
)
|
||||
{
|
||||
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