我又读了一本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

我知道%gs0x63的值,它是段描述符号,指向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/

10-11 15:42