blob: 4869cf0fd9c8165a777c08a72dc8598694687880 [file] [log] [blame]
//===-- DYLDRendezvous.cpp ------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Core/Module.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Status.h"
#include "llvm/Support/Path.h"
#include "DYLDRendezvous.h"
using namespace lldb;
using namespace lldb_private;
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 = GetLog(LLDBLog::DynamicLoader);
addr_t info_location;
addr_t info_addr;
Status error;
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 = 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 = &m_process->GetTarget();
if (target) {
ObjectFile *obj_file = target->GetExecutableModule()->GetObjectFile();
Address addr = obj_file->GetImageInfoAddress(target);
if (addr.IsValid()) {
info_location = addr.GetLoadAddress(target);
LLDB_LOGF(log,
"%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",
__FUNCTION__);
}
}
}
if (info_location == LLDB_INVALID_ADDRESS) {
LLDB_LOGF(log, "%s FAILED - invalid info address", __FUNCTION__);
return LLDB_INVALID_ADDRESS;
}
LLDB_LOGF(log, "%s reading pointer (%" PRIu32 " bytes) from 0x%" PRIx64,
__FUNCTION__, m_process->GetAddressByteSize(), info_location);
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());
return LLDB_INVALID_ADDRESS;
}
if (info_addr == 0) {
LLDB_LOGF(log,
"%s FAILED - the rendezvous address contained at 0x%" PRIx64
" returned a null value",
__FUNCTION__, info_location);
return LLDB_INVALID_ADDRESS;
}
return info_addr;
}
void DYLDRendezvous::UpdateExecutablePath() {
if (m_process) {
Log *log = GetLog(LLDBLog::DynamicLoader);
Module *exe_mod = m_process->GetTarget().GetExecutableModulePointer();
if (exe_mod) {
m_exe_file_spec = exe_mod->GetPlatformFileSpec();
LLDB_LOGF(log, "DYLDRendezvous::%s exe module executable path set: '%s'",
__FUNCTION__, m_exe_file_spec.GetCString());
} else {
LLDB_LOGF(log,
"DYLDRendezvous::%s cannot cache exe module path: null "
"executable module pointer",
__FUNCTION__);
}
}
}
bool DYLDRendezvous::Resolve() {
Log *log = GetLog(LLDBLog::DynamicLoader);
const size_t word_size = 4;
Rendezvous info;
size_t address_size;
size_t padding;
addr_t info_addr;
addr_t cursor;
address_size = m_process->GetAddressByteSize();
padding = address_size - word_size;
LLDB_LOGF(log,
"DYLDRendezvous::%s address size: %" PRIu64 ", padding %" PRIu64,
__FUNCTION__, uint64_t(address_size), uint64_t(padding));
if (m_rendezvous_addr == LLDB_INVALID_ADDRESS)
cursor = info_addr =
ResolveRendezvousAddress();
else
cursor = info_addr = m_rendezvous_addr;
LLDB_LOGF(log, "DYLDRendezvous::%s cursor = 0x%" PRIx64, __FUNCTION__,
cursor);
if (cursor == LLDB_INVALID_ADDRESS)
return false;
if (!(cursor = ReadWord(cursor, &info.version, word_size)))
return false;
if (!(cursor = ReadPointer(cursor + padding, &info.map_addr)))
return false;
if (!(cursor = ReadPointer(cursor, &info.brk)))
return false;
if (!(cursor = ReadWord(cursor, &info.state, word_size)))
return false;
if (!(cursor = ReadPointer(cursor + padding, &info.ldbase)))
return false;
// The rendezvous was successfully read. Update our internal state.
m_rendezvous_addr = info_addr;
m_previous = m_current;
m_current = info;
if (m_current.map_addr == 0)
return false;
if (UpdateSOEntriesFromRemote())
return true;
return UpdateSOEntries();
}
bool DYLDRendezvous::IsValid() {
return m_rendezvous_addr != LLDB_INVALID_ADDRESS;
}
DYLDRendezvous::RendezvousAction DYLDRendezvous::GetAction() const {
switch (m_current.state) {
case eConsistent:
switch (m_previous.state) {
// When the previous and current states are consistent this is the first
// time we have been asked to update. Just take a snapshot of the
// currently loaded modules.
case eConsistent:
return eTakeSnapshot;
// If we are about to add or remove a shared object clear out the current
// state and take a snapshot of the currently loaded images.
case eAdd:
return eAddModules;
case eDelete:
return eRemoveModules;
}
break;
case eAdd:
case eDelete:
return eNoAction;
}
return eNoAction;
}
bool DYLDRendezvous::UpdateSOEntriesFromRemote() {
auto action = GetAction();
if (action == eNoAction)
return false;
m_added_soentries.clear();
m_removed_soentries.clear();
if (action == eTakeSnapshot) {
// We already have the loaded list from the previous update so no need to
// find all the modules again.
if (!m_loaded_modules.m_list.empty())
return true;
}
llvm::Expected<LoadedModuleInfoList> module_list =
m_process->GetLoadedModuleList();
if (!module_list) {
llvm::consumeError(module_list.takeError());
return false;
}
switch (action) {
case eTakeSnapshot:
m_soentries.clear();
return SaveSOEntriesFromRemote(*module_list);
case eAddModules:
return AddSOEntriesFromRemote(*module_list);
case eRemoveModules:
return RemoveSOEntriesFromRemote(*module_list);
case eNoAction:
return false;
}
llvm_unreachable("Fully covered switch above!");
}
bool DYLDRendezvous::UpdateSOEntries() {
m_added_soentries.clear();
m_removed_soentries.clear();
switch (GetAction()) {
case eTakeSnapshot:
m_soentries.clear();
return TakeSnapshot(m_soentries);
case eAddModules:
return AddSOEntries();
case eRemoveModules:
return RemoveSOEntries();
case eNoAction:
return false;
}
llvm_unreachable("Fully covered switch above!");
}
bool DYLDRendezvous::FillSOEntryFromModuleInfo(
LoadedModuleInfoList::LoadedModuleInfo const &modInfo, SOEntry &entry) {
addr_t link_map_addr;
addr_t base_addr;
addr_t dyn_addr;
std::string name;
if (!modInfo.get_link_map(link_map_addr) || !modInfo.get_base(base_addr) ||
!modInfo.get_dynamic(dyn_addr) || !modInfo.get_name(name))
return false;
entry.link_addr = link_map_addr;
entry.base_addr = base_addr;
entry.dyn_addr = dyn_addr;
entry.file_spec.SetFile(name, FileSpec::Style::native);
UpdateBaseAddrIfNecessary(entry, name);
// not needed if we're using ModuleInfos
entry.next = 0;
entry.prev = 0;
entry.path_addr = 0;
return true;
}
bool DYLDRendezvous::SaveSOEntriesFromRemote(
const LoadedModuleInfoList &module_list) {
for (auto const &modInfo : module_list.m_list) {
SOEntry entry;
if (!FillSOEntryFromModuleInfo(modInfo, entry))
return false;
// Only add shared libraries and not the executable.
if (!SOEntryIsMainExecutable(entry)) {
UpdateFileSpecIfNecessary(entry);
m_soentries.push_back(entry);
}
}
m_loaded_modules = module_list;
return true;
}
bool DYLDRendezvous::AddSOEntriesFromRemote(
const LoadedModuleInfoList &module_list) {
for (auto const &modInfo : module_list.m_list) {
bool found = false;
for (auto const &existing : m_loaded_modules.m_list) {
if (modInfo == existing) {
found = true;
break;
}
}
if (found)
continue;
SOEntry entry;
if (!FillSOEntryFromModuleInfo(modInfo, entry))
return false;
// Only add shared libraries and not the executable.
if (!SOEntryIsMainExecutable(entry)) {
UpdateFileSpecIfNecessary(entry);
m_soentries.push_back(entry);
m_added_soentries.push_back(entry);
}
}
m_loaded_modules = module_list;
return true;
}
bool DYLDRendezvous::RemoveSOEntriesFromRemote(
const LoadedModuleInfoList &module_list) {
for (auto const &existing : m_loaded_modules.m_list) {
bool found = false;
for (auto const &modInfo : module_list.m_list) {
if (modInfo == existing) {
found = true;
break;
}
}
if (found)
continue;
SOEntry entry;
if (!FillSOEntryFromModuleInfo(existing, entry))
return false;
// Only add shared libraries and not the executable.
if (!SOEntryIsMainExecutable(entry)) {
auto pos = std::find(m_soentries.begin(), m_soentries.end(), entry);
if (pos == m_soentries.end())
return false;
m_soentries.erase(pos);
m_removed_soentries.push_back(entry);
}
}
m_loaded_modules = module_list;
return true;
}
bool DYLDRendezvous::AddSOEntries() {
SOEntry entry;
iterator pos;
assert(m_previous.state == eAdd);
if (m_current.map_addr == 0)
return false;
for (addr_t cursor = m_current.map_addr; cursor != 0; cursor = entry.next) {
if (!ReadSOEntryFromMemory(cursor, entry))
return false;
// Only add shared libraries and not the executable.
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);
m_added_soentries.push_back(entry);
}
}
return true;
}
bool DYLDRendezvous::RemoveSOEntries() {
SOEntryList entry_list;
iterator pos;
assert(m_previous.state == eDelete);
if (!TakeSnapshot(entry_list))
return false;
for (iterator I = begin(); I != end(); ++I) {
pos = std::find(entry_list.begin(), entry_list.end(), *I);
if (pos == entry_list.end())
m_removed_soentries.push_back(*I);
}
m_soentries = entry_list;
return true;
}
bool DYLDRendezvous::SOEntryIsMainExecutable(const SOEntry &entry) {
// On some systes the executable is indicated by an empty path in the entry.
// On others it is the full path to the executable.
auto triple = m_process->GetTarget().GetArchitecture().GetTriple();
switch (triple.getOS()) {
case llvm::Triple::FreeBSD:
case llvm::Triple::NetBSD:
return entry.file_spec == m_exe_file_spec;
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;
}
}
bool DYLDRendezvous::TakeSnapshot(SOEntryList &entry_list) {
SOEntry entry;
if (m_current.map_addr == 0)
return false;
// Clear previous entries since we are about to obtain an up to date list.
entry_list.clear();
for (addr_t cursor = m_current.map_addr; cursor != 0; cursor = entry.next) {
if (!ReadSOEntryFromMemory(cursor, entry))
return false;
// Only add shared libraries and not the executable.
if (SOEntryIsMainExecutable(entry))
continue;
UpdateFileSpecIfNecessary(entry);
entry_list.push_back(entry);
}
return true;
}
addr_t DYLDRendezvous::ReadWord(addr_t addr, uint64_t *dst, size_t size) {
Status error;
*dst = m_process->ReadUnsignedIntegerFromMemory(addr, size, 0, error);
if (error.Fail())
return 0;
return addr + size;
}
addr_t DYLDRendezvous::ReadPointer(addr_t addr, addr_t *dst) {
Status error;
*dst = m_process->ReadPointerFromMemory(addr, error);
if (error.Fail())
return 0;
return addr + m_process->GetAddressByteSize();
}
std::string DYLDRendezvous::ReadStringFromMemory(addr_t addr) {
std::string str;
Status error;
if (addr == LLDB_INVALID_ADDRESS)
return std::string();
m_process->ReadCStringFromMemory(addr, str, error);
return str;
}
// Returns true if the load bias reported by the linker is incorrect for the
// given entry. This function is used to handle cases where we want to work
// around a bug in the system linker.
static bool isLoadBiasIncorrect(Target &target, const std::string &file_path) {
// On Android L (API 21, 22) the load address of the "/system/bin/linker"
// isn't filled in correctly.
unsigned os_major = target.GetPlatform()->GetOSVersion().getMajor();
return target.GetArchitecture().GetTriple().isAndroid() &&
(os_major == 21 || os_major == 22) &&
(file_path == "/system/bin/linker" ||
file_path == "/system/bin/linker64");
}
void DYLDRendezvous::UpdateBaseAddrIfNecessary(SOEntry &entry,
std::string const &file_path) {
// If the load bias reported by the linker is incorrect then fetch the load
// address of the file from the proc file system.
if (isLoadBiasIncorrect(m_process->GetTarget(), file_path)) {
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
bool is_loaded = false;
Status error =
m_process->GetFileLoadAddress(entry.file_spec, is_loaded, load_addr);
if (error.Success() && is_loaded)
entry.base_addr = load_addr;
}
}
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();
entry.link_addr = addr;
if (!(addr = ReadPointer(addr, &entry.base_addr)))
return false;
// mips adds an extra load offset field to the link map struct on FreeBSD and
// NetBSD (need to validate other OSes).
// http://svnweb.freebsd.org/base/head/sys/sys/link_elf.h?revision=217153&view=markup#l57
const ArchSpec &arch = m_process->GetTarget().GetArchitecture();
if ((arch.GetTriple().getOS() == llvm::Triple::FreeBSD ||
arch.GetTriple().getOS() == llvm::Triple::NetBSD) &&
arch.IsMIPS()) {
addr_t mips_l_offs;
if (!(addr = ReadPointer(addr, &mips_l_offs)))
return false;
if (mips_l_offs != 0 && mips_l_offs != entry.base_addr)
return false;
}
if (!(addr = ReadPointer(addr, &entry.path_addr)))
return false;
if (!(addr = ReadPointer(addr, &entry.dyn_addr)))
return false;
if (!(addr = ReadPointer(addr, &entry.next)))
return false;
if (!(addr = ReadPointer(addr, &entry.prev)))
return false;
std::string file_path = ReadStringFromMemory(entry.path_addr);
entry.file_spec.SetFile(file_path, FileSpec::Style::native);
UpdateBaseAddrIfNecessary(entry, file_path);
return true;
}
bool DYLDRendezvous::FindMetadata(const char *name, PThreadField field,
uint32_t &value) {
Target &target = m_process->GetTarget();
SymbolContextList list;
target.GetImages().FindSymbolsWithNameAndType(ConstString(name),
eSymbolTypeAny, list);
if (list.IsEmpty())
return false;
Address address = list[0].symbol->GetAddress();
addr_t addr = address.GetLoadAddress(&target);
if (addr == LLDB_INVALID_ADDRESS)
return false;
Status error;
value = (uint32_t)m_process->ReadUnsignedIntegerFromMemory(
addr + field * sizeof(uint32_t), sizeof(uint32_t), 0, error);
if (error.Fail())
return false;
if (field == eSize)
value /= 8; // convert bits to bytes
return true;
}
const DYLDRendezvous::ThreadInfo &DYLDRendezvous::GetThreadInfo() {
if (!m_thread_info.valid) {
bool ok = true;
ok &= FindMetadata("_thread_db_pthread_dtvp", eOffset,
m_thread_info.dtv_offset);
ok &=
FindMetadata("_thread_db_dtv_dtv", eSize, m_thread_info.dtv_slot_size);
ok &= FindMetadata("_thread_db_link_map_l_tls_modid", eOffset,
m_thread_info.modid_offset);
ok &= FindMetadata("_thread_db_dtv_t_pointer_val", eOffset,
m_thread_info.tls_offset);
if (ok)
m_thread_info.valid = true;
}
return m_thread_info;
}
void DYLDRendezvous::DumpToLog(Log *log) const {
int state = GetState();
if (!log)
return;
log->PutCString("DYLDRendezvous:");
LLDB_LOGF(log, " Address: %" PRIx64, GetRendezvousAddress());
LLDB_LOGF(log, " Version: %" PRIu64, GetVersion());
LLDB_LOGF(log, " Link : %" PRIx64, GetLinkMapAddress());
LLDB_LOGF(log, " Break : %" PRIx64, GetBreakAddress());
LLDB_LOGF(log, " LDBase : %" PRIx64, GetLDBase());
LLDB_LOGF(log, " State : %s",
(state == eConsistent)
? "consistent"
: (state == eAdd) ? "add"
: (state == eDelete) ? "delete" : "unknown");
iterator I = begin();
iterator E = end();
if (I != E)
log->PutCString("DYLDRendezvous SOEntries:");
for (int i = 1; I != E; ++I, ++i) {
LLDB_LOGF(log, "\n SOEntry [%d] %s", i, I->file_spec.GetCString());
LLDB_LOGF(log, " Base : %" PRIx64, I->base_addr);
LLDB_LOGF(log, " Path : %" PRIx64, I->path_addr);
LLDB_LOGF(log, " Dyn : %" PRIx64, I->dyn_addr);
LLDB_LOGF(log, " Next : %" PRIx64, I->next);
LLDB_LOGF(log, " Prev : %" PRIx64, I->prev);
}
}