很詭異,竟然在delete 指標時候crash,這個很少見~~~分析這個問題費了不少周折,記錄在此。如能協助到他人,不勝開心。
拿到的運行時候堆棧包含如下部分:
.......
Back chain word : 183831f8
b930a150 [1003a150],CXC1734416%2_R1E ??:0 heap_free
Back chain word : 18383208
b92ff0dc [1002f0dc],CXC1734416%2_R1E ??:0 free
Back chain word : 18383218
ba161414 [10e91414],CXC1734416%2_R1E ??:0 operator delete
開始以為是對指標重複釋放,代碼走讀後發現排除此種可能。然後懷疑多線程記憶體同步有問題,後排除。
有同事懷疑是OS的問題,不過一般我不會懷疑OS的,比較OS一般都是很穩定的,而且這次OS沒有重大升級。為了排除這種可能,還是解碼了一下堆棧的頂部資訊
註:下面的資訊其實是在 heap_free之上,所以同事懷疑是OS問題
Stacktrace :
Using stackpointer : 0041b9cc
Back chain word : 097873b0
008136d8 [008136d8],CXC1729957_R81B13 ??:0 Cs_reportUnexpectedSignal_v
Back chain word : 097873f0
00828ac0 [00828ac0],CXC1729957_R81B13 /vobs/cello/cls_src/CLS_CRX90145_1/ERROR-MANAGER_CNX9011053_2/src/swu/error/error_handler.c:768 eman_install_appl_exten
Back chain word : 09787408
009d9f10 [009d9f10],CXC1729957_R81B13 ??:0 splaycmp_hunt_from
Back chain word : 09787448
009dc700 [009dc700],CXC1729957_R81B13 ??:0 zzkrn_get_fsem
Back chain word : 09787450
00a1d258 [00a1d258],CXC1729957_R81B13 ??:0 zzkrn_create_context
Back chain word : 09787460
00a1b414 [00a1b414],CXC1729957_R81B13 ??:0 zzkrn_create_context
Back chain word : 09787498
009b2514 [009b2514],CXC1729957_R81B13 ??:0 ose_hfmi_get_heap_reference
Back chain word : 097874c0
009b410c [009b410c],CXC1729957_R81B13 ??:0 zzheap_realloc
Back chain word : 09787500
009b910c [009b910c],CXC1729957_R81B13 ??:0 l_buddy_query_req
Back chain word : 09787540
009b4784 [009b4784],CXC1729957_R81B13 ??:0 buf_check_header
Back chain word : 097875a0
009b1bc8 [009b1bc8],CXC1729957_R81B13 ??:0 l_heap_error
Back chain word : 11c8ef68
00a1b414 [00a1b414],CXC1729957_R81B13 ??:0 zzkrn_create_context
Back chain word : 11c8ef70
009b11c8 [009b11c8],CXC1729957_R81B13 ??:0 ose_handle_create_heap
簡單推測,應該是delete拋出異常被OS捕獲,然後發出某種signal終止應用程式,由此應該不是OS的問題。既然不是OS的問題,還是從自身找原因:)
因為這是個必現的問題,單步跟蹤是個很好的方法。但由於程式跑在嵌入式OS上面,之前一直不太清楚怎麼單步,幸運的是終於有老外同事共用了單步的方法^_^
雖然編譯和上傳load module有點麻煩,沒辦法,誰讓單步比較好用呢。
設定斷點後單步跟蹤,設定指標為watch point,其實意義不大。單步觀察調用棧,果然如同之前設想,在解構函式中逐個調用各個attribute的解構函式。
在單步過程中還有一個插曲,就是inline 函數的調用。開始沒有注意是inline的,發現GDB對某些函數不能單步,直接嘗試GDB中運行會導致GDB收到SIGTRAP,異常退出,開始以為是這個地方的原因,仔細一看發現是inline函數。哎。。。重新單步跟蹤後,發現所有解構函式執行完成後程式crash,開始懷疑最後一個解構函式的地方。檢查後發現並沒有可以懷疑的地方,然後仔細檢查記憶體資料的內容終於發現端倪:
開始的資料:
{<EcXpuBoardEqmiD> = {<EcEcmiD> = {_vptr.EcEcmiD = 0xb9f580c0}, clientProcessId = {_vptr.EcClientProcessIdD = 0xb9f8b288,
static rtg_EcClientProcessIdD_fields = {{name = 0xb9f05814 "myValue", offset = 4, type = 0xb9f343d4, modifier = 0x0}}, myValue = 66851}, clientId = {
_vptr.EcClientIdD = 0xb9f8b298, static rtg_EcClientIdD_fields = {{name = 0xb9f05814 "myValue", offset = 4, type = 0xb9f343a4, modifier = 0x0}, {
name = 0xb9f0597c "valid", offset = 8, type = 0xb9f342e4, modifier = 0x0}}, myValue = 0, valid = true}}, result = 4294967295, auxPiuType = {myValue = 49152},
operState = 4294967295, availStatus = {static rtg_RbsAvailStatusCollectionD_fields = 0xb9f1e07c, myValue = 0}, admState = 4294967295, ledInfo = {
redLed = EQC_LED_NOT_APPLICABLE, greenLed = EQC_LED_NOT_APPLICABLE, yellowLed = EQC_LED_NOT_APPLICABLE, blueLed = EQC_LED_NOT_APPLICABLE}, errorCode = {myValue = 1},
hubPosition = {_vptr.BlibStringD = 0xb9f8c790, static rtg_BlibStringD_fields = {{name = 0xb9f1a040 "m_capacity", offset = 4, type = 0xb9f343a4, modifier = 0x0}, {
name = 0xb9f1a04c "m_contents", offset = 8, type = 0xb9f34314, modifier = 0xb9f1c64c}, {name = 0xb9f1a058 "m_size", offset = 12, type = 0xb9f343a4,
modifier = 0x0}}, m_capacity = 3, m_contents = 0x4085e680 "NA", m_size = 2, static emptystring = ""},
uniqueHwId = {uniqueHwIdLength = 65 'A',
uniqueHwId = "\000\0000ecMtrCdciTimer\000\022"}}
core dump前:
{<EcEcmiD> = {_vptr.EcEcmiD = 0xb9f8b200}, clientProcessId = {_vptr.EcClientProcessIdD = 0x0, static rtg_EcClientProcessIdD_fields = {{
name = 0xb9f05814 "myValue", offset = 4, type = 0xb9f343d4, modifier = 0x0}}, myValue = 0}, clientId = {_vptr.EcClientIdD = 0x0,
static rtg_EcClientIdD_fields = {{name = 0xb9f05814 "myValue", offset = 4, type = 0xb9f343a4, modifier = 0x0}, {name = 0xb9f0597c "valid", offset = 8,
type = 0xb9f342e4, modifier = 0x0}}, myValue = 0, valid = false}}, result = RBS_OK, auxPiuType = {myValue = 0}, operState = RBS_OPER_DISABLED, availStatus = {
static rtg_RbsAvailStatusCollectionD_fields = 0xb9f1e07c, myValue = 0}, admState = RBS_ADMIN_LOCKED, ledInfo = {redLed = EQC_LED_NOT_APPLICABLE,
greenLed = EQC_LED_NOT_APPLICABLE, yellowLed = EQC_LED_NOT_APPLICABLE, blueLed = EQC_LED_NOT_APPLICABLE}, errorCode = {myValue = 0}, hubPosition = {
_vptr.BlibStringD = 0x0, static rtg_BlibStringD_fields = {{name = 0xb9f1a040 "m_capacity", offset = 4, type = 0xb9f343a4, modifier = 0x0}, {
name = 0xb9f1a04c "m_contents", offset = 8, type = 0xb9f34314, modifier = 0xb9f1c64c}, {name = 0xb9f1a058 "m_size", offset = 12, type = 0xb9f343a4,
modifier = 0x0}}, m_capacity = 0, m_contents = 0x0, m_size = 0, static emptystring = ""}, uniqueHwId = {uniqueHwIdLength = 0 '\0',
uniqueHwId = '\0' <repeats 18 times>}}
難道uniqueHwId 是定長數組?檢查代碼後果然如此!!!看到uniqueHwId = "\000\0000ecMtrCdciTimer\000\022" ,推測應該是隨機值。在分配記憶體後,對資料進行賦值時使用長度65做終止條件,導致寫越界。後檢查在出問題的時候,uniqueHwId
確實沒有初始化,從而導致隨機值。分析道這裡,立刻響起Inside c++ object model裡面關於new/delete的深入說明,實際上編譯器會在分配的記憶體首位處加上格外的一些資料,比較這塊記憶體的大小,起始地址等其他必要資訊,而這些資訊就是給delete使用的,主要可以保證釋放記憶體。而且還可能在記憶體尾巴處填寫“保護資料”,一旦檢測到這些資料被擦寫,即認為有錯可以啟用異常或者其他錯誤機制。這樣的話,就可以解釋core dump的原因了,程式由于越界寫導致new的尾巴保護資料被擦寫,在delete時候拋出異常導致crash.
分析到這來,其實就是一個普通的越界寫的問題,但是由於掩蓋在new/delete的內幕之下,又多少帶了些神秘色彩。一般來說,越界寫會導致隨機crash,當出現這種情況時候很容易懷疑越界寫。但這次,卻只有在delete的時候crash,讓我忽略了忘這方面考慮。
總結:
一些看似複雜的問題其實只是小問題的組合,適當的排除,分解逐步細化問題,直至發現真相
不要輕易懷疑OS和成熟的library,寧肯多懷疑自己:)
注意積累(就像new/delete的原理一樣,還以為在這種高度整合的產品了不需要這些知識一樣,說不準以前覺得沒用的東西就會跳出來告訴你“我很有用的”)
多用google, 英文搜尋關鍵詞,呵呵~~