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 -pthread
executables)
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_iter
too, 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_minimal
has not gotten far enough we can’t rely on whatever’s in the thread registers. If this is the case,td_ta_map_lwp2thr
checks that the LWP is the initial thread and setsth->th_unique
to NULL. (Other bits of libthread_db spot this NULL and act accordingly.)td_ta_map_lwp2thr
decides whether__pthread_initialize_minimal
has gotten far enough by examining__stack_user.next
in the inferior. If it’s NULL then__pthread_initialize_minimal
has not gotten far enough. - On
ta_howto_const_thread_area
architectures (x86_64, aarch64, arm)
[glibc/sysdeps/*/nptl/tls.h
has
#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_lwp2thr
will callps_get_thread_area
withvalue
to set
th->th_unique
.ps_get_thread_area
(in GDB) does different things for different
architectures:- on x86_64,
value
is a register number (FS or GS)
ps_get_thread_area
returns the contents of that register. - on arm, GDB uses
PTRACE_GET_THREAD_AREA, NULL
and subtractsvalue
from the result. - on aarch64, GDB uses
PTRACE_GETREGSET, NT_ARM_TLS
and subtractsvalue
from the result.
- On
ta_howto_reg
architectures (ppc*, s390*)
[glibc/sysdeps/*/nptl/tls.h
has
#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_lwp2thr
will:- 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_area
architectures (i386)
[glibc/sysdeps/*/nptl/tls.h
has
#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_lwp2thr
will:- call
ps_lgetregs
to 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_area
with 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, VALUE
and 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.