记录一个有趣的bug

用gcc 8.2.0不同的优化参数编译下面这段代码,运行结果是完全不一样的:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 int a = 0;
 6 int b = 1;
 7 
 8 void doA(){
 9         while (a==b) {
10         }
11 }
12 
13 int main(){
14         b = a;
15 
16         pthread_t tid;
17         pthread_create(&tid, NULL, (void*)doA, NULL);
18 
19         usleep(1000);
20         b = 5;
21         pthread_join(tid, 0);
22         return 0;
23 }

-O0: 程序成功执行完退出.

-O1、-O2、-O3: 程序在pthread_join()这里死等。

为什么呢?

对比反汇编的结果:

%rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。

%rbp 是栈帧指针,用于标识当前栈帧的起始位置

%edx和%eax:如果使用的是64位通用寄存器的低32位,则寄存器以 ”e“ 开头,比如 %eax,%ebx (参考:https://zhuanlan.zhihu.com/p/27339191

push %rbp:子函数将父函数的栈帧起始地址压栈
mov %rsp,%rbp: 将 %rbp 指向子函数栈帧的起始地址
pop %rbp:退出前将父函数的栈帧起始地址赋给%rbp

nop 空语句,占用一个时钟周期,可以不管。

0x0(%rip),rip寄存器存放当前指令的地址值,这个字段的含义是该地址值加上偏移量0,水平有限,这个地方我猜是读取a和b的值到寄存器,但这个rip如何找到a/b我没搞懂,留个坑。

 重点关注:

O0 判断到a==b时,会跳回doA+0x5的位置(je  5),重新从内存上获取a和b的值;

O1 则是跳回doA+0xc的位置,再次执行判断,并不会更新a和b的值。

要命的是,这段代码,while循环体内加一句printf,就会影响编译器的优化,从而使Bug消失,所以加了printf反而无法定位出这个bug,必须反汇编。

 

posted @ 2020-08-16 12:12  小明583  阅读(183)  评论(0)    收藏  举报