diff --git a/Applications/Debugger/Makefile b/Applications/Debugger/Makefile index 32f18aa799..1b7141ec62 100755 --- a/Applications/Debugger/Makefile +++ b/Applications/Debugger/Makefile @@ -1,9 +1,8 @@ OBJS = \ - DebugSession.o \ main.o PROGRAM = Debugger -LIB_DEPS = Core X86 +LIB_DEPS = Core X86 Debug include ../../Makefile.common diff --git a/Applications/Debugger/main.cpp b/Applications/Debugger/main.cpp index 97740181ee..afe40166af 100644 --- a/Applications/Debugger/main.cpp +++ b/Applications/Debugger/main.cpp @@ -24,7 +24,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "DebugSession.h" #include #include #include @@ -33,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/Applications/Debugger/DebugSession.cpp b/Libraries/LibDebug/DebugSession.cpp similarity index 100% rename from Applications/Debugger/DebugSession.cpp rename to Libraries/LibDebug/DebugSession.cpp diff --git a/Applications/Debugger/DebugSession.h b/Libraries/LibDebug/DebugSession.h similarity index 98% rename from Applications/Debugger/DebugSession.h rename to Libraries/LibDebug/DebugSession.h index a1180464c1..e3138c1dcb 100644 --- a/Applications/Debugger/DebugSession.h +++ b/Libraries/LibDebug/DebugSession.h @@ -78,6 +78,7 @@ public: void run(Callback callback); const ELF::Loader& elf() const { return m_elf; } + const MappedFile& executable() const { return m_executable; } enum DebugDecision { Continue, diff --git a/Libraries/LibDebug/Makefile b/Libraries/LibDebug/Makefile new file mode 100644 index 0000000000..a3382e42ce --- /dev/null +++ b/Libraries/LibDebug/Makefile @@ -0,0 +1,11 @@ +OBJS = \ + DebugSession.o + +LIBRARY = libdebug.a + +install: + mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/LibDebug/ + cp *.h $(SERENITY_BASE_DIR)/Root/usr/include/LibDebug/ + cp $(LIBRARY) $(SERENITY_BASE_DIR)/Root/usr/lib/ + +include ../../Makefile.common diff --git a/Libraries/LibELF/Loader.h b/Libraries/LibELF/Loader.h index e7539be0b0..f9ea2877a4 100644 --- a/Libraries/LibELF/Loader.h +++ b/Libraries/LibELF/Loader.h @@ -57,6 +57,7 @@ public: { return m_image.entry(); } + const Image& image() const { return m_image; } char* symbol_ptr(const char* name) const; Optional find_demangled_function(const String& name) const; diff --git a/Userland/Makefile b/Userland/Makefile index c90523a3ea..0a0da33535 100644 --- a/Userland/Makefile +++ b/Userland/Makefile @@ -4,7 +4,7 @@ APPS = ${SRCS:.cpp=} EXTRA_CLEAN = $(APPS) -LIB_DEPS = Web GUI Gfx Audio Protocol IPC Thread Pthread PCIDB Markdown JS Core Line X86 +LIB_DEPS = Web GUI Gfx Audio Protocol IPC Thread Pthread PCIDB Markdown JS Core Line X86 Debug include ../Makefile.common diff --git a/Userland/functrace.cpp b/Userland/functrace.cpp new file mode 100644 index 0000000000..cdb37dbc56 --- /dev/null +++ b/Userland/functrace.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020, Itamar S. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int usage() +{ + printf("usage: functrace [command...]\n"); + return 1; +} + +OwnPtr g_debug_session; + +static void handle_sigint(int) +{ + printf("Debugger: SIGINT\n"); + + // The destructor of DebugSession takes care of detaching + g_debug_session = nullptr; +} + +void print_function_call(String function_name, size_t depth) +{ + for (size_t i = 0; i < depth; ++i) { + printf(" "); + } + printf("=> %s\n", function_name.characters()); +} + +NonnullOwnPtr> instrument_code() +{ + (void)demangle("foo"); // Required for linked with __cxa_demangle + auto instrumented = make>(); + g_debug_session->elf().image().for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) { + if (section.name() != ".text") + return IterationDecision::Continue; + + X86::SimpleInstructionStream stream((const u8*)((u32)g_debug_session->executable().data() + section.offset()), section.size()); + X86::Disassembler disassembler(stream); + for (;;) { + auto offset = stream.offset(); + void* instruction_address = (void*)(section.address() + offset); + auto insn = disassembler.next(); + if (!insn.has_value()) + break; + if (insn.value().mnemonic() == "RET" || insn.value().mnemonic() == "CALL") { + g_debug_session->insert_breakpoint(instruction_address); + instrumented->set(instruction_address, insn.value()); + } + } + return IterationDecision::Continue; + }); + return instrumented; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio proc exec rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (argc == 1) + return usage(); + + StringBuilder command; + command.append(argv[1]); + for (int i = 2; i < argc; ++i) { + command.appendf("%s ", argv[i]); + } + + auto result = DebugSession::exec_and_attach(command.to_string()); + if (!result) { + fprintf(stderr, "Failed to start debugging session for: \"%s\"\n", command.to_string().characters()); + exit(1); + } + g_debug_session = result.release_nonnull(); + + auto instrumented = instrument_code(); + + struct sigaction sa; + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = handle_sigint; + sigaction(SIGINT, &sa, nullptr); + + size_t depth = 0; + bool new_function = true; + + g_debug_session->run([&](DebugSession::DebugBreakReason reason, Optional regs) { + if (reason == DebugSession::DebugBreakReason::Exited) { + printf("Program exited.\n"); + return DebugSession::DebugDecision::Detach; + } + + if (new_function) { + auto function_name = g_debug_session->elf().symbolicate(regs.value().eip); + print_function_call(function_name, depth); + new_function = false; + return DebugSession::Continue; + } + auto instruction = instrumented->get((void*)regs.value().eip).value(); + + if (instruction.mnemonic() == "RET") { + if (depth != 0) + --depth; + return DebugSession::Continue; + } + + // FIXME: we could miss some leaf functions that were called with a jump + ASSERT(instruction.mnemonic() == "CALL"); + + ++depth; + new_function = true; + + return DebugSession::DebugDecision::SingleStep; + }); +}