使用-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
,以及lea
(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
后,mygcc 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!