【问题标题】:TLS Data Address the Same in All Threads所有线程中的 TLS 数据地址相同
【发布时间】:2021-04-22 07:31:04
【问题描述】:

我使用GDB 跟踪glibc-2.27。在sysdeps/unix/sysv/linux/getsysstats.c中的178行,有一个thread local storage访问,如下图:

while (l < re && isspace (*l))

IIUC,isspace() 似乎访问了 table mapping ASCII 字符到符号类型,以快速确定当前字符是否为空格。这张表似乎是TLS。相关拆解如下:

0x7f8f9ef480de <__GI___get_nprocs+318>  mov    0x2cbd1b(%rip),%rax        # 0x7f8f9f213e00                                                                        
0x7f8f9ef480e5 <__GI___get_nprocs+325>  mov    %fs:(%rax),%rdi                                                                                                    

rax 包含0xffffffffffffff98,IIUC 表示表的地址,对于每个线程,使用以下等式计算:@987654333 @。当我使用这个等式查找每个线程的表地址时,它们都返回相同0x00007f8f9732b82c。如下所示:

(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

我认为TLS每个线程专有的,但是在这里,所有线程使用相同强>变量0x00007f8f9732b82c。为什么会这样?似乎链接器识别变量是read-only 并且节省一些空间?

【问题讨论】:

  • 验证 fs_base 在每个线程中给出不同的值。我认为 gdb 可能会在 thread apply 之前替换 $fs_base ,因此它对所有线程使用当前线程的值。手动切换线程并检查。
  • 我查过了。它们是不同的。你所说的对$fs 是正确的,not $fs_base(在GDB 8 中引入以解决问题)。

标签: assembly linker gdb glibc thread-local-storage


【解决方案1】:

您已经展示了每个线程都有一个不同的指针变量,在 TLS 存储中
0x7f8f75dfc698
0x7f8f768fd698 等。

它们都指向同一个表这一事实完全正常,除非您使用uselocale(3) 在不同的线程中拥有不同的语言环境。

我认为 glibc 具有用于不同语言环境的静态常量 (.section .rodata) 字符映射表,它会根据语言环境设置指向正确表的指针。为每个线程复制整个表将非常低效,浪费更多的 L3 缓存占用空间。如果它要这样做,您会期望整个表都在那里,没有间接级别。

看看 glibc 如何实现像 isupper() 这样的函数 - 通过索引一个字符属性标志数组,并检查该数组元素中的某个位。 (即每个数组元素都是标志位图)。

https://code.woboq.org/userspace/glibc/ctype/ctype.h.html和相关的.c文件在同一目录下。

【讨论】:

  • 你的建议是合理的。但是为什么要per-thread 定义表(然后映射到same 位置)?!
  • @TheAhmad:因为语言环境的东西是每个线程的,就像我在回答中提到的那样:一个线程可以使用基于 utf-8 的语言环境,而另一个线程使用 8 位土耳其语或其他内容。 POSIX.2008 uselocale(3) - set/get the locale for the calling threadsetlocale 更清楚。或者不同的语言环境可以有不同的 toupper / tolow 规则,即使是 UTF-8。例如土耳其语是多种词汇的典型例子,包括 ASCII 范围字符上的 toupper 或 tolower 产生非 ASCII 字符,这与英语不同。
  • 所以,您是说locale 的东西是每个线程的,但它有一些重复 数据,例如映射表 .它们all 是按线程定义的,但链接器决定使用duplicate 数据的single 副本来节省一些空间。但非重复部分(例如,那些对土耳其语来说专有)将被管理,专有(即,将 被共享)。对吗?
  • @TheAhmad:我认为情况正好相反。土耳其表有一个全局副本,法语表有一个全局副本,依此类推(进程启动时通过 mmap 加载到内存中,未链接入)。每个线程在其线程本地存储中都有一个指向它打算使用的表的指针。如果线程 A 将其语言环境从土耳其语更改为法语,它只需将其线程本地指针更改为指向(全局)法语表。
  • @TheAhmad 不,彼得是说有一份“C”语言环境数据、一份“UTF-8”语言环境数据和一份土耳其语言环境数据。如果所有线程都使用相同的语言环境,那么所有线程语言环境指针将指向相同的语言环境数据。这里没有链接器魔术。 C 库刚刚将语言环境指针设置为相同的值。语言环境指针本身在线程本地存储中,但它们指向的语言环境数据在普通静态存储中。
猜你喜欢
  • 2012-08-01
  • 2014-12-23
  • 1970-01-01
  • 2016-03-16
  • 2020-04-03
  • 1970-01-01
  • 1970-01-01
  • 2012-10-10
  • 1970-01-01
相关资源
最近更新 更多