記錄一個有趣的bug
阿新 • • 發佈:2020-08-16
編譯器的水很深啊...
用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; 17pthread_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,必須反彙編。