我又读了一本discussion。
我知道%gs
是段寄存器,它存储段描述符。OS获取段描述符并计算物理地址。大多数情况下,段描述符对于程序员来说是不可内联的。我可以做一些技巧,比如拦截systcallset_thread_area
并获取%gs的值。
但他们说的大多数东西对我来说还是太抽象了。所以我试图构建一个简单的代码来表达我的问题。我希望有人能告诉我在我的例子中我做错了什么。
首先,我编写了一个pthread代码,如下所示。
__thread int Sum = 123; // declare as __thread type. 123 = 0x7b
void *show_msg( void *ptr ) {
for( int x = 5 ; x > 0 ; --x){
printf("%d\n", Sum++ ); // print the value of Sum and plus 1
sleep(1);
}
pthread_exit((void *)1234);
}
int main(){
pthread_t thread1;
pthread_t thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";
pthread_create(&thread1, NULL , show_msg , (void*) message1);
pthread_create(&thread2, NULL , show_msg , (void*) message2);
pthread_join( thread1, &ret);
pthread_join( thread2, &ret);
return 0;
}
我用
gcc test.cpp -lpthread -static -m32
编译它然后我会
objdump -D a.out
。我只发布了一部分我不明白的结果。因为a.out
是一个静态链接的二进制文件,所以我可以得到一些初始化代码,比如<__libc_setup_tls>
08052510 <__libc_setup_tls>:
...
805262c: mov $0xf3,%eax ; syscall number 0xf3 is set_thread_area
8052631: mov %ebx,0x24(%esp)
805262c: lea 0x20(%esp),%ebx ; %ebx stores a pointer to struct user_desc
...
8052651: int $0x80
...
080496d4<_Z8show_msgPv>:
...
80496f0: mov %gs:0xffffffd0,%eax
80496f6: lea 0x1(%eax),%edx
80496f9: mov %edx,%gs:0xffffffd0
...
我用a.out运行gdb,并将断点设置为0x805262c和0x80496f0。
805262c: lea 0x20(%esp),%ebx ; %ebx stores a pointer to struct user_desc
执行此指令后,%ebx的值为0xffffccd0。我知道值0xffffccd0是
user_desc
的指针,内存0xffffccd4存储%gs
的值,即0x080fd840。然后我继续调试。
80496f0: mov %gs:0xffffffd0,%eax
我知道
%gs
0x63的值,它是段描述符号,指向0x080fd840。所以我可以计算出%gs:0xffffffd0
的值是0x080fd810
。0x080fd810的内存存储0x7b。当我得到这个值时,我很兴奋,因为0x7b是123的十六进制值,这是全局变量
Sum
的初始值。但是当我按照下面的指示做的时候有些奇怪。
80496f6: lea 0x1(%eax),%edx ; yield %edx = 0x7c
80496f9: mov %edx,%gs:0xffffffd0 ; store 0x7c to %gs:0xffffffd0(????)
加法的结果不存储到
0x080fd810
,即%gs:0xffffffd0
的内存地址。但是这个线程的下一次迭代可以从%gs:0xffffffd0
获得0x7c!!!我使用
strace -c ./a.out
跟踪系统调用。结果表明,呼叫set_thread_area
的次数仅为1。也就是说,%gs
只设置一次。我认为当线程上下文切换发生时,操作系统会做一些改变。有谁能给我更多的细节,告诉我为什么我的想法在这种情况下是错误的?
最佳答案
操作系统将处理线程本地存储(TLS)的内存,并保持在加载下一个线程时更新%gs
[或其基址],以及在创建新线程时分配内存[1]。
编译器和链接器负责计算TLS的大小和相应的偏移量-在这种情况下,实现似乎使用了与基址的负偏移量,因此您的特定变量是-0x30 from%gs
。
[当您说“我知道%gs
是0x080fd840时,您的意思是段的基址就是这个值,对吧?因为%gs
是x86描述符表的16位索引]
[1]这可能意味着操作系统只是为TLS提供了一个虚拟地址,但是物理内存的实际分配是“根据需要”进行的,就像执行文件、共享库或大内存分配一样。
关于c++ - 关于访问%gs,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/33708206/