[lldb][RISCV] Implement access to TLS variables on RISC-V (#191410)
On RISC-V Linux, LLDB computes TLS variable addresses incorrectly: `GetThreadLocalData` returns a correct tls_block, but then unconditionally adds tls_file_addr from `DW_OP_GNU_push_tls_address`, which on RISC-V/glibc is a VMA inside PT_TLS, not a pure offset. This results in an over-shifted address. This patch: * Adds a small helper that, for an ELF module, finds the PT_TLS program header and reads its p_vaddr. * In `DynamicLoaderPOSIXDYLD::GetThreadLocalData`, normalizes tls_file_addr to an offset: if `PT_TLS` is found and tls_file_addr >= p_vaddr, it uses tpoff = tls_file_addr - p_vaddr, otherwise keeps the old value. * Returns tls_block + tpoff instead of always tls_block + tls_file_addr. This fixes reading TLS variables on RISC-V/glibc while leaving behavior unchanged on targets where DWARF already provides a plain offset (e.g. x86_64). Also this patch fixes `TestTlsGlobals.py` --------- Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
This commit is contained in:
@@ -91,7 +91,7 @@ static const std::array<RegisterInfo, 33> g_register_infos = {
|
||||
DEFINE_GENERIC_REGISTER_STUB(ra, nullptr, LLDB_REGNUM_GENERIC_RA),
|
||||
DEFINE_GENERIC_REGISTER_STUB(sp, nullptr, LLDB_REGNUM_GENERIC_SP),
|
||||
DEFINE_REGISTER_STUB(gp, nullptr),
|
||||
DEFINE_REGISTER_STUB(tp, nullptr),
|
||||
DEFINE_GENERIC_REGISTER_STUB(tp, nullptr, LLDB_REGNUM_GENERIC_TP),
|
||||
DEFINE_REGISTER_STUB(t0, nullptr),
|
||||
DEFINE_REGISTER_STUB(t1, nullptr),
|
||||
DEFINE_REGISTER_STUB(t2, nullptr),
|
||||
@@ -820,6 +820,7 @@ static uint32_t GetGenericNum(llvm::StringRef name) {
|
||||
.Cases({"ra", "x1"}, LLDB_REGNUM_GENERIC_RA)
|
||||
.Cases({"sp", "x2"}, LLDB_REGNUM_GENERIC_SP)
|
||||
.Cases({"fp", "s0"}, LLDB_REGNUM_GENERIC_FP)
|
||||
.Cases({"tp", "x4"}, LLDB_REGNUM_GENERIC_TP)
|
||||
.Case("a0", LLDB_REGNUM_GENERIC_ARG1)
|
||||
.Case("a1", LLDB_REGNUM_GENERIC_ARG2)
|
||||
.Case("a2", LLDB_REGNUM_GENERIC_ARG3)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// Main header include
|
||||
#include "DynamicLoaderPOSIXDYLD.h"
|
||||
|
||||
#include "Plugins/ObjectFile/ELF/ObjectFileELF.h"
|
||||
#include "Plugins/ObjectFile/Placeholder/ObjectFilePlaceholder.h"
|
||||
#include "lldb/Breakpoint/BreakpointLocation.h"
|
||||
#include "lldb/Core/Debugger.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "lldb/Utility/LLDBLog.h"
|
||||
#include "lldb/Utility/Log.h"
|
||||
#include "lldb/Utility/ProcessInfo.h"
|
||||
#include "llvm/BinaryFormat/ELF.h"
|
||||
#include "llvm/Support/ThreadPool.h"
|
||||
|
||||
#include <memory>
|
||||
@@ -814,6 +816,26 @@ addr_t DynamicLoaderPOSIXDYLD::GetEntryPoint() {
|
||||
return m_entry_point;
|
||||
}
|
||||
|
||||
static lldb::addr_t GetPTTLSVAddr(const lldb::ModuleSP &module_sp) {
|
||||
if (!module_sp)
|
||||
return LLDB_INVALID_ADDRESS;
|
||||
|
||||
ObjectFile *objfile = module_sp->GetObjectFile();
|
||||
if (!objfile)
|
||||
return LLDB_INVALID_ADDRESS;
|
||||
|
||||
auto *elf_obj = llvm::dyn_cast<ObjectFileELF>(objfile);
|
||||
if (!elf_obj)
|
||||
return LLDB_INVALID_ADDRESS;
|
||||
|
||||
for (const auto &phdr : elf_obj->ProgramHeaders()) {
|
||||
if (phdr.p_type == llvm::ELF::PT_TLS)
|
||||
return phdr.p_vaddr;
|
||||
}
|
||||
|
||||
return LLDB_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
lldb::addr_t
|
||||
DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
|
||||
const lldb::ThreadSP thread,
|
||||
@@ -894,6 +916,18 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
|
||||
dtv_ptr = tp;
|
||||
}
|
||||
}
|
||||
const llvm::Triple &triple = module_sp->GetArchitecture().GetTriple();
|
||||
|
||||
// On RISC-V with glibc the TLS layout uses Variant I (TLS_DTV_AT_TP).
|
||||
// The thread pointer (tp) points just past a tcbhead_t header which
|
||||
// contains two pointers: { dtv, private }. This means the DTV pointer
|
||||
// itself is located two pointer-sized slots before tp, so dtv_ptr must
|
||||
// be computed as tp - 2 * sizeof(void*). See MaskRay, “All about
|
||||
// thread-local storage” (RISC-V/glibc section) and glibc's RISC-V
|
||||
// TLS port (__tls_get_addr / THREAD_DTV).
|
||||
if (triple.isRISCV())
|
||||
dtv_ptr = tp - 2 * triple.getArchPointerBitWidth() / 8;
|
||||
|
||||
addr_t dtv = (dtv_ptr != LLDB_INVALID_ADDRESS) ? ReadPointer(dtv_ptr)
|
||||
: LLDB_INVALID_ADDRESS;
|
||||
if (dtv == LLDB_INVALID_ADDRESS) {
|
||||
@@ -915,8 +949,26 @@ DynamicLoaderPOSIXDYLD::GetThreadLocalData(const lldb::ModuleSP module_sp,
|
||||
if (tls_block == LLDB_INVALID_ADDRESS) {
|
||||
LLDB_LOGF(log, "GetThreadLocalData error: fail to read tls_block");
|
||||
return LLDB_INVALID_ADDRESS;
|
||||
} else
|
||||
return tls_block + tls_file_addr;
|
||||
}
|
||||
|
||||
// DW_OP_GNU_push_tls_address gives us a value in tls_file_addr that can be
|
||||
// either:
|
||||
// - a pure offset inside the TLS block (e.g. x86_64/glibc), or
|
||||
// - a virtual address inside the PT_TLS segment (e.g. RISC-V/glibc),
|
||||
// roughly PT_TLS.p_vaddr + TPOFF(sym).
|
||||
// To handle both cases, we try to normalize it to a plain offset (tpoff)
|
||||
// by subtracting PT_TLS.p_vaddr when available.
|
||||
addr_t pt_tls_vaddr = GetPTTLSVAddr(module_sp);
|
||||
addr_t tpoff = tls_file_addr;
|
||||
|
||||
// If the module has a PT_TLS segment and the DWARF value lies at or above
|
||||
// its p_vaddr, treat tls_file_addr as a VMA within PT_TLS and convert it
|
||||
// to an offset. Otherwise, keep the original value (it is already an offset
|
||||
// on targets like x86_64).
|
||||
if (pt_tls_vaddr != LLDB_INVALID_ADDRESS && tls_file_addr >= pt_tls_vaddr)
|
||||
tpoff = tls_file_addr - pt_tls_vaddr;
|
||||
|
||||
return tls_block + tpoff;
|
||||
}
|
||||
|
||||
void DynamicLoaderPOSIXDYLD::ResolveExecutableModule(
|
||||
|
||||
@@ -85,7 +85,7 @@ static lldb_private::RegisterInfo g_register_infos_riscv64_gpr[] = {
|
||||
DEFINE_GPR64_ALT(ra, x1, LLDB_REGNUM_GENERIC_RA),
|
||||
DEFINE_GPR64_ALT(sp, x2, LLDB_REGNUM_GENERIC_SP),
|
||||
DEFINE_GPR64_ALT(gp, x3, LLDB_INVALID_REGNUM),
|
||||
DEFINE_GPR64_ALT(tp, x4, LLDB_INVALID_REGNUM),
|
||||
DEFINE_GPR64_ALT(tp, x4, LLDB_REGNUM_GENERIC_TP),
|
||||
DEFINE_GPR64_ALT(t0, x5, LLDB_INVALID_REGNUM),
|
||||
DEFINE_GPR64_ALT(t1, x6, LLDB_INVALID_REGNUM),
|
||||
DEFINE_GPR64_ALT(t2, x7, LLDB_INVALID_REGNUM),
|
||||
|
||||
@@ -279,7 +279,7 @@ Changes to LLDB
|
||||
### Linux
|
||||
|
||||
* On Arm Linux, the `tpidruro` register can now be read. Writing to this register is not supported.
|
||||
* Thread local variables are now supported on Arm Linux if the program being debugged is using glibc.
|
||||
* Thread local variables are now supported on Arm and RISC-V Linux if the program being debugged is using glibc.
|
||||
* LLDB now supports AArch64 Linux systems that only have SME (as opposed to
|
||||
SVE and SME). See the AArch64 Linux [documentation](https://lldb.llvm.org/use/aarch64-linux.html#sme-only-systems)
|
||||
for more details.
|
||||
|
||||
Reference in New Issue
Block a user