Skip to content

Commit

Permalink
[lldb] Add support for debugging via the dynamic linker.
Browse files Browse the repository at this point in the history
This patch adds support for shared library load when the executable is
called through ld.so.

Differential Revision:https://reviews.llvm.org/D108061
  • Loading branch information
rdhindsa14 committed Sep 10, 2021
1 parent f3c2094 commit 03df971
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 35 deletions.
74 changes: 56 additions & 18 deletions lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.cpp
Expand Up @@ -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);
Expand All @@ -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",
Expand All @@ -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());
Expand All @@ -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));
Expand Down Expand Up @@ -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__,
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -447,6 +470,8 @@ bool DYLDRendezvous::TakeSnapshot(SOEntryList &entry_list) {
if (SOEntryIsMainExecutable(entry))
continue;

UpdateFileSpecIfNecessary(entry);

entry_list.push_back(entry);
}

Expand Down Expand Up @@ -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();

Expand Down
11 changes: 11 additions & 0 deletions lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DYLDRendezvous.h
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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<std::string> 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) {
Expand Down
4 changes: 4 additions & 0 deletions 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
@@ -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())
6 changes: 6 additions & 0 deletions 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();
}
@@ -0,0 +1,7 @@
#include "signal_file.h"
#include <signal.h>

int get_signal_crash(void) {
raise(SIGSEGV);
return 0;
}
@@ -0,0 +1 @@
int get_signal_crash();

0 comments on commit 03df971

Please sign in to comment.