GCC優化引起的一個"問題"

本來是發在長微博的, 不過, 鑒於, 好久沒更新博客了…… 就轉過來, 湊個數吧, 大家湊合著看 🙂

白忙活了近2個小時,不吐不快:

一切要從今天下午5點左右說起, 調試一個擴展, 用valgrind(valgrind-3.8.1)做例行檢查, 很不幸的valgrind報告invalid read:

GCC優化引起的一個"問題"

db attach上去以後, 發現報告錯誤的地方是:

GCC優化引起的一個"問題"

因為在PHP NG(PHP New Generation)中, 使用了新的字符串結構來保存字符串, 也就是zend_string:
GCC優化引起的一個"問題"
而排查了半天, 我確認這個op是經過正常初始化的, 那問題出在哪裡呢?

突然看到op是一個長度為1的字符串”0″, 就突然想起來, 之前我們做了個很”精細”的優化, 因為對於上面的結構體, 在64位的系統上, sizeof它, 由於padding, 實際上會得到大於8 + 8 + 4 + 1(21) 的大小(8 + 8 + 8 = 24).

所以我們不會使用一般來說的做法:

 str = malloc(sizeof(str) + len + 1) 

來為一個長度為len的字符串申請內存. 而是會使用類似:

 str = malloc ((int)((str*)0)->val) + len + 1) 

的方式來為一個字符串申請內存, 所以對於”0″, 我們實際上申請分配的內存是22bytes.

但, 又會有什麼問題呢? 於是讓我們再次db attach上去, disassmble下看看具體是什麼原因:
GCC優化引起的一個"問題"

恩, 問題就出在f3b5這行, GCC讀取了0x10(%rdx)位置上的一個word大小的數據, %rdx此時是zend_string op的指針, 而0x10偏移是str->len. 原來是因為GCC優化很聰明的把

 if (str->len == 1 && str->val[0] == '0') 

優化成了和一個數據0x3000000001比較的一條指令….

於是, 如上面所說, 因為這個str只有22個bytes, 當嘗試從16偏移處嘗試讀取8個字節的時候, 我們其實多讀了str結構體外面的3個字節…… 於是就invalid read了

問題清楚了, GCC聰明的優化, 引起的一個無害的報告(and 0xffffffffff)………… 於是, 白忙活了…. (當然, 最好還是修復掉, 我現在打算的修復就是, 最小也要分配一個24bytes).

发表评论

电子邮件地址不会被公开。 必填项已用*标注