fprintf函數調用出core

最近寫了一段代碼,程序core在了打日誌的地方,很詭異。

        LOG_FATAL("[log_id=%ld]sql exception throw, mysql error code = %d, exception %s",
                request.log_id,
                e.getErrorCode(),
                e.what());

LOG_FATAL這個宏是自己封裝的一個打日誌的函數,底層會調用log4cpp的接口,再底層當然就是調用fprintf,vfprintf等。
很普通的一個函數,在代碼運行到這裏時出core了。


#0  0x0049fe29 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#1  0x004c526b in vsnprintf () from /lib/i386-linux-gnu/libc.so.6
#2  0x081a08be in log4cpp::StringUtil::vform(char const*, char*) ()
#3  0x081942f6 in log4cpp::Category::_logUnconditionally(int, char const*, char*) ()
#4  0x081944bc in log4cpp::Category::logva(int, char const*, char*) ()
#5  0x08066b1a in utils::Logger::logva (level=0, stringFormat=0x81b2740 "insert coin earn record failed", arguments=@0xb6965a3c: 0xb6965a54 ",%\033\b|")
    at src/logger.cpp:86
#6  0x08066b93 in utils::Logger::fatal (stringFormat=0x81b2740 "insert coin earn record failed") at src/logger.cpp:96

後來自己排查了一下,覺得這個還蠻有意思的一個問題。

問題的原因在於,由於習慣於在64位的系統上寫代碼,所以習慣於認爲%ld就是打印一個64位的整形。
代碼中想用%ld打印64位的request.log_id,在64位的系統中沒問題。
但在32爲的系統中%ld表示long其實只是32位的整形。於是在格式化輸出的時候就出現了問題。
假設一個字母x表示一個32位,那麼代碼中的格式化輸出函數在調用的時候內存是這麼排的
x x x x
前兩個x表示第一個參數,第二個x表示第二個參數,第三個x表示第三個參數。
那麼由於%ld是32位的整形,於是在格式化輸出的時候,對於第一個輸出格式,第一個x被拿走了,
然後在輸出第二個格式的時候,拿第二個x,這個時候雖然結果不對,但是程序未必會crash。
但在輸出第三個格式的時候,由於第三個格式是字符串,本來應該取第四個x作爲字符串的首地址,
但是由於我們在打第一個格式的時候誤吧64位認爲是32位,導致第三個格式在輸出的時候沒法取到正確的地址值,
然後由於訪問這個非法地址,程序出core。


當然真正的棧幀情況未必就如我所說,但原理是這樣的。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章