├── examples ├── hello.cpp ├── variable.cpp └── stack_unwinding.cpp ├── .gitmodules ├── README.md ├── LICENSE ├── CMakeLists.txt ├── include ├── breakpoint.hpp ├── debugger.hpp └── registers.hpp └── src └── minidbg.cpp /examples/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main () { 4 | std::cerr << "Hello world"; 5 | } 6 | -------------------------------------------------------------------------------- /examples/variable.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | long a = 3; 3 | long b = 2; 4 | long c = a + b; 5 | a = 4; 6 | } 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/libelfin"] 2 | path = ext/libelfin 3 | url = https://github.com/TartanLlama/libelfin.git 4 | [submodule "ext/linenoise"] 5 | path = ext/linenoise 6 | url = https://github.com/antirez/linenoise.git 7 | -------------------------------------------------------------------------------- /examples/stack_unwinding.cpp: -------------------------------------------------------------------------------- 1 | void a() { 2 | int foo = 1; 3 | } 4 | 5 | void b() { 6 | int foo = 2; 7 | a(); 8 | } 9 | 10 | void c() { 11 | int foo = 3; 12 | b(); 13 | } 14 | 15 | void d() { 16 | int foo = 4; 17 | c(); 18 | } 19 | 20 | void e() { 21 | int foo = 5; 22 | d(); 23 | } 24 | 25 | void f() { 26 | int foo = 6; 27 | e(); 28 | } 29 | 30 | int main() { 31 | f(); 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *This project has been expanded into a book! It covers many more topics in much greater detail. You can now pre-order [Building a Debugger](https://nostarch.com/building-a-debugger).* 2 | 3 | # minidbg 4 | A mini x86 linux debugger for teaching purposes 5 | 6 | See my [Writing a linux debugger](http://blog.tartanllama.xyz/c++/2017/03/21/writing-a-linux-debugger-setup/) blog post series for a tutorial on how to write something like this. 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Simon Brand 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | project (MiniDbg) 3 | 4 | add_compile_options(-std=c++14) 5 | 6 | include_directories(ext/libelfin ext/linenoise include) 7 | add_executable(minidbg src/minidbg.cpp ext/linenoise/linenoise.c) 8 | 9 | add_executable(hello examples/hello.cpp) 10 | set_target_properties(hello 11 | PROPERTIES COMPILE_FLAGS "-g -O0") 12 | 13 | add_executable(variable examples/variable.cpp) 14 | set_target_properties(variable 15 | PROPERTIES COMPILE_FLAGS "-gdwarf-2 -O0") 16 | 17 | add_executable(unwinding examples/stack_unwinding.cpp) 18 | set_target_properties(unwinding 19 | PROPERTIES COMPILE_FLAGS "-g -O0") 20 | 21 | 22 | add_custom_target( 23 | libelfin 24 | COMMAND make 25 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/ext/libelfin 26 | ) 27 | target_link_libraries(minidbg 28 | ${PROJECT_SOURCE_DIR}/ext/libelfin/dwarf/libdwarf++.so 29 | ${PROJECT_SOURCE_DIR}/ext/libelfin/elf/libelf++.so) 30 | add_dependencies(minidbg libelfin) 31 | -------------------------------------------------------------------------------- /include/breakpoint.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MINIDBG_BREAKPOINT_HPP 2 | #define MINIDBG_BREAKPOINT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace minidbg { 8 | class breakpoint { 9 | public: 10 | breakpoint() = default; 11 | breakpoint(pid_t pid, std::intptr_t addr) : m_pid{pid}, m_addr{addr}, m_enabled{false}, m_saved_data{} {} 12 | 13 | void enable() { 14 | auto data = ptrace(PTRACE_PEEKDATA, m_pid, m_addr, nullptr); 15 | m_saved_data = static_cast(data & 0xff); //save bottom byte 16 | uint64_t int3 = 0xcc; 17 | uint64_t data_with_int3 = ((data & ~0xff) | int3); //set bottom byte to 0xcc 18 | ptrace(PTRACE_POKEDATA, m_pid, m_addr, data_with_int3); 19 | 20 | m_enabled = true; 21 | } 22 | 23 | void disable() { 24 | auto data = ptrace(PTRACE_PEEKDATA, m_pid, m_addr, nullptr); 25 | auto restored_data = ((data & ~0xff) | m_saved_data); 26 | ptrace(PTRACE_POKEDATA, m_pid, m_addr, restored_data); 27 | 28 | m_enabled = false; 29 | } 30 | 31 | bool is_enabled() const { return m_enabled; } 32 | 33 | auto get_address() const -> std::intptr_t { return m_addr; } 34 | private: 35 | pid_t m_pid; 36 | std::intptr_t m_addr; 37 | bool m_enabled; 38 | uint8_t m_saved_data; //data which used to be at the breakpoint address 39 | }; 40 | } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/debugger.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MINIDBG_DEBUGGER_HPP 2 | #define MINIDBG_DEBUGGER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "breakpoint.hpp" 10 | #include "dwarf/dwarf++.hh" 11 | #include "elf/elf++.hh" 12 | 13 | namespace minidbg { 14 | enum class symbol_type { 15 | notype, // No type (e.g., absolute symbol) 16 | object, // Data object 17 | func, // Function entry point 18 | section, // Symbol is associated with a section 19 | file, // Source file associated with the 20 | }; // object file 21 | 22 | std::string to_string (symbol_type st) { 23 | switch (st) { 24 | case symbol_type::notype: return "notype"; 25 | case symbol_type::object: return "object"; 26 | case symbol_type::func: return "func"; 27 | case symbol_type::section: return "section"; 28 | case symbol_type::file: return "file"; 29 | } 30 | } 31 | 32 | struct symbol { 33 | symbol_type type; 34 | std::string name; 35 | std::uintptr_t addr; 36 | }; 37 | 38 | class debugger { 39 | public: 40 | debugger (std::string prog_name, pid_t pid) 41 | : m_prog_name{std::move(prog_name)}, m_pid{pid} { 42 | auto fd = open(m_prog_name.c_str(), O_RDONLY); 43 | 44 | m_elf = elf::elf{elf::create_mmap_loader(fd)}; 45 | m_dwarf = dwarf::dwarf{dwarf::elf::create_loader(m_elf)}; 46 | } 47 | 48 | void run(); 49 | void set_breakpoint_at_address(std::intptr_t addr); 50 | void set_breakpoint_at_function(const std::string& name); 51 | void set_breakpoint_at_source_line(const std::string& file, unsigned line); 52 | void dump_registers(); 53 | void print_backtrace(); 54 | void read_variables(); 55 | void print_source(const std::string& file_name, unsigned line, unsigned n_lines_context=2); 56 | auto lookup_symbol(const std::string& name) -> std::vector; 57 | 58 | void single_step_instruction(); 59 | void single_step_instruction_with_breakpoint_check(); 60 | void step_in(); 61 | void step_over(); 62 | void step_out(); 63 | void remove_breakpoint(std::intptr_t addr); 64 | 65 | private: 66 | void handle_command(const std::string& line); 67 | void continue_execution(); 68 | auto get_pc() -> uint64_t; 69 | auto get_offset_pc() -> uint64_t; 70 | void set_pc(uint64_t pc); 71 | void step_over_breakpoint(); 72 | void wait_for_signal(); 73 | auto get_signal_info() -> siginfo_t; 74 | 75 | void handle_sigtrap(siginfo_t info); 76 | 77 | void initialise_load_address(); 78 | uint64_t offset_load_address(uint64_t addr); 79 | uint64_t offset_dwarf_address(uint64_t addr); 80 | 81 | auto get_function_from_pc(uint64_t pc) -> dwarf::die; 82 | auto get_line_entry_from_pc(uint64_t pc) -> dwarf::line_table::iterator; 83 | 84 | auto read_memory(uint64_t address) -> uint64_t ; 85 | void write_memory(uint64_t address, uint64_t value); 86 | 87 | std::string m_prog_name; 88 | pid_t m_pid; 89 | uint64_t m_load_address = 0; 90 | std::unordered_map m_breakpoints; 91 | dwarf::dwarf m_dwarf; 92 | elf::elf m_elf; 93 | }; 94 | } 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /include/registers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MINIDBG_REGISTERS_HPP 2 | #define MINIDBG_REGISTERS_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace minidbg { 8 | enum class reg { 9 | rax, rbx, rcx, rdx, 10 | rdi, rsi, rbp, rsp, 11 | r8, r9, r10, r11, 12 | r12, r13, r14, r15, 13 | rip, rflags, cs, 14 | orig_rax, fs_base, 15 | gs_base, 16 | fs, gs, ss, ds, es 17 | }; 18 | 19 | static constexpr std::size_t n_registers = 27; 20 | 21 | struct reg_descriptor { 22 | reg r; 23 | int dwarf_r; 24 | std::string name; 25 | }; 26 | 27 | //have a look in /usr/include/sys/user.h for how to lay this out 28 | static const std::array g_register_descriptors {{ 29 | { reg::r15, 15, "r15" }, 30 | { reg::r14, 14, "r14" }, 31 | { reg::r13, 13, "r13" }, 32 | { reg::r12, 12, "r12" }, 33 | { reg::rbp, 6, "rbp" }, 34 | { reg::rbx, 3, "rbx" }, 35 | { reg::r11, 11, "r11" }, 36 | { reg::r10, 10, "r10" }, 37 | { reg::r9, 9, "r9" }, 38 | { reg::r8, 8, "r8" }, 39 | { reg::rax, 0, "rax" }, 40 | { reg::rcx, 2, "rcx" }, 41 | { reg::rdx, 1, "rdx" }, 42 | { reg::rsi, 4, "rsi" }, 43 | { reg::rdi, 5, "rdi" }, 44 | { reg::orig_rax, -1, "orig_rax" }, 45 | { reg::rip, -1, "rip" }, 46 | { reg::cs, 51, "cs" }, 47 | { reg::rflags, 49, "eflags" }, 48 | { reg::rsp, 7, "rsp" }, 49 | { reg::ss, 52, "ss" }, 50 | { reg::fs_base, 58, "fs_base" }, 51 | { reg::gs_base, 59, "gs_base" }, 52 | { reg::ds, 53, "ds" }, 53 | { reg::es, 50, "es" }, 54 | { reg::fs, 54, "fs" }, 55 | { reg::gs, 55, "gs" }, 56 | }}; 57 | 58 | uint64_t get_register_value(pid_t pid, reg r) { 59 | user_regs_struct regs; 60 | ptrace(PTRACE_GETREGS, pid, nullptr, ®s); 61 | auto it = std::find_if(begin(g_register_descriptors), end(g_register_descriptors), 62 | [r](auto&& rd) { return rd.r == r; }); 63 | 64 | return *(reinterpret_cast(®s) + (it - begin(g_register_descriptors))); 65 | } 66 | 67 | void set_register_value(pid_t pid, reg r, uint64_t value) { 68 | user_regs_struct regs; 69 | ptrace(PTRACE_GETREGS, pid, nullptr, ®s); 70 | auto it = std::find_if(begin(g_register_descriptors), end(g_register_descriptors), 71 | [r](auto&& rd) { return rd.r == r; }); 72 | 73 | *(reinterpret_cast(®s) + (it - begin(g_register_descriptors))) = value; 74 | ptrace(PTRACE_SETREGS, pid, nullptr, ®s); 75 | } 76 | 77 | uint64_t get_register_value_from_dwarf_register (pid_t pid, unsigned regnum) { 78 | auto it = std::find_if(begin(g_register_descriptors), end(g_register_descriptors), 79 | [regnum](auto&& rd) { return rd.dwarf_r == regnum; }); 80 | if (it == end(g_register_descriptors)) { 81 | throw std::out_of_range{"Unknown dwarf register"}; 82 | } 83 | 84 | return get_register_value(pid, it->r); 85 | } 86 | 87 | std::string get_register_name(reg r) { 88 | auto it = std::find_if(begin(g_register_descriptors), end(g_register_descriptors), 89 | [r](auto&& rd) { return rd.r == r; }); 90 | return it->name; 91 | } 92 | 93 | reg get_register_from_name(const std::string& name) { 94 | auto it = std::find_if(begin(g_register_descriptors), end(g_register_descriptors), 95 | [name](auto&& rd) { return rd.name == name; }); 96 | return it->r; 97 | } 98 | } 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/minidbg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | #include "linenoise.h" 17 | 18 | #include "debugger.hpp" 19 | #include "registers.hpp" 20 | 21 | using namespace minidbg; 22 | 23 | class ptrace_expr_context : public dwarf::expr_context { 24 | public: 25 | ptrace_expr_context (pid_t pid, uint64_t load_address) : 26 | m_pid{pid}, m_load_address(load_address) {} 27 | 28 | dwarf::taddr reg (unsigned regnum) override { 29 | return get_register_value_from_dwarf_register(m_pid, regnum); 30 | } 31 | 32 | dwarf::taddr pc() override { 33 | struct user_regs_struct regs; 34 | ptrace(PTRACE_GETREGS, m_pid, nullptr, ®s); 35 | return regs.rip - m_load_address; 36 | } 37 | 38 | dwarf::taddr deref_size (dwarf::taddr address, unsigned size) override { 39 | //TODO take into account size 40 | return ptrace(PTRACE_PEEKDATA, m_pid, address + m_load_address, nullptr); 41 | } 42 | 43 | private: 44 | pid_t m_pid; 45 | uint64_t m_load_address; 46 | }; 47 | template class std::initializer_list; 48 | void debugger::read_variables() { 49 | using namespace dwarf; 50 | 51 | auto func = get_function_from_pc(get_offset_pc()); 52 | 53 | for (const auto& die : func) { 54 | if (die.tag == DW_TAG::variable) { 55 | auto loc_val = die[DW_AT::location]; 56 | 57 | //only supports exprlocs for now 58 | if (loc_val.get_type() == value::type::exprloc) { 59 | ptrace_expr_context context {m_pid, m_load_address}; 60 | auto result = loc_val.as_exprloc().evaluate(&context); 61 | 62 | switch (result.location_type) { 63 | case expr_result::type::address: 64 | { 65 | auto offset_addr = result.value; 66 | auto value = read_memory(offset_addr); 67 | std::cout << at_name(die) << " (0x" << std::hex << offset_addr << ") = " << value << std::endl; 68 | break; 69 | } 70 | 71 | case expr_result::type::reg: 72 | { 73 | auto value = get_register_value_from_dwarf_register(m_pid, result.value); 74 | std::cout << at_name(die) << " (reg " << result.value << ") = " << value << std::endl; 75 | break; 76 | } 77 | 78 | default: 79 | throw std::runtime_error{"Unhandled variable location"}; 80 | } 81 | } 82 | else { 83 | throw std::runtime_error{"Unhandled variable location"}; 84 | } 85 | } 86 | } 87 | } 88 | 89 | 90 | void debugger::print_backtrace() { 91 | auto output_frame = [frame_number = 0] (auto&& func) mutable { 92 | std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) 93 | << ' ' << dwarf::at_name(func) << std::endl; 94 | }; 95 | 96 | auto current_func = get_function_from_pc(offset_load_address(get_pc())); 97 | output_frame(current_func); 98 | 99 | auto frame_pointer = get_register_value(m_pid, reg::rbp); 100 | auto return_address = read_memory(frame_pointer+8); 101 | 102 | while (dwarf::at_name(current_func) != "main") { 103 | current_func = get_function_from_pc(offset_load_address(return_address)); 104 | output_frame(current_func); 105 | frame_pointer = read_memory(frame_pointer); 106 | return_address = read_memory(frame_pointer+8); 107 | } 108 | } 109 | 110 | symbol_type to_symbol_type(elf::stt sym) { 111 | switch (sym) { 112 | case elf::stt::notype: return symbol_type::notype; 113 | case elf::stt::object: return symbol_type::object; 114 | case elf::stt::func: return symbol_type::func; 115 | case elf::stt::section: return symbol_type::section; 116 | case elf::stt::file: return symbol_type::file; 117 | default: return symbol_type::notype; 118 | } 119 | }; 120 | 121 | std::vector debugger::lookup_symbol(const std::string& name) { 122 | std::vector syms; 123 | 124 | for (auto& sec : m_elf.sections()) { 125 | if (sec.get_hdr().type != elf::sht::symtab && sec.get_hdr().type != elf::sht::dynsym) 126 | continue; 127 | 128 | for (auto sym : sec.as_symtab()) { 129 | if (sym.get_name() == name) { 130 | auto& d = sym.get_data(); 131 | syms.push_back(symbol{ to_symbol_type(d.type()), sym.get_name(), d.value }); 132 | } 133 | } 134 | } 135 | 136 | return syms; 137 | } 138 | 139 | void debugger::initialise_load_address() { 140 | //If this is a dynamic library (e.g. PIE) 141 | if (m_elf.get_hdr().type == elf::et::dyn) { 142 | //The load address is found in /proc//maps 143 | std::ifstream map("/proc/" + std::to_string(m_pid) + "/maps"); 144 | 145 | //Read the first address from the file 146 | std::string addr; 147 | std::getline(map, addr, '-'); 148 | 149 | m_load_address = std::stoi(addr, 0, 16); 150 | } 151 | } 152 | 153 | uint64_t debugger::offset_load_address(uint64_t addr) { 154 | return addr - m_load_address; 155 | } 156 | 157 | uint64_t debugger::offset_dwarf_address(uint64_t addr) { 158 | return addr + m_load_address; 159 | } 160 | 161 | void debugger::remove_breakpoint(std::intptr_t addr) { 162 | if (m_breakpoints.at(addr).is_enabled()) { 163 | m_breakpoints.at(addr).disable(); 164 | } 165 | m_breakpoints.erase(addr); 166 | } 167 | 168 | void debugger::step_out() { 169 | auto frame_pointer = get_register_value(m_pid, reg::rbp); 170 | auto return_address = read_memory(frame_pointer+8); 171 | 172 | bool should_remove_breakpoint = false; 173 | if (!m_breakpoints.count(return_address)) { 174 | set_breakpoint_at_address(return_address); 175 | should_remove_breakpoint = true; 176 | } 177 | 178 | continue_execution(); 179 | 180 | if (should_remove_breakpoint) { 181 | remove_breakpoint(return_address); 182 | } 183 | } 184 | 185 | void debugger::step_in() { 186 | auto line = get_line_entry_from_pc(get_offset_pc())->line; 187 | 188 | while (get_line_entry_from_pc(get_offset_pc())->line == line) { 189 | single_step_instruction_with_breakpoint_check(); 190 | } 191 | 192 | auto line_entry = get_line_entry_from_pc(get_offset_pc()); 193 | print_source(line_entry->file->path, line_entry->line); 194 | } 195 | 196 | void debugger::step_over() { 197 | auto func = get_function_from_pc(get_offset_pc()); 198 | auto func_entry = at_low_pc(func); 199 | auto func_end = at_high_pc(func); 200 | 201 | auto line = get_line_entry_from_pc(func_entry); 202 | auto start_line = get_line_entry_from_pc(get_offset_pc()); 203 | 204 | std::vector to_delete{}; 205 | 206 | while (line->address < func_end) { 207 | auto load_address = offset_dwarf_address(line->address); 208 | if (line->address != start_line->address && !m_breakpoints.count(load_address)) { 209 | set_breakpoint_at_address(load_address); 210 | to_delete.push_back(load_address); 211 | } 212 | ++line; 213 | } 214 | 215 | auto frame_pointer = get_register_value(m_pid, reg::rbp); 216 | auto return_address = read_memory(frame_pointer+8); 217 | if (!m_breakpoints.count(return_address)) { 218 | set_breakpoint_at_address(return_address); 219 | to_delete.push_back(return_address); 220 | } 221 | 222 | continue_execution(); 223 | 224 | for (auto addr : to_delete) { 225 | remove_breakpoint(addr); 226 | } 227 | } 228 | 229 | void debugger::single_step_instruction() { 230 | ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr); 231 | wait_for_signal(); 232 | } 233 | 234 | void debugger::single_step_instruction_with_breakpoint_check() { 235 | //first, check to see if we need to disable and enable a breakpoint 236 | if (m_breakpoints.count(get_pc())) { 237 | step_over_breakpoint(); 238 | } 239 | else { 240 | single_step_instruction(); 241 | } 242 | } 243 | 244 | uint64_t debugger::read_memory(uint64_t address) { 245 | return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr); 246 | } 247 | 248 | void debugger::write_memory(uint64_t address, uint64_t value) { 249 | ptrace(PTRACE_POKEDATA, m_pid, address, value); 250 | } 251 | 252 | uint64_t debugger::get_pc() { 253 | return get_register_value(m_pid, reg::rip); 254 | } 255 | 256 | uint64_t debugger::get_offset_pc() { 257 | return offset_load_address(get_pc()); 258 | } 259 | 260 | void debugger::set_pc(uint64_t pc) { 261 | set_register_value(m_pid, reg::rip, pc); 262 | } 263 | 264 | dwarf::die debugger::get_function_from_pc(uint64_t pc) { 265 | for (auto &cu : m_dwarf.compilation_units()) { 266 | if (die_pc_range(cu.root()).contains(pc)) { 267 | for (const auto& die : cu.root()) { 268 | if (die.tag == dwarf::DW_TAG::subprogram) { 269 | if (die_pc_range(die).contains(pc)) { 270 | return die; 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | throw std::out_of_range{"Cannot find function"}; 278 | } 279 | 280 | dwarf::line_table::iterator debugger::get_line_entry_from_pc(uint64_t pc) { 281 | for (auto &cu : m_dwarf.compilation_units()) { 282 | if (die_pc_range(cu.root()).contains(pc)) { 283 | auto < = cu.get_line_table(); 284 | auto it = lt.find_address(pc); 285 | if (it == lt.end()) { 286 | throw std::out_of_range{"Cannot find line entry"}; 287 | } 288 | else { 289 | return it; 290 | } 291 | } 292 | } 293 | 294 | throw std::out_of_range{"Cannot find line entry"}; 295 | } 296 | 297 | void debugger::print_source(const std::string& file_name, unsigned line, unsigned n_lines_context) { 298 | std::ifstream file {file_name}; 299 | 300 | //Work out a window around the desired line 301 | auto start_line = line <= n_lines_context ? 1 : line - n_lines_context; 302 | auto end_line = line + n_lines_context + (line < n_lines_context ? n_lines_context - line : 0) + 1; 303 | 304 | char c{}; 305 | auto current_line = 1u; 306 | //Skip lines up until start_line 307 | while (current_line != start_line && file.get(c)) { 308 | if (c == '\n') { 309 | ++current_line; 310 | } 311 | } 312 | 313 | //Output cursor if we're at the current line 314 | std::cout << (current_line==line ? "> " : " "); 315 | 316 | //Write lines up until end_line 317 | while (current_line <= end_line && file.get(c)) { 318 | std::cout << c; 319 | if (c == '\n') { 320 | ++current_line; 321 | //Output cursor if we're at the current line 322 | std::cout << (current_line==line ? "> " : " "); 323 | } 324 | } 325 | 326 | //Write newline and make sure that the stream is flushed properly 327 | std::cout << std::endl; 328 | } 329 | 330 | siginfo_t debugger::get_signal_info() { 331 | siginfo_t info; 332 | ptrace(PTRACE_GETSIGINFO, m_pid, nullptr, &info); 333 | return info; 334 | } 335 | 336 | void debugger::step_over_breakpoint() { 337 | if (m_breakpoints.count(get_pc())) { 338 | auto& bp = m_breakpoints[get_pc()]; 339 | if (bp.is_enabled()) { 340 | bp.disable(); 341 | ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr); 342 | wait_for_signal(); 343 | bp.enable(); 344 | } 345 | } 346 | } 347 | 348 | void debugger::wait_for_signal() { 349 | int wait_status; 350 | auto options = 0; 351 | waitpid(m_pid, &wait_status, options); 352 | 353 | auto siginfo = get_signal_info(); 354 | 355 | switch (siginfo.si_signo) { 356 | case SIGTRAP: 357 | handle_sigtrap(siginfo); 358 | break; 359 | case SIGSEGV: 360 | std::cout << "Yay, segfault. Reason: " << siginfo.si_code << std::endl; 361 | break; 362 | default: 363 | std::cout << "Got signal " << strsignal(siginfo.si_signo) << std::endl; 364 | } 365 | } 366 | 367 | void debugger::handle_sigtrap(siginfo_t info) { 368 | switch (info.si_code) { 369 | //one of these will be set if a breakpoint was hit 370 | case SI_KERNEL: 371 | case TRAP_BRKPT: 372 | { 373 | set_pc(get_pc()-1); 374 | std::cout << "Hit breakpoint at address 0x" << std::hex << get_pc() << std::endl; 375 | auto offset_pc = offset_load_address(get_pc()); //rember to offset the pc for querying DWARF 376 | auto line_entry = get_line_entry_from_pc(offset_pc); 377 | print_source(line_entry->file->path, line_entry->line); 378 | return; 379 | } 380 | //this will be set if the signal was sent by single stepping 381 | case TRAP_TRACE: 382 | return; 383 | default: 384 | std::cout << "Unknown SIGTRAP code " << info.si_code << std::endl; 385 | return; 386 | } 387 | } 388 | 389 | void debugger::continue_execution() { 390 | step_over_breakpoint(); 391 | ptrace(PTRACE_CONT, m_pid, nullptr, nullptr); 392 | wait_for_signal(); 393 | } 394 | 395 | void debugger::dump_registers() { 396 | for (const auto& rd : g_register_descriptors) { 397 | std::cout << rd.name << " 0x" 398 | << std::setfill('0') << std::setw(16) << std::hex << get_register_value(m_pid, rd.r) << std::endl; 399 | } 400 | } 401 | 402 | std::vector split(const std::string &s, char delimiter) { 403 | std::vector out{}; 404 | std::stringstream ss {s}; 405 | std::string item; 406 | 407 | while (std::getline(ss,item,delimiter)) { 408 | out.push_back(item); 409 | } 410 | 411 | return out; 412 | } 413 | 414 | bool is_prefix(const std::string& s, const std::string& of) { 415 | if (s.size() > of.size()) return false; 416 | return std::equal(s.begin(), s.end(), of.begin()); 417 | } 418 | 419 | void debugger::handle_command(const std::string& line) { 420 | auto args = split(line,' '); 421 | auto command = args[0]; 422 | 423 | if (is_prefix(command, "cont")) { 424 | continue_execution(); 425 | } 426 | else if(is_prefix(command, "break")) { 427 | if (args[1][0] == '0' && args[1][1] == 'x') { 428 | std::string addr {args[1], 2}; 429 | set_breakpoint_at_address(std::stol(addr, 0, 16)); 430 | } 431 | else if (args[1].find(':') != std::string::npos) { 432 | auto file_and_line = split(args[1], ':'); 433 | set_breakpoint_at_source_line(file_and_line[0], std::stoi(file_and_line[1])); 434 | } 435 | else { 436 | set_breakpoint_at_function(args[1]); 437 | } 438 | } 439 | else if(is_prefix(command, "step")) { 440 | step_in(); 441 | } 442 | else if(is_prefix(command, "next")) { 443 | step_over(); 444 | } 445 | else if(is_prefix(command, "finish")) { 446 | step_out(); 447 | } 448 | 449 | else if(is_prefix(command, "step")) { 450 | step_in(); 451 | } 452 | 453 | else if(is_prefix(command, "next")) { 454 | step_over(); 455 | } 456 | 457 | else if(is_prefix(command, "finish")) { 458 | step_out(); 459 | } 460 | else if (is_prefix(command, "register")) { 461 | if (is_prefix(args[1], "dump")) { 462 | dump_registers(); 463 | } 464 | } 465 | else if (is_prefix(args[1], "read")) { 466 | std::cout << get_register_value(m_pid, get_register_from_name(args[2])) << std::endl; 467 | } 468 | else if (is_prefix(args[1], "write")) { 469 | std::string val {args[3], 2}; //assume 0xVAL 470 | set_register_value(m_pid, get_register_from_name(args[2]), std::stol(val, 0, 16)); 471 | } 472 | 473 | else if (is_prefix(command, "register")) { 474 | if (is_prefix(args[1], "dump")) { 475 | dump_registers(); 476 | } 477 | else if (is_prefix(args[1], "read")) { 478 | std::cout << get_register_value(m_pid, get_register_from_name(args[2])) << std::endl; 479 | } 480 | else if (is_prefix(args[1], "write")) { 481 | std::string val {args[3], 2}; //assume 0xVAL 482 | set_register_value(m_pid, get_register_from_name(args[2]), std::stol(val, 0, 16)); 483 | } 484 | } 485 | 486 | else if(is_prefix(command, "memory")) { 487 | std::string addr {args[2], 2}; //assume 0xADDRESS 488 | 489 | if (is_prefix(args[1], "read")) { 490 | std::cout << std::hex << read_memory(std::stol(addr, 0, 16)) << std::endl; 491 | } 492 | if (is_prefix(args[1], "write")) { 493 | std::string val {args[3], 2}; //assume 0xVAL 494 | write_memory(std::stol(addr, 0, 16), std::stol(val, 0, 16)); 495 | } 496 | } 497 | 498 | else if(is_prefix(command, "variables")) { 499 | read_variables(); 500 | } 501 | 502 | else if(is_prefix(command, "backtrace")) { 503 | print_backtrace(); 504 | } 505 | else if(is_prefix(command, "symbol")) { 506 | auto syms = lookup_symbol(args[1]); 507 | for (auto&& s : syms) { 508 | std::cout << s.name << ' ' << to_string(s.type) << " 0x" << std::hex << s.addr << std::endl; 509 | } 510 | } 511 | else if(is_prefix(command, "stepi")) { 512 | single_step_instruction_with_breakpoint_check(); 513 | auto line_entry = get_line_entry_from_pc(get_pc()); 514 | print_source(line_entry->file->path, line_entry->line); 515 | } 516 | else { 517 | std::cerr << "Unknown command\n"; 518 | } 519 | } 520 | 521 | bool is_suffix(const std::string& s, const std::string& of) { 522 | if (s.size() > of.size()) return false; 523 | auto diff = of.size() - s.size(); 524 | return std::equal(s.begin(), s.end(), of.begin() + diff); 525 | } 526 | 527 | void debugger::set_breakpoint_at_function(const std::string& name) { 528 | for (const auto& cu : m_dwarf.compilation_units()) { 529 | for (const auto& die : cu.root()) { 530 | if (die.has(dwarf::DW_AT::name) && at_name(die) == name) { 531 | auto low_pc = at_low_pc(die); 532 | auto entry = get_line_entry_from_pc(low_pc); 533 | ++entry; //skip prologue 534 | set_breakpoint_at_address(offset_dwarf_address(entry->address)); 535 | } 536 | } 537 | } 538 | } 539 | 540 | void debugger::set_breakpoint_at_source_line(const std::string& file, unsigned line) { 541 | for (const auto& cu : m_dwarf.compilation_units()) { 542 | if (is_suffix(file, at_name(cu.root()))) { 543 | const auto& lt = cu.get_line_table(); 544 | 545 | for (const auto& entry : lt) { 546 | if (entry.is_stmt && entry.line == line) { 547 | set_breakpoint_at_address(offset_dwarf_address(entry.address)); 548 | return; 549 | } 550 | } 551 | } 552 | } 553 | } 554 | 555 | void debugger::set_breakpoint_at_address(std::intptr_t addr) { 556 | std::cout << "Set breakpoint at address 0x" << std::hex << addr << std::endl; 557 | breakpoint bp {m_pid, addr}; 558 | bp.enable(); 559 | m_breakpoints[addr] = bp; 560 | } 561 | 562 | void debugger::run() { 563 | wait_for_signal(); 564 | initialise_load_address(); 565 | 566 | char* line = nullptr; 567 | while((line = linenoise("minidbg> ")) != nullptr) { 568 | handle_command(line); 569 | linenoiseHistoryAdd(line); 570 | linenoiseFree(line); 571 | } 572 | } 573 | 574 | void execute_debugee (const std::string& prog_name) { 575 | if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { 576 | std::cerr << "Error in ptrace\n"; 577 | return; 578 | } 579 | execl(prog_name.c_str(), prog_name.c_str(), nullptr); 580 | } 581 | 582 | int main(int argc, char* argv[]) { 583 | if (argc < 2) { 584 | std::cerr << "Program name not specified"; 585 | return -1; 586 | } 587 | 588 | auto prog = argv[1]; 589 | 590 | auto pid = fork(); 591 | if (pid == 0) { 592 | //child 593 | personality(ADDR_NO_RANDOMIZE); 594 | execute_debugee(prog); 595 | } 596 | else if (pid >= 1) { 597 | //parent 598 | std::cout << "Started debugging process " << pid << '\n'; 599 | debugger dbg{prog, pid}; 600 | dbg.run(); 601 | } 602 | } 603 | --------------------------------------------------------------------------------