To make this work this patch extends LLDB to: - Explicitly track the link_map address for each module. This is effectively the module handle, not sure why it wasn't already being stored off anywhere. As an extension later, it would be nice if someone were to add support for printing this as part of the modules list. - Allow reading the per-thread data pointer via ptrace. I have added support for Linux here. I'll be happy to add support for FreeBSD once this is reviewed. OS X does not appear to have __thread variables, so maybe we don't need it there. Windows support should eventually be workable along the same lines. - Make DWARF expressions track which module they originated from. - Add support for the DW_OP_GNU_push_tls_address DWARF opcode, as generated by gcc and recent versions of clang. Earlier versions of clang (such as 3.2, which is default on Ubuntu right now) do not generate TLS debug info correctly so can not be supported here. - Understand the format of the pthread DTV block. This is where it gets tricky. We have three basic options here: 1) Call "dlinfo" or "__tls_get_addr" on the inferior and ask it directly. However this won't work on core dumps, and generally speaking it's not a good idea for the debugger to call functions itself, as it has the potential to not work depending on the state of the target. 2) Use libthread_db. This is what GDB does. However this option requires having a version of libthread_db on the host cross-compiled for each potential target. This places a large burden on the user, and would make it very hard to cross-debug from Windows to Linux, for example. Trying to build a library intended exclusively for one OS on a different one is not pleasant. GDB sidesteps the problem and asks the user to figure it out. 3) Parse the DTV structure ourselves. On initial inspection this seems to be a bad option, as the DTV structure (the format used by the runtime to manage TLS data) is not in fact a kernel data structure, it is implemented entirely in useerland in libc. Therefore the layout of it's fields are version and OS dependent, and are not standardized. However, it turns out not to be such a problem. All OSes use basically the same algorithm (a per-module lookup table) as detailed in Ulrich Drepper's TLS ELF ABI document, so we can easily write code to decode it ourselves. The only question therefore is the exact field layouts required. Happily, the implementors of libpthread expose the structure of the DTV via metadata exported as symbols from the .so itself, designed exactly for this kind of thing. So this patch simply reads that metadata in, and re-implements libthread_db's algorithm itself. We thereby get cross-platform TLS lookup without either requiring third-party libraries, while still being independent of the version of libpthread being used. Test case included. llvm-svn: 192922
408 lines
11 KiB
C++
408 lines
11 KiB
C++
//===-- 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/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)
|
|
{
|
|
addr_t info_location;
|
|
addr_t info_addr;
|
|
Error error;
|
|
|
|
info_location = process->GetImageInfoAddress();
|
|
|
|
if (info_location == LLDB_INVALID_ADDRESS)
|
|
return LLDB_INVALID_ADDRESS;
|
|
|
|
info_addr = process->ReadPointerFromMemory(info_location, error);
|
|
if (error.Fail())
|
|
return LLDB_INVALID_ADDRESS;
|
|
|
|
if (info_addr == 0)
|
|
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()
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
bool
|
|
DYLDRendezvous::Resolve()
|
|
{
|
|
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 (m_rendezvous_addr == LLDB_INVALID_ADDRESS)
|
|
cursor = info_addr = ResolveRendezvousAddress(m_process);
|
|
else
|
|
cursor = info_addr = m_rendezvous_addr;
|
|
|
|
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);
|
|
}
|
|
}
|