I traced glibc-2.27
using GDB
. At line 178
in sysdeps/unix/sysv/linux/getsysstats.c
, there exists a thread local storage
access, as shown below:
while (l < re && isspace (*l))
IIUC, isspace()
seems to access a table mapping ASCII
characters to symbol type to, quickly, determine if the current character is space or not. This table seems to be a TLS
. The relevant disassembly is as follows:
0x7f8f9ef480de <__GI___get_nprocs+318> mov 0x2cbd1b(%rip),%rax # 0x7f8f9f213e00
0x7f8f9ef480e5 <__GI___get_nprocs+325> mov %fs:(%rax),%rdi
rax
contains 0xffffffffffffff98
, which, IIUC, means that the address of the table, for each thread, is calculated using the following equation:$fs_base + 0xffffffffffffff98
. When I use this equation to find the table address for each thread, they all return the same value, 0x00007f8f9732b82c
. This is shown below:
(gdb) thread apply all x/2x $fs_base + 0xffffffffffffff98
Thread 47 (Thread 22457.22471):
0x7f8f75dfc698: 0x9732b82c 0x00007f8f
Thread 46 (Thread 22457.22470):
0x7f8f768fd698: 0x9732b82c 0x00007f8f
Thread 45 (Thread 22457.22469):
0x7f8f773fe698: 0x9732b82c 0x00007f8f
Thread 44 (Thread 22457.22468):
0x7f8f77eff698: 0x9732b82c 0x00007f8f
Thread 43 (Thread 22457.22467):
0x7f8f80a53698: 0x9732b82c 0x00007f8f
Thread 37 (Thread 22457.22465):
0x7f8f81c55698: 0x9732b82c 0x00007f8f
Thread 36 (Thread 22457.22464):
0x7f8f82456698: 0x9732b82c 0x00007f8f
Thread 35 (Thread 22457.22463):
0x7f8f8e6b0698: 0x9732b82c 0x00007f8f
Thread 34 (Thread 22457.22461):
0x7f8f8f480698: 0x9732b82c 0x00007f8f
Thread 33 (Thread 22457.22460):
0x7f8f94824698: 0x9732b82c 0x00007f8f
Thread 32 (Thread 22457.22459):
0x7f8f9649f698: 0x9732b82c 0x00007f8f
Thread 31 (Thread 22457.22458):
0x7f8f96ea0698: 0x9732b82c 0x00007f8f
Thread 30 (Thread 22457.22457):
0x7f8fa2570a18: 0x9732b82c 0x00007f8f
Thread 29 (Thread 22457.22466):
0x7f8f81454698: 0x9732b82c 0x00007f8f
I thought that TLS
is exclusive to each thread, but, here, all threads use the same variable at 0x00007f8f9732b82c
. Why is this the case? It seems that the linker recognizes the variable is read-only
and saves some space?
You've shown that each thread has a different pointer variable, in TLS storage at
0x7f8f75dfc698
vs.
0x7f8f768fd698
etc.
The fact that they're all pointing to the same table is completely normal, unless you've used uselocale(3)
to have different locales in different threads.
I think glibc has static constant (.section .rodata
) char-map tables for different locales, and it sets a pointer to the right table based on the locale. Duplicating the whole table for every thread would be very inefficient, wasting more L3 cache footprint. If it was going to do that, you'd expect the whole table to be right there without a level of indirection.
Have a look at how glibc implements functions like isupper()
- by indexing an array of character-attribute flags, and checking a certain bit in that array element. (i.e. every array element is a bitmap of flags).
https://code.woboq.org/userspace/glibc/ctype/ctype.h.html and related .c files in the same directory.