Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/linux/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用-fPIC编译的程序在GDB中跳过线程局部变量时崩溃_C_Linux_Gcc_Gdb_Pthreads - Fatal编程技术网

使用-fPIC编译的程序在GDB中跳过线程局部变量时崩溃

使用-fPIC编译的程序在GDB中跳过线程局部变量时崩溃,c,linux,gcc,gdb,pthreads,C,Linux,Gcc,Gdb,Pthreads,这是一个非常奇怪的问题,只有在使用-fPIC选项编译程序时才会出现 使用gdb我可以打印线程局部变量,但是跳过它们会导致崩溃 thread.c #include <pthread.h> #include <stdlib.h> #include <stdio.h> #define MAX_NUMBER_OF_THREADS 2 struct mystruct { int x; int y; }; __thread struct my

这是一个非常奇怪的问题,只有在使用
-fPIC
选项编译程序时才会出现

使用
gdb
我可以打印线程局部变量,但是跳过它们会导致崩溃

thread.c

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

#define MAX_NUMBER_OF_THREADS 2

struct mystruct {
    int   x;
    int   y;
};

__thread struct mystruct obj;

void* threadMain(void *args) {
    obj.x = 1;
    obj.y = 2;

    printf("obj.x = %d\n", obj.x);
    printf("obj.y = %d\n", obj.y);

    return NULL;
}

int main(int argc, char *arg[]) {
    pthread_t tid[MAX_NUMBER_OF_THREADS];
    int i = 0;

    for(i = 0; i < MAX_NUMBER_OF_THREADS; i++) {
        pthread_create(&tid[i], NULL, threadMain, NULL);
    }

    for(i = 0; i < MAX_NUMBER_OF_THREADS; i++) {
        pthread_join(tid[i], NULL);
    }

    return 0;
}
尽管如此,如果我编译它时没有使用
-fPIC
,那么这个问题就不会发生

在有人问我为什么要使用
-fPIC
之前,这只是一个简化的测试用例。我们有一个巨大的组件,它编译成一个
so
文件,然后插入另一个组件。因此,
fPIC
是必要的

它不会对功能产生影响,只是调试几乎是不可能的

平台信息:
Linux 2.6.32-431.el6.x86#u 64#1 SMP Sun 11月10日22:19:54 EST 2013 x86_64 x86_64 GNU/Linux
,Red Hat Enterprise Linux Server 6.5版(圣地亚哥)

在以下情况下也可复制

Linux 3.13.0-66-generic #108-Ubuntu SMP Wed Oct 7 15:20:27 
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4

问题在于GNU汇编程序GAS的内部,以及它如何生成调试信息

编译器GCC负责为独立于位置的线程本地访问生成特定的指令序列,该指令序列记录在文档第22页第4.1.6节:x86-64通用动态TLS模型中。这一顺序是:

0x00 .byte 0x66
0x01 leaq  x@tlsgd(%rip),%rdi
0x08 .word 0x6666
0x0a rex64
0x0b call __tls_get_addr@plt
,这是因为它占用的16个字节为后端/汇编程序/链接器优化留出了空间。实际上,编译器为
threadMain()
生成以下汇编程序:

然后,汇编程序GAS将包含函数调用(!)的这段代码放松到只有两条指令。这些是:

  • 具有
    fs:
    段覆盖的
    mov
    ,以及
  • a
    lea
  • ,在总装中。它们总共占用16个字节,这说明了为什么通用动态模型指令序列设计为需要16个字节

    (gdb) disas/r threadMain                                                                                                                                                                                         
    Dump of assembler code for function threadMain:                                                                                                                                                                  
       0x00000000004007f0 <+0>:     55      push   %rbp                                                                                                                                                              
       0x00000000004007f1 <+1>:     48 89 e5        mov    %rsp,%rbp                                                                                                                                                 
       0x00000000004007f4 <+4>:     48 83 ec 10     sub    $0x10,%rsp                                                                                                                                                
       0x00000000004007f8 <+8>:     48 89 7d f8     mov    %rdi,-0x8(%rbp)                                                                                                                                           
       0x00000000004007fc <+12>:    64 48 8b 04 25 00 00 00 00      mov    %fs:0x0,%rax
       0x0000000000400805 <+21>:    48 8d 80 f8 ff ff ff    lea    -0x8(%rax),%rax
       0x000000000040080c <+28>:    c7 00 01 00 00 00       movl   $0x1,(%rax)
    
    。正确放置断点需要将指令的第一个字节保存并替换为
    int3
    (操作码
    0xcc
    ),保留

    。然后,正常的单步执行顺序将涉及恢复指令的第一个字节,将程序计数器
    eip
    设置为该断点的地址,单步执行,重新插入断点,然后继续程序

    但是,当GDB将断点放在不正确的地址1字节太远时,程序会看到

    64 cc                           fs:int3
    8b 04 25 00 00 00 00            <garbage>
    
    64 cc fs:int3
    8b 04 25 00 00
    
    这是一个wierd但仍然有效的断点。这就是为什么你没有看到SIGILL(非法指令)

    现在,当GDB尝试跳转时,它会恢复指令字节,将PC设置为断点的地址,这就是它现在看到的:

    64                              fs:                # CPU DOESN'T SEE THIS!
    48 8b 04 25 00 00 00 00         mov    (0x0),%rax  # <- CPU EXECUTES STARTING HERE!
    # BOOM! SEGFAULT!
    
    64 fs:#CPU没有看到这个!
    
    48 8b 04 25 00 mov(0x0),%rax#更新到最新的gdb(如果需要,从源代码生成)。如果问题仍然存在,请提交一个bug。如果您是RH的付费客户,您也可以尝试从RH获得支持。Debian Wheezy(Linux Debian stable 3.2.0-4-amd64#1 SMP Debian 3.2.68-1+deb7u5 x86_64 GNU/Linux)在使用gcc 4.7.2时的相同行为。在将
    -lpthread
    修复为
    -pthread
    后,my
    gcc 4.8.4
    /
    gdb 7.7.1
    运行此程序没有任何问题。@Kartikan不,它不应该是
    -lpthread
    。参见OK,我认为问题的根源在于:对于
    obj.x=1
    ,gcc发出的汇编代码是
    .loc 1 14 0\n.byte 0x66\n leaqobj@tlsgd(%rip),%rdi\n.值0x6666\n rex64\n调用_addr@PLT\n movl$1,(%rax)
    。(大部分指令序列在可执行文件生成之前被加载程序替换。)当gas看到
    .loc
    时,当它看到下一条指令时,即当它看到
    leaq时,它将发出矮化行表信息obj@tlsgd(%rip),%rdi
    。但gcc显然希望气体在看到
    字节0x66
    指令时立即发出行号信息。
    64 48 8b 04 25 00 00 00 00      mov    %fs:0x0,%rax
    
    cc                              int3
    48 8b 04 25 00 00 00 00         mov    (0x0),%rax
    
    64 cc                           fs:int3
    8b 04 25 00 00 00 00            <garbage>
    
    64                              fs:                # CPU DOESN'T SEE THIS!
    48 8b 04 25 00 00 00 00         mov    (0x0),%rax  # <- CPU EXECUTES STARTING HERE!
    # BOOM! SEGFAULT!