blob: 0e203fe43a790691a6402902e28db57385fd6374 [file] [log] [blame]
//===-- DYLDRendezvous.cpp --------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// C Includes
// C++ Includes
// Other libraries and framework includes
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/Module.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "DYLDRendezvous.h"
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)
{
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
addr_t info_location;
addr_t info_addr;
Error error;
// 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();
if (log)
log->Printf ("%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 ? &process->GetTarget() : nullptr;
if (target)
{
ObjectFile *obj_file = target->GetExecutableModule()->GetObjectFile();
Address addr = obj_file->GetImageInfoAddress(target);
if (addr.IsValid())
{
info_location = addr.GetLoadAddress(target);
if (log)
log->Printf ("%s resolved via direct object file approach to 0x%" PRIx64, __FUNCTION__, info_location);
}
else
{
if (log)
log->Printf ("%s FAILED - direct object file approach did not yield a valid address", __FUNCTION__);
}
}
}
if (info_location == LLDB_INVALID_ADDRESS)
{
if (log)
log->Printf ("%s FAILED - invalid info address", __FUNCTION__);
return LLDB_INVALID_ADDRESS;
}
info_addr = process->ReadPointerFromMemory(info_location, error);
if (error.Fail())
{
if (log)
log->Printf ("%s FAILED - could not read from the info location: %s", __FUNCTION__, error.AsCString ());
return LLDB_INVALID_ADDRESS;
}
if (info_addr == 0)
{
if (log)
log->Printf ("%s FAILED - the rendezvous address contained at 0x%" PRIx64 " returned a null value", __FUNCTION__, info_location);
return LLDB_INVALID_ADDRESS;
}
return info_addr;
}
DYLDRendezvous::DYLDRendezvous(Process *process)
: m_process(process),
m_rendezvous_addr(LLDB_INVALID_ADDRESS),
m_current(),
m_previous(),
m_soentries(),
m_added_soentries(),
m_removed_soentries()
{
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
m_thread_info.valid = false;
// Cache a copy of the executable path
if (m_process)
{
Module *exe_mod = m_process->GetTarget().GetExecutableModulePointer();
if (exe_mod)
{
exe_mod->GetFileSpec().GetPath(m_exe_path, PATH_MAX);
if (log)
log->Printf ("DYLDRendezvous::%s exe module executable path set: '%s'", __FUNCTION__, m_exe_path);
}
else
{
if (log)
log->Printf ("DYLDRendezvous::%s cannot cache exe module path: null executable module pointer", __FUNCTION__);
}
}
}
bool
DYLDRendezvous::Resolve()
{
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
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;
if (log)
log->Printf ("DYLDRendezvous::%s address size: %zu, padding %zu", __FUNCTION__, address_size, padding);
if (m_rendezvous_addr == LLDB_INVALID_ADDRESS)
cursor = info_addr = ResolveRendezvousAddress(m_process);
else
cursor = info_addr = m_rendezvous_addr;
if (log)
log->Printf ("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;
return UpdateSOEntries();
}
bool
DYLDRendezvous::IsValid()
{
return m_rendezvous_addr != LLDB_INVALID_ADDRESS;
}
bool
DYLDRendezvous::UpdateSOEntries()
{
SOEntry entry;
if (m_current.map_addr == 0)
return false;
// 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.
if (m_previous.state == eConsistent && m_current.state == eConsistent)
return TakeSnapshot(m_soentries);
// 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.
if (m_current.state == eAdd || m_current.state == eDelete)
{
assert(m_previous.state == eConsistent);
m_soentries.clear();
m_added_soentries.clear();
m_removed_soentries.clear();
return TakeSnapshot(m_soentries);
}
assert(m_current.state == eConsistent);
// Otherwise check the previous state to determine what to expect and update
// accordingly.
if (m_previous.state == eAdd)
return UpdateSOEntriesForAddition();
else if (m_previous.state == eDelete)
return UpdateSOEntriesForDeletion();
return false;
}
bool
DYLDRendezvous::UpdateSOEntriesForAddition()
{
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.
// On Linux this is indicated by an empty path in the entry.
// On FreeBSD it is the name of the executable.
if (entry.path.empty() || ::strcmp(entry.path.c_str(), m_exe_path) == 0)
continue;
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::UpdateSOEntriesForDeletion()
{
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::TakeSnapshot(SOEntryList &entry_list)
{
SOEntry entry;
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.
// On Linux this is indicated by an empty path in the entry.
// On FreeBSD it is the name of the executable.
if (entry.path.empty() || ::strcmp(entry.path.c_str(), m_exe_path) == 0)
continue;
entry_list.push_back(entry);
}
return true;
}
addr_t
DYLDRendezvous::ReadWord(addr_t addr, uint64_t *dst, size_t size)
{
Error 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)
{
Error 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;
Error error;
size_t size;
char c;
if (addr == LLDB_INVALID_ADDRESS)
return std::string();
for (;;) {
size = m_process->DoReadMemory(addr, &c, 1, error);
if (size != 1 || error.Fail())
return std::string();
if (c == 0)
break;
else {
str.push_back(c);
addr++;
}
}
return str;
}
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.GetCore() == ArchSpec::eCore_mips64)
{
assert (arch.GetTriple().getOS() == llvm::Triple::FreeBSD ||
arch.GetTriple().getOS() == llvm::Triple::NetBSD);
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;
entry.path = ReadStringFromMemory(entry.path_addr);
return true;
}
bool
DYLDRendezvous::FindMetadata(const char *name, PThreadField field, uint32_t& value)
{
Target& target = m_process->GetTarget();
SymbolContextList list;
if (!target.GetImages().FindSymbolsWithNameAndType (ConstString(name), eSymbolTypeAny, list))
return false;
Address address = list[0].symbol->GetAddress();
addr_t addr = address.GetLoadAddress (&target);
if (addr == LLDB_INVALID_ADDRESS)
return false;
Error 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:");
log->Printf(" Address: %" PRIx64, GetRendezvousAddress());
log->Printf(" Version: %" PRIu64, GetVersion());
log->Printf(" Link : %" PRIx64, GetLinkMapAddress());
log->Printf(" Break : %" PRIx64, GetBreakAddress());
log->Printf(" LDBase : %" PRIx64, GetLDBase());
log->Printf(" 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)
{
log->Printf("\n SOEntry [%d] %s", i, I->path.c_str());
log->Printf(" Base : %" PRIx64, I->base_addr);
log->Printf(" Path : %" PRIx64, I->path_addr);
log->Printf(" Dyn : %" PRIx64, I->dyn_addr);
log->Printf(" Next : %" PRIx64, I->next);
log->Printf(" Prev : %" PRIx64, I->prev);
}
}