glibc的TLS实现中的相关结构体浅析

通过一个几行代码的示例来浅层地观察一下glibc的TLS实现中的结构体:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>

pthread_t ntid;

void *thr_fn(void *args)
{
char a;
printf("new thread:\n");
scanf("%c",&a);
}

int main()
{
int err;
err = pthread_create(&ntid,NULL,thr_fn,NULL);
pthread_join(ntid,NULL);
printf("main thread:\n");
char b;
scanf(" %c",&b);
return 0;
}

首先在main下断点,进入主线程:

gGx5VJ.png

X86TLSFS寄存器(通常由运行时代码或线程库管理)指向,TLSglibc中的实现为tcbhead_t(TCB)结构体,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard; //储存canary的值
uintptr_t pointer_guard;
unsigned long int vgetcpu_cache[2];
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
int __glibc_unused1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
/* The lowest address of shadow stack, */
unsigned long long int ssp_base;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));
void *__padding[8];
} tcbhead_t;

查看主线程的$fs_base(在保护模式下$fs为空值,需查看每个线程的$fs_base=0x7ffff7d9f740

gGx0bQ.png

动态链接器完成内存映射后将调用init_tls等一系列函数来初始化主线程的静态TLSDTV,由上面vmmap的结果可知此时TLS结构体(TCB)被分配在mmap段。

其中dtv部分指向一个类型为dtv_t的数组的第二个元素,该结构体的定义为:

1
2
3
4
5
6
7
typedef union dtv {
size_t counter;
struct {
void* val;
bool is_static;
} pointer;
} dtv_t;

dtv数组前两个元素具有特定功能,如下:

1
2
3
dtv[0].counter; /* Pro tip: The length of this dtv array */
dtv[1].counter; /* Generation counter for the DTV in this thread */
dtv[2].pointer; /* Pointer to the main executable TLS block in this thread */

查看主线程的dtv

gGxYCt.png

可见主线程dtv数组大小为0xfTLS块的数量为1,第三个元素0x7ffff7d9f6b0指向TLS(0x7ffff7d9f740)-0x90的位置

接着在子线程的执行scanf前下了断点,进入子线程查看:

gJSNnS.png

gJSa7Q.png

可以看出子线程的栈和TCB都被分配在mmap段(相关的知识我还不太懂,需要读pthread_creat源码),此处由pthread_creat源码中的__pthread_create_2_1->allocate_stack->_dl_allocate_tls函数知子线程的dtvcalloc分配,如下图:

gJ9RY9.png

gJ9hS1.png

大概了解到这里,后续再学习。

参考文章:A Deep dive into (implicit) Thread Local Storage (chao-tic.github.io)(写得很好,但是我英语不好很多读不太懂,推荐阅读)

pthread_create源码分析_conansonic的博客-CSDN博客

https://code.woboq.org/userspace/glibc

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器