implement attach and launch

This commit is contained in:
2025-11-17 08:07:40 -08:00
parent 8e1842d412
commit 22f031c268
12 changed files with 327 additions and 39 deletions

View File

@@ -10,7 +10,7 @@ include(CTest)
add_subdirectory(src)
add_subdirectory(tools)
if(BUILD_TESTING)
if (BUILD_TESTING)
find_package(Catch2 CONFIG REQUIRED)
add_subdirectory(test)
endif()
endif ()

13
LICENSE Normal file
View 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
View 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
View 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

View File

@@ -1,8 +0,0 @@
#ifndef LIBEDBG_INCLUDE_LIBEDBG_H
#define LIBEDBG_INCLUDE_LIBEDBG_H
namespace edbg {
void hello();
}
#endif //LIBEDBG_INCLUDE_LIBEDBG_H

View 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

View File

@@ -1,39 +1,42 @@
add_library(libedbg libedbg.cpp)
SET(SRC
process.cpp
)
add_library(libedbg ${SRC})
add_library(edbg::libedbg ALIAS libedbg)
set_target_properties(
libedbg
PROPERTIES OUTPUT_NAME edbg
libedbg
PROPERTIES OUTPUT_NAME edbg
)
target_compile_features(libedbg PUBLIC cxx_std_20)
target_include_directories(
libedbg
PUBLIC
libedbg
PUBLIC
$<INSTALL_INTERFACE::include>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
PRIVATE
PRIVATE
${CMAKE_SOURCE_DIR}/src/include
)
include(GNUInstallDirs)
install(TARGETS libedbg
EXPORT edbg-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
EXPORT edbg-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(
DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(
EXPORT edbg-targets
FILE edbg-config.cmake
NAMESPACE edbg::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/edbg
EXPORT edbg-targets
FILE edbg-config.cmake
NAMESPACE edbg::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/edbg
)

View File

@@ -1,6 +0,0 @@
#include <iostream>
#include <libedbg/libedbg.hpp>
void edbg::hello() {
std::cout << "Hello edbg!\n";
}

90
src/process.cpp Normal file
View 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);
}
}
}

View File

@@ -1,5 +1,8 @@
#include <catch2/catch_test_macros.hpp>
TEST_CASE("validate environment") {
TEST_CASE (
"validate environment"
)
{
REQUIRE(true);
}

View File

@@ -3,8 +3,8 @@ target_link_libraries(edbg PRIVATE edbg::libedbg)
include(GNUInstallDirs)
install(
TARGETS edbg
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
TARGETS edbg
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
target_link_libraries(edbg PRIVATE edbg::libedbg PkgConfig::libedit)

View File

@@ -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();
}