diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f00c8e..dc5b3fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 () diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eb2db5c --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2025 Evan Burkey + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e03291e --- /dev/null +++ b/README.md @@ -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) \ No newline at end of file diff --git a/include/libedbg/error.hpp b/include/libedbg/error.hpp new file mode 100644 index 0000000..c7c799e --- /dev/null +++ b/include/libedbg/error.hpp @@ -0,0 +1,26 @@ +#ifndef LIBEDBG_INCLUDE_ERROR_H +#define LIBEDBG_INCLUDE_ERROR_H + +#include +#include + +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 diff --git a/include/libedbg/libedbg.hpp b/include/libedbg/libedbg.hpp deleted file mode 100644 index 0515776..0000000 --- a/include/libedbg/libedbg.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef LIBEDBG_INCLUDE_LIBEDBG_H -#define LIBEDBG_INCLUDE_LIBEDBG_H - -namespace edbg { - void hello(); -} - -#endif //LIBEDBG_INCLUDE_LIBEDBG_H \ No newline at end of file diff --git a/include/libedbg/process.hpp b/include/libedbg/process.hpp new file mode 100644 index 0000000..9a03211 --- /dev/null +++ b/include/libedbg/process.hpp @@ -0,0 +1,55 @@ +#ifndef LIBEDBG_INCLUDE_PROCESS_H +#define LIBEDBG_INCLUDE_PROCESS_H + +#include +#include +#include + +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 launch(std::filesystem::path path); + + static std::unique_ptr 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa4fb50..f79fa3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 $ $ - 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 ) \ No newline at end of file diff --git a/src/libedbg.cpp b/src/libedbg.cpp deleted file mode 100644 index a35cf6c..0000000 --- a/src/libedbg.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include - -void edbg::hello() { - std::cout << "Hello edbg!\n"; -} \ No newline at end of file diff --git a/src/process.cpp b/src/process.cpp new file mode 100644 index 0000000..8801297 --- /dev/null +++ b/src/process.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +#include +#include + + +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::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 proc(new process(pid, true)); + proc->wait_on_signal(); + + return proc; +} + +std::unique_ptr 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 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); + } + } +} diff --git a/test/tests.cpp b/test/tests.cpp index df871cd..3bf5d4d 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -1,5 +1,8 @@ #include -TEST_CASE("validate environment") { +TEST_CASE ( +"validate environment" +) + { REQUIRE(true); } \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 38fdf82..0023361 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/tools/edbg.cpp b/tools/edbg.cpp index 0d7c0f4..31110ef 100644 --- a/tools/edbg.cpp +++ b/tools/edbg.cpp @@ -1,5 +1,114 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libedbg/error.hpp" +#include "libedbg/process.hpp" + +namespace { +std::vector split(const std::string_view str, char delimiter) { + std::vector 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(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& 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 attach(const int argc, const char **argv) { + if (argc == 3 && argv[1] == std::string_view("-p")) { + const auto pid = static_cast(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& 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(); } \ No newline at end of file