To debug live processes on modern Linux GDB needs four libthread_db functions:
td_ta_map_lwp2thr(required for initial attach)td_thr_get_info(required for initial attach)td_thr_tls_get_addr(not required for initial attach, but required for “p errno” on regular executables)td_thr_tlsbase(not required for initial attach, but required for “p errno” for-static -pthreadexecutables)
To debug a corefile on modern Linux GDB needs one more libthread_db function:
td_ta_thr_iter
GDB makes some other libthread_db calls too, but these are bookkeeping that won’t be required with the replacement. So, the order of work will be:
- Implement replacements for the four core functions.
- Get those approved and committed in GDB, BFD and glibc (and in binutils, coreutils readelf).
- Replace
td_ta_thr_itertoo, and get that committed. - Implement runtime-linker interface stuff to allow GDB to follow
dlmopen.
The first (non-bookkeeping) function GDB calls is td_ta_map_lwp2thr and it’s a pig. If I can do td_ta_map_lwp2thr I can do anything.
When you call it, td_ta_map_lwp2thr has four ways it can proceed:
- If
__pthread_initialize_minimalhas not gotten far enough we can’t rely on whatever’s in the thread registers. If this is the case,td_ta_map_lwp2thrchecks that the LWP is the initial thread and setsth->th_uniqueto NULL. (Other bits of libthread_db spot this NULL and act accordingly.)td_ta_map_lwp2thrdecides whether__pthread_initialize_minimalhas gotten far enough by examining__stack_user.nextin the inferior. If it’s NULL then__pthread_initialize_minimalhas not gotten far enough. - On
ta_howto_const_thread_areaarchitectures (x86_64, aarch64, arm)
[glibc/sysdeps/*/nptl/tls.hhas
#define DB_THREAD_SELF CONST_THREAD_AREA(bits, value)
which exports
const uint32_t _thread_db_const_thread_area = value;
fromglibc/nptl_db/db_info.c]:td_ta_map_lwp2thrwill callps_get_thread_areawithvalue
to set
th->th_unique.ps_get_thread_area(in GDB) does different things for different
architectures:- on x86_64,
valueis a register number (FS or GS)
ps_get_thread_areareturns the contents of that register. - on arm, GDB uses
PTRACE_GET_THREAD_AREA, NULLand subtractsvaluefrom the result. - on aarch64, GDB uses
PTRACE_GETREGSET, NT_ARM_TLSand subtractsvaluefrom the result.
- On
ta_howto_regarchitectures (ppc*, s390*)
[glibc/sysdeps/*/nptl/tls.hhas
#define DB_THREAD_SELF REGISTER(bits, size, regofs, bias)...
which exports
const uint32_t _thread_db_register32[3] = {size, regofs, bias};
and/or
const uint32_t _thread_db_register64[3] = {size, regofs, bias};
fromglibc/nptl_db/db_info.c]:td_ta_map_lwp2thrwill:- call ps_lgetregs to get the inferior’s registers
- get the contents of the specified register (with _td_fetch_value_local)
and
- SUBTRACT bias from the register’s contents
to set
th->unique. - On
ta_howto_reg_thread_areaarchitectures (i386)
[glibc/sysdeps/*/nptl/tls.hhas
#define DB_THREAD_SELF REGISTER_THREAD_AREA(bits, size, regofs, bias)...
which exports
const uint32_t _thread_db_register32_thread_area[3] = {size, regofs, bias};
and/or
const uint32_t _thread_db_register64_thread_area[3] = {size, regofs, bias};
fromglibc/nptl_db/db_info.c]:td_ta_map_lwp2thrwill:- call
ps_lgetregsto get the inferior’s registers - get the contents of the specified register (with
_td_fetch_value_local) - RIGHT SHIFT the register’s contents by bias
and
- call
ps_get_thread_areawith that number
to set
th->unique.ps_get_thread_area(in GDB) does different things for different
architectures:- on i386, GDB uses
PTRACE_GET_THREAD_AREA, VALUEand returns the second element of the result.
- call
Cases 2, 3, and 4 will obviously be hardwired into the specific architecture’s libpthread. But… yeah.