aboutsummaryrefslogtreecommitdiff
path: root/src/debugger
diff options
context:
space:
mode:
Diffstat (limited to 'src/debugger')
-rw-r--r--src/debugger/debugger.cpp333
-rw-r--r--src/debugger/debugger.h13
-rw-r--r--src/debugger/types.h31
3 files changed, 373 insertions, 4 deletions
diff --git a/src/debugger/debugger.cpp b/src/debugger/debugger.cpp
index 00bdda7..9bc36da 100644
--- a/src/debugger/debugger.cpp
+++ b/src/debugger/debugger.cpp
@@ -135,7 +135,9 @@ namespace Hsdbg
breakpoint.line = line_entry.GetLine();
- if (breakpoint.by_function)
+ // only a file and line breakpoint was told where it belongs, the rest
+ // learn it from wherever lldb landed
+ if (breakpoint.by_function || breakpoint.by_address)
breakpoint.file = path_of(line_entry.GetFileSpec());
}
@@ -190,6 +192,10 @@ namespace Hsdbg
constexpr uint32_t MAX_VARIABLE_DEPTH = 3;
constexpr uint32_t MAX_VARIABLE_CHILDREN = 64;
+ // only used for frames lldb cannot name, where there is no function to
+ // bound the listing
+ constexpr uint32_t DISASSEMBLY_WINDOW = 64;
+
auto frame_of(lldb::SBProcess& process, uint64_t thread_id, uint32_t frame_index) -> lldb::SBFrame
{
lldb::SBThread thread = process.GetThreadByID(thread_id);
@@ -200,6 +206,114 @@ namespace Hsdbg
return thread.GetFrameAtIndex(frame_index);
}
+ auto instruction_of(lldb::SBTarget& target,
+ lldb::SBInstruction source,
+ lldb::addr_t program_counter) -> Instruction
+ {
+ Instruction entry;
+
+ if (!source.IsValid())
+ return entry;
+
+ lldb::SBAddress address = source.GetAddress();
+ const lldb::addr_t load = address.GetLoadAddress(target);
+
+ entry.file_address = address.GetFileAddress();
+ entry.address = load != LLDB_INVALID_ADDRESS ? load : entry.file_address;
+ entry.mnemonic = text_or(source.GetMnemonic(target), "?");
+ entry.operands = text_or(source.GetOperands(target), "");
+ entry.comment = text_or(source.GetComment(target), "");
+ entry.size = static_cast<uint32_t>(source.GetByteSize());
+ entry.current = program_counter != LLDB_INVALID_ADDRESS &&
+ entry.address == program_counter;
+
+ return entry;
+ }
+
+ auto instructions_of(lldb::SBTarget& target,
+ lldb::SBInstructionList source,
+ lldb::addr_t program_counter) -> std::vector<Instruction>
+ {
+ std::vector<Instruction> instructions;
+
+ for (uint32_t index = 0; index < source.GetSize(); ++index)
+ {
+ Instruction entry = instruction_of(target, source.GetInstructionAtIndex(index), program_counter);
+
+ if (entry.file_address == 0 && entry.mnemonic.empty())
+ continue;
+
+ instructions.push_back(std::move(entry));
+ }
+
+ return instructions;
+ }
+
+ auto module_symbols(lldb::SBTarget& target) -> std::vector<Symbol>
+ {
+ std::vector<Symbol> symbols;
+
+ lldb::SBModule module = target.FindModule(target.GetExecutable());
+
+ if (!module.IsValid())
+ return symbols;
+
+ struct Candidate
+ {
+ uint64_t file_address = 0;
+ uint64_t size = 0;
+ lldb::SBSymbol symbol;
+ };
+
+ std::vector<Candidate> candidates;
+
+ for (size_t index = 0; index < module.GetNumSymbols(); ++index)
+ {
+ lldb::SBSymbol symbol = module.GetSymbolAtIndex(index);
+
+ if (!symbol.IsValid() || symbol.GetType() != lldb::eSymbolTypeCode)
+ continue;
+
+ lldb::SBAddress start = symbol.GetStartAddress();
+
+ if (!start.IsValid())
+ continue;
+
+ candidates.push_back({ start.GetFileAddress(), symbol.GetSize(), symbol });
+ }
+
+ std::ranges::sort(candidates, [](const Candidate& left, const Candidate& right)
+ {
+ if (left.file_address != right.file_address)
+ return left.file_address < right.file_address;
+
+ return left.size > right.size;
+ });
+
+ uint64_t previous = LLDB_INVALID_ADDRESS;
+
+ for (Candidate& candidate : candidates)
+ {
+ if (candidate.file_address == previous)
+ continue;
+
+ previous = candidate.file_address;
+
+ const lldb::addr_t load = candidate.symbol.GetStartAddress().GetLoadAddress(target);
+
+ Symbol entry;
+ entry.name = text_or(candidate.symbol.GetDisplayName(),
+ text_or(candidate.symbol.GetName(), "?"));
+ entry.file_address = candidate.file_address;
+ entry.address = load != LLDB_INVALID_ADDRESS ? load : candidate.file_address;
+ entry.size = candidate.size;
+
+ symbols.push_back(std::move(entry));
+ }
+
+ return symbols;
+ }
+
auto display_of(lldb::SBValue& value) -> std::string
{
const char* text = value.GetValue();
@@ -341,6 +455,11 @@ namespace Hsdbg
for (Breakpoint& breakpoint : m_breakpoints)
resolve_breakpoint(breakpoint);
+ m_disassembly.clear();
+ m_disassembly_name.clear();
+ m_selected_symbol = 0;
+ refresh_disassembly();
+
return {};
}
@@ -360,21 +479,25 @@ namespace Hsdbg
m_call_stack.clear();
m_locals.clear();
m_registers.clear();
+ m_symbols.clear();
+ m_disassembly.clear();
+ m_disassembly_name.clear();
m_console_output.clear();
m_selected_thread = 0;
m_selected_frame = 0;
+ m_selected_symbol = 0;
for (Breakpoint& breakpoint : m_breakpoints)
{
breakpoint.backend_id = 0;
breakpoint.resolved = false;
breakpoint.hit_count = 0;
- breakpoint.address = 0;
+ breakpoint.address = breakpoint.by_address ? breakpoint.file_address : 0;
// whatever lldb told us about a named breakpoint belonged to the
// binary that just went away
- if (breakpoint.by_function)
+ if (breakpoint.by_function || breakpoint.by_address)
{
breakpoint.file.clear();
breakpoint.line = 0;
@@ -727,6 +850,30 @@ namespace Hsdbg
return m_breakpoints.back().id;
}
+ auto Debugger::add_address_breakpoint(uint64_t file_address) -> uint32_t
+ {
+ const auto existing = std::ranges::find_if(m_breakpoints, [&](const Breakpoint& candidate)
+ {
+ return candidate.by_address && candidate.file_address == file_address;
+ });
+
+ if (existing != m_breakpoints.end())
+ return existing->id;
+
+ Breakpoint breakpoint;
+ breakpoint.id = m_next_breakpoint_id++;
+ breakpoint.by_address = true;
+ breakpoint.file_address = file_address;
+ breakpoint.address = file_address;
+
+ m_breakpoints.push_back(std::move(breakpoint));
+ resolve_breakpoint(m_breakpoints.back());
+
+ Log::info("debugger: breakpoint {} at 0x{:x}", m_breakpoints.back().id, file_address);
+
+ return m_breakpoints.back().id;
+ }
+
auto Debugger::remove_breakpoint(uint32_t id) -> bool
{
const auto entry = std::ranges::find(m_breakpoints, id, &Breakpoint::id);
@@ -910,6 +1057,16 @@ namespace Hsdbg
return true;
}
+ auto Debugger::select_symbol(uint64_t file_address) -> bool
+ {
+ if (!m_session->target.IsValid() || file_address == 0)
+ return false;
+
+ load_disassembly(file_address);
+
+ return !m_disassembly.empty();
+ }
+
auto Debugger::update() -> void
{
pump_events();
@@ -975,6 +1132,11 @@ namespace Hsdbg
case lldb::eStateStepping:
m_locals.clear();
m_registers.clear();
+
+ // the instructions are still the ones on screen, only the
+ // program counter stopped meaning anything
+ for (Instruction& instruction : m_disassembly)
+ instruction.current = false;
set_state(TargetState::Running);
break;
@@ -1129,6 +1291,9 @@ namespace Hsdbg
m_selected_thread = 0;
m_selected_frame = 0;
+ // the target outlives the process, so fall back to reading the binary
+ refresh_disassembly();
+
set_state(TargetState::Exited);
}
@@ -1173,6 +1338,8 @@ namespace Hsdbg
m_locals.clear();
m_registers.clear();
+ refresh_disassembly();
+
if (!is_alive(m_session->process))
return;
@@ -1215,6 +1382,155 @@ namespace Hsdbg
}
}
+ auto Debugger::refresh_symbols() -> void
+ {
+ m_symbols.clear();
+
+ if (!m_session->target.IsValid())
+ return;
+
+ m_symbols = module_symbols(m_session->target);
+ }
+
+ auto Debugger::load_disassembly(uint64_t file_address) -> void
+ {
+ m_disassembly.clear();
+ m_disassembly_name.clear();
+ m_selected_symbol = 0;
+
+ if (!m_session->target.IsValid() || file_address == 0)
+ return;
+
+ lldb::SBAddress address = m_session->target.ResolveFileAddress(file_address);
+
+ if (!address.IsValid())
+ return;
+
+ lldb::addr_t program_counter = LLDB_INVALID_ADDRESS;
+
+ if (is_alive(m_session->process))
+ {
+ lldb::SBFrame frame = frame_of(m_session->process, m_selected_thread, m_selected_frame);
+
+ if (frame.IsValid())
+ program_counter = frame.GetPC();
+ }
+
+ lldb::SBSymbolContext context = address.GetSymbolContext(lldb::eSymbolContextEverything);
+ lldb::SBInstructionList instructions;
+ std::string name;
+
+ if (lldb::SBFunction function = context.GetFunction(); function.IsValid())
+ {
+ instructions = function.GetInstructions(m_session->target);
+ name = text_or(function.GetDisplayName(), text_or(function.GetName(), "?"));
+ }
+ else if (lldb::SBSymbol symbol = context.GetSymbol(); symbol.IsValid())
+ {
+ instructions = symbol.GetInstructions(m_session->target);
+ name = text_or(symbol.GetDisplayName(), text_or(symbol.GetName(), "?"));
+ }
+ else
+ {
+ instructions = m_session->target.ReadInstructions(address, DISASSEMBLY_WINDOW);
+ name = std::format("{:#x}", file_address);
+ }
+
+ m_disassembly = instructions_of(m_session->target, instructions, program_counter);
+ m_disassembly_name = std::move(name);
+ m_selected_symbol = file_address;
+ }
+
+ auto Debugger::refresh_disassembly() -> void
+ {
+ if (!m_session->target.IsValid())
+ {
+ m_symbols.clear();
+ m_disassembly.clear();
+ m_disassembly_name.clear();
+ m_selected_symbol = 0;
+ return;
+ }
+
+ if (m_symbols.empty())
+ refresh_symbols();
+
+ for (Symbol& symbol : m_symbols)
+ {
+ lldb::SBAddress address = m_session->target.ResolveFileAddress(symbol.file_address);
+ const lldb::addr_t load = address.IsValid()
+ ? address.GetLoadAddress(m_session->target)
+ : LLDB_INVALID_ADDRESS;
+
+ symbol.address = load != LLDB_INVALID_ADDRESS ? load : symbol.file_address;
+ }
+
+ lldb::SBFrame frame;
+ lldb::addr_t program_counter = LLDB_INVALID_ADDRESS;
+ lldb::addr_t file_pc = LLDB_INVALID_ADDRESS;
+
+ if (is_alive(m_session->process))
+ frame = frame_of(m_session->process, m_selected_thread, m_selected_frame);
+
+ if (frame.IsValid())
+ {
+ program_counter = frame.GetPC();
+ file_pc = frame.GetPCAddress().GetFileAddress();
+ }
+
+ uint64_t wanted = m_selected_symbol;
+
+ if (file_pc != LLDB_INVALID_ADDRESS)
+ {
+ const auto containing = std::ranges::find_if(m_symbols, [&](const Symbol& symbol)
+ {
+ if (symbol.size == 0)
+ return symbol.file_address == file_pc;
+
+ return file_pc >= symbol.file_address &&
+ file_pc < symbol.file_address + symbol.size;
+ });
+
+ if (containing != m_symbols.end())
+ {
+ wanted = containing->file_address;
+ }
+ else if (lldb::SBFunction function = frame.GetFunction(); function.IsValid())
+ {
+ wanted = function.GetStartAddress().GetFileAddress();
+ }
+ else if (lldb::SBSymbol symbol = frame.GetSymbol(); symbol.IsValid())
+ {
+ wanted = symbol.GetStartAddress().GetFileAddress();
+ }
+ }
+
+ if (wanted == 0)
+ {
+ const auto main = std::ranges::find(m_symbols, "main", &Symbol::name);
+ wanted = main != m_symbols.end() ? main->file_address
+ : (m_symbols.empty() ? 0 : m_symbols.front().file_address);
+ }
+
+ if (wanted != 0 && (wanted != m_selected_symbol || m_disassembly.empty()))
+ {
+ load_disassembly(wanted);
+ return;
+ }
+
+ for (Instruction& instruction : m_disassembly)
+ {
+ lldb::SBAddress address = m_session->target.ResolveFileAddress(instruction.file_address);
+ const lldb::addr_t load = address.IsValid()
+ ? address.GetLoadAddress(m_session->target)
+ : LLDB_INVALID_ADDRESS;
+
+ instruction.address = load != LLDB_INVALID_ADDRESS ? load : instruction.file_address;
+ instruction.current = program_counter != LLDB_INVALID_ADDRESS &&
+ instruction.address == program_counter;
+ }
+ }
+
auto Debugger::resolve_breakpoint(Breakpoint& breakpoint) -> void
{
if (!m_session->target.IsValid())
@@ -1222,7 +1538,16 @@ namespace Hsdbg
lldb::SBBreakpoint created;
- if (breakpoint.by_function)
+ if (breakpoint.by_address)
+ {
+ // a section relative address keeps pointing at the same instruction
+ // when the next run loads the binary somewhere else
+ lldb::SBAddress address = m_session->target.ResolveFileAddress(breakpoint.file_address);
+
+ if (address.IsValid())
+ created = m_session->target.BreakpointCreateBySBAddress(address);
+ }
+ else if (breakpoint.by_function)
{
// unscoped, a name like main matches every module the target pulls in
created = m_session->target.BreakpointCreateByName(
diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h
index 82a993a..175683e 100644
--- a/src/debugger/debugger.h
+++ b/src/debugger/debugger.h
@@ -43,6 +43,7 @@ namespace Hsdbg
// breakpoints, the only part that keeps real state for now
auto add_breakpoint(const std::filesystem::path& file, uint32_t line) -> uint32_t;
auto add_function_breakpoint(std::string_view function) -> uint32_t;
+ auto add_address_breakpoint(uint64_t file_address) -> uint32_t;
auto remove_breakpoint(uint32_t id) -> bool;
auto set_breakpoint_enabled(uint32_t id, bool enabled) -> bool;
auto set_breakpoint_condition(uint32_t id, std::string_view condition) -> bool;
@@ -56,6 +57,9 @@ namespace Hsdbg
auto call_stack() const -> std::span<const StackFrame> { return m_call_stack; }
auto locals() const -> std::span<const Variable> { return m_locals; }
auto registers() const -> std::span<const Register> { return m_registers; }
+ auto symbols() const -> std::span<const Symbol> { return m_symbols; }
+ auto disassembly() const -> std::span<const Instruction> { return m_disassembly; }
+ auto disassembly_name() const -> std::string_view { return m_disassembly_name; }
auto evaluate(std::string_view expression) -> Result<std::string>;
auto read_memory(uint64_t address, size_t size) -> Result<std::vector<uint8_t>>;
auto console_output() const -> std::span<const std::string> { return m_console_output; }
@@ -63,8 +67,10 @@ namespace Hsdbg
// selection, what the ui is currently looking at
auto select_thread(uint64_t thread_id) -> bool;
auto select_frame(uint32_t frame_index) -> bool;
+ auto select_symbol(uint64_t file_address) -> bool;
auto selected_thread() const -> uint64_t { return m_selected_thread; }
auto selected_frame() const -> uint32_t { return m_selected_frame; }
+ auto selected_symbol() const -> uint64_t { return m_selected_symbol; }
// pumps whatever the debug session has to say, called once per frame
auto update() -> void;
@@ -99,6 +105,9 @@ namespace Hsdbg
auto on_exited() -> void;
auto refresh_call_stack() -> void;
auto refresh_frame_data() -> void;
+ auto refresh_symbols() -> void;
+ auto refresh_disassembly() -> void;
+ auto load_disassembly(uint64_t file_address) -> void;
std::unique_ptr<Session> m_session;
@@ -115,10 +124,14 @@ namespace Hsdbg
std::vector<StackFrame> m_call_stack;
std::vector<Variable> m_locals;
std::vector<Register> m_registers;
+ std::vector<Symbol> m_symbols;
+ std::vector<Instruction> m_disassembly;
+ std::string m_disassembly_name;
std::vector<std::string> m_console_output;
uint64_t m_selected_thread = 0;
uint32_t m_selected_frame = 0;
+ uint64_t m_selected_symbol = 0;
uint64_t m_stop_count = 0;
};
}
diff --git a/src/debugger/types.h b/src/debugger/types.h
index dec74e5..e288f85 100644
--- a/src/debugger/types.h
+++ b/src/debugger/types.h
@@ -96,6 +96,11 @@ namespace Hsdbg
// set by name, so file and line are whatever lldb found rather than
// what was asked for
bool by_function = false;
+
+ // set on an instruction, identified by where it sits in the binary so it
+ // survives the process being restarted somewhere else
+ bool by_address = false;
+ uint64_t file_address = 0;
};
struct StackFrame
@@ -127,4 +132,30 @@ namespace Hsdbg
std::string name;
uint64_t value = 0;
};
+
+ struct Instruction
+ {
+ // where the instruction is right now, which is the load address once a
+ // process is running and the address in the binary before that
+ uint64_t address = 0;
+
+ // the same instruction across runs, so a breakpoint can point at it
+ uint64_t file_address = 0;
+
+ std::string mnemonic;
+ std::string operands;
+ std::string comment;
+ uint32_t size = 0;
+
+ // whether the selected frame is sitting on this one
+ bool current = false;
+ };
+
+ struct Symbol
+ {
+ std::string name;
+ uint64_t file_address = 0;
+ uint64_t address = 0;
+ uint64_t size = 0;
+ };
}