分类: C/C++
2012-11-06 09:35:57
很诡异,竟然在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,开始怀疑最后一个析构函数的地方。检查后发现并没有可以怀疑的地方,然后仔细检查内存数据的内容终于发现端倪:
开始的数据:
{
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前:
{
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'
难道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, 英文搜索关键词,呵呵~~