From 03df97101287e8cb647c6c0982c4efdb82585c21 Mon Sep 17 00:00:00 2001 From: Rumeet Dhindsa Date: Fri, 10 Sep 2021 10:59:31 -0700 Subject: [PATCH] [lldb] Add support for debugging via the dynamic linker. This patch adds support for shared library load when the executable is called through ld.so. Differential Revision:https://reviews.llvm.org/D108061 --- .../POSIX-DYLD/DYLDRendezvous.cpp | 74 ++++++++++++++----- .../DynamicLoader/POSIX-DYLD/DYLDRendezvous.h | 11 +++ .../POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp | 43 ++++++----- .../dyld-launch-linux/Makefile | 4 + .../dyld-launch-linux/TestDyldLaunchLinux.py | 58 +++++++++++++++ .../dyld-launch-linux/main.cpp | 6 ++ .../dyld-launch-linux/signal_file.cpp | 7 ++ .../dyld-launch-linux/signal_file.h | 1 + 8 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 lldb/test/API/functionalities/dyld-launch-linux/Makefile create mode 100644 lldb/test/API/functionalities/dyld-launch-linux/TestDyldLaunchLinux.py create mode 100644 lldb/test/API/functionalities/dyld-launch-linux/main.cpp create mode 100644 lldb/test/API/functionalities/dyld-launch-linux/signal_file.cpp create mode 100644 lldb/test/API/functionalities/dyld-launch-linux/signal_file.h diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp index 866acbddbdc8a..7e80dc28e56b2 100644 --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp @@ -24,28 +24,35 @@ using namespace lldb; using namespace lldb_private; -/// Locates the address of the rendezvous structure. Returns the address on -/// success and LLDB_INVALID_ADDRESS on failure. -static addr_t ResolveRendezvousAddress(Process *process) { +DYLDRendezvous::DYLDRendezvous(Process *process) + : m_process(process), m_rendezvous_addr(LLDB_INVALID_ADDRESS), + m_executable_interpreter(false), m_current(), m_previous(), + m_loaded_modules(), m_soentries(), m_added_soentries(), + m_removed_soentries() { + m_thread_info.valid = false; + UpdateExecutablePath(); +} + +addr_t DYLDRendezvous::ResolveRendezvousAddress() { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER)); addr_t info_location; addr_t info_addr; Status error; - if (!process) { + if (!m_process) { LLDB_LOGF(log, "%s null process provided", __FUNCTION__); return LLDB_INVALID_ADDRESS; } // Try to get it from our process. This might be a remote process and might // grab it via some remote-specific mechanism. - info_location = process->GetImageInfoAddress(); + info_location = m_process->GetImageInfoAddress(); LLDB_LOGF(log, "%s info_location = 0x%" PRIx64, __FUNCTION__, info_location); // If the process fails to return an address, fall back to seeing if the // local object file can help us find it. if (info_location == LLDB_INVALID_ADDRESS) { - Target *target = &process->GetTarget(); + Target *target = &m_process->GetTarget(); if (target) { ObjectFile *obj_file = target->GetExecutableModule()->GetObjectFile(); Address addr = obj_file->GetImageInfoAddress(target); @@ -56,6 +63,20 @@ static addr_t ResolveRendezvousAddress(Process *process) { "%s resolved via direct object file approach to 0x%" PRIx64, __FUNCTION__, info_location); } else { + const Symbol *_r_debug = + target->GetExecutableModule()->FindFirstSymbolWithNameAndType( + ConstString("_r_debug")); + if (_r_debug) { + info_addr = _r_debug->GetAddress().GetLoadAddress(target); + if (info_addr != LLDB_INVALID_ADDRESS) { + LLDB_LOGF(log, + "%s resolved by finding symbol '_r_debug' whose value is " + "0x%" PRIx64, + __FUNCTION__, info_addr); + m_executable_interpreter = true; + return info_addr; + } + } LLDB_LOGF(log, "%s FAILED - direct object file approach did not yield a " "valid address", @@ -70,9 +91,9 @@ static addr_t ResolveRendezvousAddress(Process *process) { } LLDB_LOGF(log, "%s reading pointer (%" PRIu32 " bytes) from 0x%" PRIx64, - __FUNCTION__, process->GetAddressByteSize(), info_location); + __FUNCTION__, m_process->GetAddressByteSize(), info_location); - info_addr = process->ReadPointerFromMemory(info_location, error); + info_addr = m_process->ReadPointerFromMemory(info_location, error); if (error.Fail()) { LLDB_LOGF(log, "%s FAILED - could not read from the info location: %s", __FUNCTION__, error.AsCString()); @@ -90,14 +111,6 @@ static addr_t ResolveRendezvousAddress(Process *process) { return info_addr; } -DYLDRendezvous::DYLDRendezvous(Process *process) - : m_process(process), m_rendezvous_addr(LLDB_INVALID_ADDRESS), m_current(), - m_previous(), m_loaded_modules(), m_soentries(), m_added_soentries(), - m_removed_soentries() { - m_thread_info.valid = false; - UpdateExecutablePath(); -} - void DYLDRendezvous::UpdateExecutablePath() { if (m_process) { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER)); @@ -132,7 +145,8 @@ bool DYLDRendezvous::Resolve() { __FUNCTION__, uint64_t(address_size), uint64_t(padding)); if (m_rendezvous_addr == LLDB_INVALID_ADDRESS) - cursor = info_addr = ResolveRendezvousAddress(m_process); + cursor = info_addr = + ResolveRendezvousAddress(); else cursor = info_addr = m_rendezvous_addr; LLDB_LOGF(log, "DYLDRendezvous::%s cursor = 0x%" PRIx64, __FUNCTION__, @@ -296,8 +310,10 @@ bool DYLDRendezvous::SaveSOEntriesFromRemote( return false; // Only add shared libraries and not the executable. - if (!SOEntryIsMainExecutable(entry)) + if (!SOEntryIsMainExecutable(entry)) { + UpdateFileSpecIfNecessary(entry); m_soentries.push_back(entry); + } } m_loaded_modules = module_list; @@ -324,6 +340,7 @@ bool DYLDRendezvous::AddSOEntriesFromRemote( // Only add shared libraries and not the executable. if (!SOEntryIsMainExecutable(entry)) { + UpdateFileSpecIfNecessary(entry); m_soentries.push_back(entry); m_added_soentries.push_back(entry); } @@ -383,6 +400,8 @@ bool DYLDRendezvous::AddSOEntries() { if (SOEntryIsMainExecutable(entry)) continue; + UpdateFileSpecIfNecessary(entry); + pos = std::find(m_soentries.begin(), m_soentries.end(), entry); if (pos == m_soentries.end()) { m_soentries.push_back(entry); @@ -424,6 +443,10 @@ bool DYLDRendezvous::SOEntryIsMainExecutable(const SOEntry &entry) { case llvm::Triple::Linux: if (triple.isAndroid()) return entry.file_spec == m_exe_file_spec; + // If we are debugging ld.so, then all SOEntries should be treated as + // libraries, including the "main" one (denoted by an empty string). + if (!entry.file_spec && m_executable_interpreter) + return false; return !entry.file_spec; default: return false; @@ -447,6 +470,8 @@ bool DYLDRendezvous::TakeSnapshot(SOEntryList &entry_list) { if (SOEntryIsMainExecutable(entry)) continue; + UpdateFileSpecIfNecessary(entry); + entry_list.push_back(entry); } @@ -512,6 +537,19 @@ void DYLDRendezvous::UpdateBaseAddrIfNecessary(SOEntry &entry, } } +void DYLDRendezvous::UpdateFileSpecIfNecessary(SOEntry &entry) { + // Updates filename if empty. It is useful while debugging ld.so, + // when the link map returns empty string for the main executable. + if (!entry.file_spec) { + MemoryRegionInfo region; + Status region_status = + m_process->GetMemoryRegionInfo(entry.dyn_addr, region); + if (!region.GetName().IsEmpty()) + entry.file_spec.SetFile(region.GetName().AsCString(), + FileSpec::Style::native); + } +} + bool DYLDRendezvous::ReadSOEntryFromMemory(lldb::addr_t addr, SOEntry &entry) { entry.clear(); diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h index 5775f5a730cd7..04d3e665f8598 100644 --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h @@ -47,6 +47,12 @@ class DYLDRendezvous { Rendezvous() = default; }; + /// Locates the address of the rendezvous structure. It updates + /// m_executable_interpreter if address is extracted from _r_debug. + /// + /// \returns address on success and LLDB_INVALID_ADDRESS on failure. + lldb::addr_t ResolveRendezvousAddress(); + public: // Various metadata supplied by the inferior's threading library to describe // the per-thread state. @@ -183,6 +189,9 @@ class DYLDRendezvous { /// Location of the r_debug structure in the inferiors address space. lldb::addr_t m_rendezvous_addr; + // True if the main program is the dynamic linker/loader/program interpreter. + bool m_executable_interpreter; + /// Current and previous snapshots of the rendezvous structure. Rendezvous m_current; Rendezvous m_previous; @@ -246,6 +255,8 @@ class DYLDRendezvous { void UpdateBaseAddrIfNecessary(SOEntry &entry, std::string const &file_path); + void UpdateFileSpecIfNecessary(SOEntry &entry); + bool SOEntryIsMainExecutable(const SOEntry &entry); /// Reads the current list of shared objects according to the link map diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp index 160faa74af239..75b9d69bc7e03 100644 --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp @@ -333,28 +333,37 @@ bool DynamicLoaderPOSIXDYLD::SetRendezvousBreakpoint() { LLDB_LOG(log, "Rendezvous structure is not set up yet. " "Trying to locate rendezvous breakpoint in the interpreter " "by symbol name."); - ModuleSP interpreter = LoadInterpreterModule(); - if (!interpreter) { - LLDB_LOG(log, "Can't find interpreter, rendezvous breakpoint isn't set."); - return false; - } - - // Function names from different dynamic loaders that are known to be used - // as rendezvous between the loader and debuggers. + // Function names from different dynamic loaders that are known to be + // used as rendezvous between the loader and debuggers. static std::vector DebugStateCandidates{ "_dl_debug_state", "rtld_db_dlactivity", "__dl_rtld_db_dlactivity", "r_debug_state", "_r_debug_state", "_rtld_debug_state", }; - FileSpecList containingModules; - containingModules.Append(interpreter->GetFileSpec()); - dyld_break = target.CreateBreakpoint( - &containingModules, nullptr /* containingSourceFiles */, - DebugStateCandidates, eFunctionNameTypeFull, eLanguageTypeC, - 0, /* offset */ - eLazyBoolNo, /* skip_prologue */ - true, /* internal */ - false /* request_hardware */); + ModuleSP interpreter = LoadInterpreterModule(); + if (!interpreter) { + FileSpecList containingModules; + containingModules.Append( + m_process->GetTarget().GetExecutableModulePointer()->GetFileSpec()); + + dyld_break = target.CreateBreakpoint( + &containingModules, /*containingSourceFiles=*/nullptr, + DebugStateCandidates, eFunctionNameTypeFull, eLanguageTypeC, + /*offset=*/0, + /*skip_prologue=*/eLazyBoolNo, + /*internal=*/true, + /*request_hardware=*/false); + } else { + FileSpecList containingModules; + containingModules.Append(interpreter->GetFileSpec()); + dyld_break = target.CreateBreakpoint( + &containingModules, /*containingSourceFiles=*/nullptr, + DebugStateCandidates, eFunctionNameTypeFull, eLanguageTypeC, + /*offset=*/0, + /*skip_prologue=*/eLazyBoolNo, + /*internal=*/true, + /*request_hardware=*/false); + } } if (dyld_break->GetNumResolvedLocations() != 1) { diff --git a/lldb/test/API/functionalities/dyld-launch-linux/Makefile b/lldb/test/API/functionalities/dyld-launch-linux/Makefile new file mode 100644 index 0000000000000..2621dc44502bf --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/Makefile @@ -0,0 +1,4 @@ +CXX_SOURCES := main.cpp +DYLIB_NAME := signal_file +DYLIB_CXX_SOURCES := signal_file.cpp +include Makefile.rules diff --git a/lldb/test/API/functionalities/dyld-launch-linux/TestDyldLaunchLinux.py b/lldb/test/API/functionalities/dyld-launch-linux/TestDyldLaunchLinux.py new file mode 100644 index 0000000000000..bf475890035ab --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/TestDyldLaunchLinux.py @@ -0,0 +1,58 @@ +""" +Test that LLDB can launch a linux executable through the dynamic loader and still hit a breakpoint. +""" + +import lldb +import os + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + +class TestLinux64LaunchingViaDynamicLoader(TestBase): + mydir = TestBase.compute_mydir(__file__) + + @skipIf(oslist=no_match(['linux'])) + @no_debug_info_test + def test(self): + self.build() + + # Extracts path of the interpreter. + spec = lldb.SBModuleSpec() + spec.SetFileSpec(lldb.SBFileSpec(self.getBuildArtifact("a.out"))) + interp_section = lldb.SBModule(spec).FindSection(".interp") + if not interp_section: + return + section_data = interp_section.GetSectionData() + error = lldb.SBError() + exe = section_data.GetString(error,0) + if error.Fail(): + return + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + # Set breakpoints both on shared library function as well as on + # main. Both of them will be pending breakpoints. + breakpoint_main = target.BreakpointCreateBySourceRegex("// Break here", lldb.SBFileSpec("main.cpp")) + breakpoint_shared_library = target.BreakpointCreateBySourceRegex("get_signal_crash", lldb.SBFileSpec("signal_file.cpp")) + launch_info = lldb.SBLaunchInfo([ "--library-path", self.get_process_working_directory(), self.getBuildArtifact("a.out")]) + launch_info.SetWorkingDirectory(self.get_process_working_directory()) + error = lldb.SBError() + process = target.Launch(launch_info, error) + self.assertTrue(error.Success()) + + # Stopped on main here. + self.assertEqual(process.GetState(), lldb.eStateStopped) + thread = process.GetSelectedThread() + self.assertIn("main", thread.GetFrameAtIndex(0).GetDisplayFunctionName()) + process.Continue() + + # Stopped on get_signal_crash function here. + self.assertEqual(process.GetState(), lldb.eStateStopped) + self.assertIn("get_signal_crash", thread.GetFrameAtIndex(0).GetDisplayFunctionName()) + process.Continue() + + # Stopped because of generated signal. + self.assertEqual(process.GetState(), lldb.eStateStopped) + self.assertIn("raise", thread.GetFrameAtIndex(0).GetDisplayFunctionName()) + self.assertIn("get_signal_crash", thread.GetFrameAtIndex(1).GetDisplayFunctionName()) diff --git a/lldb/test/API/functionalities/dyld-launch-linux/main.cpp b/lldb/test/API/functionalities/dyld-launch-linux/main.cpp new file mode 100644 index 0000000000000..0ff05466c58ca --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/main.cpp @@ -0,0 +1,6 @@ +#include "signal_file.h" + +int main() { + // Break here + return get_signal_crash(); +} diff --git a/lldb/test/API/functionalities/dyld-launch-linux/signal_file.cpp b/lldb/test/API/functionalities/dyld-launch-linux/signal_file.cpp new file mode 100644 index 0000000000000..3c904a34f4210 --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/signal_file.cpp @@ -0,0 +1,7 @@ +#include "signal_file.h" +#include + +int get_signal_crash(void) { + raise(SIGSEGV); + return 0; +} diff --git a/lldb/test/API/functionalities/dyld-launch-linux/signal_file.h b/lldb/test/API/functionalities/dyld-launch-linux/signal_file.h new file mode 100644 index 0000000000000..a93d9125d5c62 --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/signal_file.h @@ -0,0 +1 @@ +int get_signal_crash();