diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h @@ -183,6 +183,9 @@ /// Location of the r_debug structure in the inferiors address space. lldb::addr_t m_rendezvous_addr; + /// It is set to true when executable is run with argv[0] as ld.so. + bool m_executable_interpreter; + /// Current and previous snapshots of the rendezvous structure. Rendezvous m_current; Rendezvous m_previous; @@ -246,6 +249,8 @@ 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/DYLDRendezvous.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp @@ -25,8 +25,10 @@ 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) { +/// success and LLDB_INVALID_ADDRESS on failure. It updates +/// m_executable_interpreter if address is extracted from _r_debug. +static addr_t ResolveRendezvousAddress(Process *process, + bool &m_executable_interpreter) { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER)); addr_t info_location; addr_t info_addr; @@ -56,6 +58,20 @@ "%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", @@ -91,8 +107,9 @@ } 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_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(); @@ -132,7 +149,8 @@ __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(m_process, m_executable_interpreter); else cursor = info_addr = m_rendezvous_addr; LLDB_LOGF(log, "DYLDRendezvous::%s cursor = 0x%" PRIx64, __FUNCTION__, @@ -296,8 +314,11 @@ return false; // Only add shared libraries and not the executable. - if (!SOEntryIsMainExecutable(entry)) + if (!SOEntryIsMainExecutable(entry)) { + if (entry.file_spec.GetFilename().IsEmpty()) + UpdateFileSpecIfNecessary(entry); m_soentries.push_back(entry); + } } m_loaded_modules = module_list; @@ -324,6 +345,8 @@ // Only add shared libraries and not the executable. if (!SOEntryIsMainExecutable(entry)) { + if (entry.file_spec.GetFilename().IsEmpty()) + UpdateFileSpecIfNecessary(entry); m_soentries.push_back(entry); m_added_soentries.push_back(entry); } @@ -383,6 +406,9 @@ if (SOEntryIsMainExecutable(entry)) continue; + if (entry.file_spec.GetFilename().IsEmpty()) + UpdateFileSpecIfNecessary(entry); + pos = std::find(m_soentries.begin(), m_soentries.end(), entry); if (pos == m_soentries.end()) { m_soentries.push_back(entry); @@ -424,6 +450,11 @@ case llvm::Triple::Linux: if (triple.isAndroid()) return entry.file_spec == m_exe_file_spec; + // When executable is run with argv[0] as ld.so, ld.so corresponds to + // m_exe_file_spec. In this case, the returned link map + // has empty entry for executable, which is not the main executable. + if (!entry.file_spec && m_executable_interpreter) + return false; return !entry.file_spec; default: return false; @@ -447,6 +478,9 @@ if (SOEntryIsMainExecutable(entry)) continue; + if (entry.file_spec.GetFilename().IsEmpty()) + UpdateFileSpecIfNecessary(entry); + entry_list.push_back(entry); } @@ -512,6 +546,18 @@ } } +void DYLDRendezvous::UpdateFileSpecIfNecessary(SOEntry &entry) { + // ld.so saves empty file name for the executable file in the link map. + // When argv[0] is ld.so, we need to be update executable path. + if (entry.file_spec.GetFilename().IsEmpty()) { + MemoryRegionInfo region; + Status region_status = + m_process->GetMemoryRegionInfo(entry.dyn_addr, region); + std::string file_path = region.GetName().AsCString(); + entry.file_spec.SetFile(file_path, FileSpec::Style::native); + } +} + bool DYLDRendezvous::ReadSOEntryFromMemory(lldb::addr_t addr, SOEntry &entry) { entry.clear(); diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp @@ -333,28 +333,37 @@ 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 --- /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 --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/TestDyldLaunchLinux.py @@ -0,0 +1,52 @@ +""" +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() + candidates = [ + "/lib64/ld-linux-x86-64.so.2", + "/usr/lib/ld-linux-x86-64.so.2" + ] + exe = next((c for c in candidates if os.path.exists(c)), None) + if not os.path.exists(exe): + 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 --- /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.h b/lldb/test/API/functionalities/dyld-launch-linux/signal_file.h new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/dyld-launch-linux/signal_file.h @@ -0,0 +1 @@ +int 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 --- /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; +}