1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 15:37:46 +00:00

LibDebug: Support shared libraries

DebugSession now makes the loader stop after loading the libraries,
and parses the loaded libraries of the program before continuing its
execution.

DebugSession now also supports inserting a breakpoint at a given symbol
or source position.
Additionally, DebugInfo now takes the base address of its object into
consideration.
This commit is contained in:
Itamar 2021-01-06 21:49:25 +02:00 committed by Andreas Kling
parent ca9d6d21b5
commit 4b91e7c821
6 changed files with 328 additions and 53 deletions

View file

@ -11,4 +11,4 @@ set(SOURCES
) )
serenity_lib(LibDebug debug) serenity_lib(LibDebug debug)
target_link_libraries(LibDebug LibC) target_link_libraries(LibDebug LibC LibRegex)

View file

@ -25,6 +25,7 @@
*/ */
#include "DebugInfo.h" #include "DebugInfo.h"
#include <AK/LexicalPath.h>
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <AK/QuickSort.h> #include <AK/QuickSort.h>
#include <LibDebug/Dwarf/CompilationUnit.h> #include <LibDebug/Dwarf/CompilationUnit.h>
@ -35,8 +36,10 @@
namespace Debug { namespace Debug {
DebugInfo::DebugInfo(NonnullOwnPtr<const ELF::Image> elf) DebugInfo::DebugInfo(NonnullOwnPtr<const ELF::Image> elf, String source_root, FlatPtr base_address)
: m_elf(move(elf)) : m_elf(move(elf))
, m_source_root(source_root)
, m_base_address(base_address)
, m_dwarf_info(*m_elf) , m_dwarf_info(*m_elf)
{ {
prepare_variable_scopes(); prepare_variable_scopes();
@ -124,6 +127,9 @@ void DebugInfo::prepare_lines()
auto start_index = file_path.index_of(serenity_slash).value() + serenity_slash.length(); auto start_index = file_path.index_of(serenity_slash).value() + serenity_slash.length();
file_path = file_path.substring(start_index, file_path.length() - start_index); file_path = file_path.substring(start_index, file_path.length() - start_index);
} }
if (file_path.starts_with("./") && !m_source_root.is_null()) {
file_path = LexicalPath::canonicalized_path(String::formatted("{}/{}", m_source_root, file_path));
}
m_sorted_lines.append({ line_info.address, file_path, line_info.line }); m_sorted_lines.append({ line_info.address, file_path, line_info.line });
} }
quick_sort(m_sorted_lines, [](auto& a, auto& b) { quick_sort(m_sorted_lines, [](auto& a, auto& b) {
@ -147,19 +153,34 @@ Optional<DebugInfo::SourcePosition> DebugInfo::get_source_position(u32 target_ad
return {}; return {};
} }
Optional<u32> DebugInfo::get_instruction_from_source(const String& file, size_t line) const Optional<DebugInfo::SourcePositionAndAddress> DebugInfo::get_address_from_source_position(const String& file, size_t line) const
{ {
String file_path = file; String file_path = file;
if (!file_path.starts_with("/"))
file_path = String::format("/%s", file_path.characters());
constexpr char SERENITY_LIBS_PREFIX[] = "/usr/src/serenity"; constexpr char SERENITY_LIBS_PREFIX[] = "/usr/src/serenity";
if (file.starts_with(SERENITY_LIBS_PREFIX)) { if (file.starts_with(SERENITY_LIBS_PREFIX)) {
file_path = file.substring(sizeof(SERENITY_LIBS_PREFIX), file.length() - sizeof(SERENITY_LIBS_PREFIX)); file_path = file.substring(sizeof(SERENITY_LIBS_PREFIX), file.length() - sizeof(SERENITY_LIBS_PREFIX));
file_path = String::format("../%s", file_path.characters()); file_path = String::format("../%s", file_path.characters());
} }
Optional<SourcePositionAndAddress> result;
for (const auto& line_entry : m_sorted_lines) { for (const auto& line_entry : m_sorted_lines) {
if (line_entry.file == file_path && line_entry.line == line) if (!line_entry.file.ends_with(file_path))
return Optional<u32>(line_entry.address); continue;
if (line_entry.line > line)
continue;
// We look for the source position that is closest to the desired position, and is not after it.
// For example, get_address_of_source_position("main.cpp", 73) could return the address for an instruction whose location is ("main.cpp", 72)
// as there might not be an instruction mapped for "main.cpp", 73.
if (!result.has_value() || (line_entry.line > result.value().line)) {
result = SourcePositionAndAddress { line_entry.file, line_entry.line, line_entry.address };
}
} }
return {}; return result;
} }
NonnullOwnPtrVector<DebugInfo::VariableInfo> DebugInfo::get_variables_in_current_scope(const PtraceRegisters& regs) const NonnullOwnPtrVector<DebugInfo::VariableInfo> DebugInfo::get_variables_in_current_scope(const PtraceRegisters& regs) const
@ -168,7 +189,7 @@ NonnullOwnPtrVector<DebugInfo::VariableInfo> DebugInfo::get_variables_in_current
// TODO: We can store the scopes in a better data structure // TODO: We can store the scopes in a better data structure
for (const auto& scope : m_scopes) { for (const auto& scope : m_scopes) {
if (regs.eip < scope.address_low || regs.eip >= scope.address_high) if (regs.eip - m_base_address < scope.address_low || regs.eip - m_base_address >= scope.address_high)
continue; continue;
for (const auto& die_entry : scope.dies_of_variables) { for (const auto& die_entry : scope.dies_of_variables) {
@ -336,7 +357,7 @@ Vector<DebugInfo::SourcePosition> DebugInfo::source_lines_in_scope(const Variabl
DebugInfo::SourcePosition DebugInfo::SourcePosition::from_line_info(const Dwarf::LineProgram::LineInfo& line) DebugInfo::SourcePosition DebugInfo::SourcePosition::from_line_info(const Dwarf::LineProgram::LineInfo& line)
{ {
return { line.file, line.line, line.address }; return { line.file, line.line, { line.address } };
} }
} }

View file

@ -40,14 +40,30 @@ namespace Debug {
class DebugInfo { class DebugInfo {
public: public:
explicit DebugInfo(NonnullOwnPtr<const ELF::Image>); explicit DebugInfo(NonnullOwnPtr<const ELF::Image>, String source_root = {}, FlatPtr base_address = 0);
const ELF::Image& elf() const { return *m_elf; } const ELF::Image& elf() const { return *m_elf; }
struct SourcePosition { struct SourcePosition {
FlyString file_path; FlyString file_path;
size_t line_number { 0 }; size_t line_number { 0 };
u32 address_of_first_statement { 0 }; Optional<u32> address_of_first_statement;
SourcePosition()
: SourcePosition(String::empty(), 0)
{
}
SourcePosition(String file_path, size_t line_number)
: file_path(file_path)
, line_number(line_number)
{
}
SourcePosition(String file_path, size_t line_number, u32 address_of_first_statement)
: file_path(file_path)
, line_number(line_number)
, address_of_first_statement(address_of_first_statement)
{
}
bool operator==(const SourcePosition& other) const { return file_path == other.file_path && line_number == other.line_number; } bool operator==(const SourcePosition& other) const { return file_path == other.file_path && line_number == other.line_number; }
bool operator!=(const SourcePosition& other) const { return !(*this == other); } bool operator!=(const SourcePosition& other) const { return !(*this == other); }
@ -93,7 +109,14 @@ public:
NonnullOwnPtrVector<VariableInfo> get_variables_in_current_scope(const PtraceRegisters&) const; NonnullOwnPtrVector<VariableInfo> get_variables_in_current_scope(const PtraceRegisters&) const;
Optional<SourcePosition> get_source_position(u32 address) const; Optional<SourcePosition> get_source_position(u32 address) const;
Optional<u32> get_instruction_from_source(const String& file, size_t line) const;
struct SourcePositionAndAddress {
String file;
size_t line;
FlatPtr address;
};
Optional<SourcePositionAndAddress> get_address_from_source_position(const String& file, size_t line) const;
template<typename Callback> template<typename Callback>
void for_each_source_position(Callback callback) const void for_each_source_position(Callback callback) const
@ -120,6 +143,8 @@ private:
OwnPtr<VariableInfo> create_variable_info(const Dwarf::DIE& variable_die, const PtraceRegisters&) const; OwnPtr<VariableInfo> create_variable_info(const Dwarf::DIE& variable_die, const PtraceRegisters&) const;
NonnullOwnPtr<const ELF::Image> m_elf; NonnullOwnPtr<const ELF::Image> m_elf;
String m_source_root;
FlatPtr m_base_address { 0 };
Dwarf::DwarfInfo m_dwarf_info; Dwarf::DwarfInfo m_dwarf_info;
Vector<VariablesScope> m_scopes; Vector<VariablesScope> m_scopes;

View file

@ -25,15 +25,20 @@
*/ */
#include "DebugSession.h" #include "DebugSession.h"
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/LexicalPath.h>
#include <AK/Optional.h> #include <AK/Optional.h>
#include <LibCore/File.h>
#include <LibRegex/Regex.h>
#include <stdlib.h> #include <stdlib.h>
namespace Debug { namespace Debug {
DebugSession::DebugSession(pid_t pid) DebugSession::DebugSession(pid_t pid, String source_root)
: m_debuggee_pid(pid) : m_debuggee_pid(pid)
, m_executable(map_executable_for_process(pid)) , m_source_root(source_root)
, m_debug_info(make<ELF::Image>(reinterpret_cast<const u8*>(m_executable.data()), m_executable.size()))
{ {
} }
@ -59,7 +64,7 @@ DebugSession::~DebugSession()
} }
} }
OwnPtr<DebugSession> DebugSession::exec_and_attach(const String& command) OwnPtr<DebugSession> DebugSession::exec_and_attach(const String& command, String source_root)
{ {
auto pid = fork(); auto pid = fork();
@ -80,7 +85,10 @@ OwnPtr<DebugSession> DebugSession::exec_and_attach(const String& command)
for (size_t i = 0; i < parts.size(); i++) { for (size_t i = 0; i < parts.size(); i++) {
args[i] = parts[i].characters(); args[i] = parts[i].characters();
} }
int rc = execvp(args[0], const_cast<char**>(args)); const char** envp = (const char**)calloc(2, sizeof(const char*));
// This causes loader to stop on a breakpoint before jumping to the entry point of the program.
envp[0] = "_LOADER_BREAKPOINT=1";
int rc = execvpe(args[0], const_cast<char**>(args), const_cast<char**>(envp));
if (rc < 0) { if (rc < 0) {
perror("execvp"); perror("execvp");
} }
@ -107,7 +115,19 @@ OwnPtr<DebugSession> DebugSession::exec_and_attach(const String& command)
return nullptr; return nullptr;
} }
return adopt_own(*new DebugSession(pid)); auto debug_session = adopt_own(*new DebugSession(pid, source_root));
// Continue until breakpoint before entry point of main program
int wstatus = debug_session->continue_debuggee_and_wait();
if (WSTOPSIG(wstatus) != SIGTRAP) {
dbgln("expected SIGTRAP");
return nullptr;
}
// At this point, libraries should have been loaded
debug_session->update_loaded_libs();
return move(debug_session);
} }
bool DebugSession::poke(u32* address, u32 data) bool DebugSession::poke(u32* address, u32 data)
@ -268,4 +288,152 @@ void DebugSession::detach()
continue_debuggee(); continue_debuggee();
} }
Optional<DebugSession::InsertBreakpointAtSymbolResult> DebugSession::insert_breakpoint(const String& symbol_name)
{
Optional<InsertBreakpointAtSymbolResult> result;
for_each_loaded_library([this, symbol_name, &result](auto& lib) {
// The loader contains its own definitions for LibC symbols, so we don't want to include it in the search.
if (lib.name == "Loader.so")
return IterationDecision::Continue;
auto symbol = lib.debug_info->elf().find_demangled_function(symbol_name);
if (!symbol.has_value())
return IterationDecision::Continue;
auto breakpoint_address = symbol.value().value() + lib.base_address;
bool rc = this->insert_breakpoint(reinterpret_cast<void*>(breakpoint_address));
if (!rc)
return IterationDecision::Break;
result = InsertBreakpointAtSymbolResult { lib.name, breakpoint_address };
return IterationDecision::Break;
});
return result;
}
Optional<DebugSession::InsertBreakpointAtSourcePositionResult> DebugSession::insert_breakpoint(const String& file_name, size_t line_number)
{
auto address_and_source_position = get_address_from_source_position(file_name, line_number);
if (!address_and_source_position.has_value())
return {};
auto address = address_and_source_position.value().address;
bool rc = this->insert_breakpoint(reinterpret_cast<void*>(address));
if (!rc)
return {};
auto lib = library_at(address);
ASSERT(lib);
return InsertBreakpointAtSourcePositionResult { lib->name, address_and_source_position.value().file, address_and_source_position.value().line, address };
}
void DebugSession::update_loaded_libs()
{
auto file = Core::File::construct(String::format("/proc/%u/vm", m_debuggee_pid));
bool rc = file->open(Core::IODevice::ReadOnly);
ASSERT(rc);
auto file_contents = file->read_all();
auto json = JsonValue::from_string(file_contents);
ASSERT(json.has_value());
auto vm_entries = json.value().as_array();
Regex<PosixExtended> re("(.+): \\.text");
auto get_path_to_object = [&re](const String& vm_name) -> Optional<String> {
if (vm_name == "/usr/lib/Loader.so")
return vm_name;
RegexResult result;
auto rc = re.search(vm_name, result);
if (!rc)
return {};
auto lib_name = result.capture_group_matches.at(0).at(0).view.u8view().to_string();
if (lib_name.starts_with("/"))
return lib_name;
return String::format("/usr/lib/%s", lib_name.characters());
};
vm_entries.for_each([&](auto& entry) {
// TODO: check that region is executable
auto vm_name = entry.as_object().get("name").as_string();
auto object_path = get_path_to_object(vm_name);
if (!object_path.has_value())
return IterationDecision::Continue;
String lib_name = object_path.value();
if (lib_name.ends_with(".so"))
lib_name = LexicalPath(object_path.value()).basename();
// FIXME: DebugInfo currently cannot parse the debug information of libgcc_s.so
if (lib_name == "libgcc_s.so")
return IterationDecision::Continue;
if (m_loaded_libraries.contains(lib_name))
return IterationDecision::Continue;
MappedFile lib_file(object_path.value());
if (!lib_file.is_valid())
return IterationDecision::Continue;
FlatPtr base_address = entry.as_object().get("address").as_u32();
auto debug_info = make<DebugInfo>(make<ELF::Image>(reinterpret_cast<const u8*>(lib_file.data()), lib_file.size()), m_source_root, base_address);
auto lib = make<LoadedLibrary>(lib_name, move(lib_file), move(debug_info), base_address);
m_loaded_libraries.set(lib_name, move(lib));
return IterationDecision::Continue;
});
}
const DebugSession::LoadedLibrary* DebugSession::library_at(FlatPtr address) const
{
const LoadedLibrary* result = nullptr;
for_each_loaded_library([&result, address](const auto& lib) {
if (address >= lib.base_address && address < lib.base_address + lib.debug_info->elf().size()) {
result = &lib;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
return result;
}
Optional<DebugSession::SymbolicationResult> DebugSession::symbolicate(FlatPtr address) const
{
auto* lib = library_at(address);
if (!lib)
return {};
//FIXME: ELF::Image symlicate() API should return String::empty() if symbol is not found (It currently returns ??)
auto symbol = lib->debug_info->elf().symbolicate(address - lib->base_address);
return { { lib->name, symbol } };
}
Optional<DebugInfo::SourcePositionAndAddress> DebugSession::get_address_from_source_position(const String& file, size_t line) const
{
Optional<DebugInfo::SourcePositionAndAddress> result;
for_each_loaded_library([this, file, line, &result](auto& lib) {
// The loader contains its own definitions for LibC symbols, so we don't want to include it in the search.
if (lib.name == "Loader.so")
return IterationDecision::Continue;
auto source_position_and_address = lib.debug_info->get_address_from_source_position(file, line);
if (!source_position_and_address.has_value())
return IterationDecision::Continue;
result = source_position_and_address;
result.value().address += lib.base_address;
return IterationDecision::Break;
});
return result;
}
Optional<DebugInfo::SourcePosition> DebugSession::get_source_position(FlatPtr address) const
{
auto* lib = library_at(address);
if (!lib)
return {};
return lib->debug_info->get_source_position(address - lib->base_address);
}
} }

View file

@ -45,7 +45,7 @@ namespace Debug {
class DebugSession { class DebugSession {
public: public:
static OwnPtr<DebugSession> exec_and_attach(const String& command); static OwnPtr<DebugSession> exec_and_attach(const String& command, String source_root = {});
~DebugSession(); ~DebugSession();
@ -60,11 +60,27 @@ public:
}; };
struct BreakPoint { struct BreakPoint {
void* address; void* address { nullptr };
u32 original_first_word; u32 original_first_word { 0 };
BreakPointState state; BreakPointState state { BreakPointState::Disabled };
}; };
struct InsertBreakpointAtSymbolResult {
String library_name;
FlatPtr address { 0 };
};
Optional<InsertBreakpointAtSymbolResult> insert_breakpoint(const String& symbol_name);
struct InsertBreakpointAtSourcePositionResult {
String library_name;
String file_name;
size_t line_number { 0 };
FlatPtr address { 0 };
};
Optional<InsertBreakpointAtSourcePositionResult> insert_breakpoint(const String& file_name, size_t line_number);
bool insert_breakpoint(void* address); bool insert_breakpoint(void* address);
bool disable_breakpoint(void* address); bool disable_breakpoint(void* address);
bool enable_breakpoint(void* address); bool enable_breakpoint(void* address);
@ -95,12 +111,12 @@ public:
void detach(); void detach();
enum DesiredInitialDebugeeState {
Running,
Stopped
};
template<typename Callback> template<typename Callback>
void run(Callback callback); void run(DesiredInitialDebugeeState, Callback);
const ELF::Image& elf() const { return m_debug_info.elf(); }
const MappedFile& executable() const { return m_executable; }
const DebugInfo& debug_info() const { return m_debug_info; }
enum DebugDecision { enum DebugDecision {
Continue, Continue,
@ -116,38 +132,79 @@ public:
Exited, Exited,
}; };
struct LoadedLibrary {
String name;
MappedFile file;
NonnullOwnPtr<DebugInfo> debug_info;
FlatPtr base_address;
LoadedLibrary(const String& name, MappedFile&& file, NonnullOwnPtr<DebugInfo>&& debug_info, FlatPtr base_address)
: name(name)
, file(move(file))
, debug_info(move(debug_info))
, base_address(base_address)
{
}
};
template<typename Func>
void for_each_loaded_library(Func f) const
{
for (const auto& lib_name : m_loaded_libraries.keys()) {
const auto& lib = *m_loaded_libraries.get(lib_name).value();
if (f(lib) == IterationDecision::Break)
break;
}
}
const LoadedLibrary* library_at(FlatPtr address) const;
struct SymbolicationResult {
String library_name;
String symbol;
};
Optional<SymbolicationResult> symbolicate(FlatPtr address) const;
Optional<DebugInfo::SourcePositionAndAddress> get_address_from_source_position(const String& file, size_t line) const;
Optional<DebugInfo::SourcePosition> get_source_position(FlatPtr address) const;
private: private:
explicit DebugSession(pid_t); explicit DebugSession(pid_t, String source_root);
// x86 breakpoint instruction "int3" // x86 breakpoint instruction "int3"
static constexpr u8 BREAKPOINT_INSTRUCTION = 0xcc; static constexpr u8 BREAKPOINT_INSTRUCTION = 0xcc;
static MappedFile map_executable_for_process(pid_t); static MappedFile map_executable_for_process(pid_t);
void update_loaded_libs();
int m_debuggee_pid { -1 }; int m_debuggee_pid { -1 };
String m_source_root;
bool m_is_debuggee_dead { false }; bool m_is_debuggee_dead { false };
MappedFile m_executable;
DebugInfo m_debug_info;
HashMap<void*, BreakPoint> m_breakpoints; HashMap<void*, BreakPoint> m_breakpoints;
// Maps from base address to loaded library
HashMap<String, NonnullOwnPtr<LoadedLibrary>> m_loaded_libraries;
}; };
template<typename Callback> template<typename Callback>
void DebugSession::run(Callback callback) void DebugSession::run(DesiredInitialDebugeeState initial_debugee_state, Callback callback)
{ {
enum class State { enum class State {
FirstIteration,
FreeRun, FreeRun,
Syscall, Syscall,
ConsecutiveBreakpoint, ConsecutiveBreakpoint,
SingleStep, SingleStep,
}; };
State state { State::FreeRun }; State state { State::FirstIteration };
auto do_continue_and_wait = [&]() { auto do_continue_and_wait = [&]() {
int wstatus = continue_debuggee_and_wait((state == State::FreeRun) ? ContinueType::FreeRun : ContinueType::Syscall); int wstatus = continue_debuggee_and_wait((state == State::Syscall) ? ContinueType::Syscall : ContinueType::FreeRun);
// FIXME: This check actually only checks whether the debuggee // FIXME: This check actually only checks whether the debuggee
// stopped because it hit a breakpoint/syscall/is in single stepping mode or not // stopped because it hit a breakpoint/syscall/is in single stepping mode or not
@ -160,10 +217,12 @@ void DebugSession::run(Callback callback)
}; };
for (;;) { for (;;) {
if (state == State::FreeRun || state == State::Syscall) { if ((state == State::FirstIteration && initial_debugee_state == DesiredInitialDebugeeState::Running) || state == State::FreeRun || state == State::Syscall) {
if (do_continue_and_wait()) if (do_continue_and_wait())
break; break;
} }
if (state == State::FirstIteration)
state = State::FreeRun;
auto regs = get_registers(); auto regs = get_registers();
Optional<BreakPoint> current_breakpoint; Optional<BreakPoint> current_breakpoint;

View file

@ -84,25 +84,27 @@ static void print_syscall(PtraceRegisters& regs, size_t depth)
static NonnullOwnPtr<HashMap<void*, X86::Instruction>> instrument_code() static NonnullOwnPtr<HashMap<void*, X86::Instruction>> instrument_code()
{ {
[[maybe_unused]] auto r = demangle("foo"); // Required for linked with __cxa_demangle
auto instrumented = make<HashMap<void*, X86::Instruction>>(); auto instrumented = make<HashMap<void*, X86::Instruction>>();
g_debug_session->elf().for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) { g_debug_session->for_each_loaded_library([&](const Debug::DebugSession::LoadedLibrary& lib) {
if (section.name() != ".text") lib.debug_info->elf().for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) {
return IterationDecision::Continue; if (section.name() != ".text")
return IterationDecision::Continue;
X86::SimpleInstructionStream stream((const u8*)((u32)g_debug_session->executable().data() + section.offset()), section.size()); X86::SimpleInstructionStream stream((const u8*)((u32)lib.file.data() + section.offset()), section.size());
X86::Disassembler disassembler(stream); X86::Disassembler disassembler(stream);
for (;;) { for (;;) {
auto offset = stream.offset(); auto offset = stream.offset();
void* instruction_address = (void*)(section.address() + offset); void* instruction_address = (void*)(section.address() + offset + lib.base_address);
auto insn = disassembler.next(); auto insn = disassembler.next();
if (!insn.has_value()) if (!insn.has_value())
break; break;
if (insn.value().mnemonic() == "RET" || insn.value().mnemonic() == "CALL") { if (insn.value().mnemonic() == "RET" || insn.value().mnemonic() == "CALL") {
g_debug_session->insert_breakpoint(instruction_address); g_debug_session->insert_breakpoint(instruction_address);
instrumented->set(instruction_address, insn.value()); instrumented->set(instruction_address, insn.value());
}
} }
} return IterationDecision::Continue;
});
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
return instrumented; return instrumented;
@ -142,7 +144,7 @@ int main(int argc, char** argv)
size_t depth = 0; size_t depth = 0;
bool new_function = true; bool new_function = true;
g_debug_session->run([&](Debug::DebugSession::DebugBreakReason reason, Optional<PtraceRegisters> regs) { g_debug_session->run(Debug::DebugSession::DesiredInitialDebugeeState::Running, [&](Debug::DebugSession::DebugBreakReason reason, Optional<PtraceRegisters> regs) {
if (reason == Debug::DebugSession::DebugBreakReason::Exited) { if (reason == Debug::DebugSession::DebugBreakReason::Exited) {
outln("Program exited."); outln("Program exited.");
return Debug::DebugSession::DebugDecision::Detach; return Debug::DebugSession::DebugDecision::Detach;
@ -154,8 +156,8 @@ int main(int argc, char** argv)
} }
if (new_function) { if (new_function) {
auto function_name = g_debug_session->elf().symbolicate(regs.value().eip); auto function_name = g_debug_session->symbolicate(regs.value().eip);
print_function_call(function_name, depth); print_function_call(function_name.value().symbol, depth);
new_function = false; new_function = false;
return Debug::DebugSession::ContinueBreakAtSyscall; return Debug::DebugSession::ContinueBreakAtSyscall;
} }