计算机ä¸å˜åœ¨çµå¼‚事件
å†æŠŠè¿™æ¡é“律拉出æ¥ä¸€æ¬¡ã€‚
今天碰到了一个bug,æœåŠ¡å™¨åœ¨è¿è¡Œæ—¶ä¼šcore dump在一个很çµå¼‚的地方,排除这个错误的过程,以åŠæœ€åŽå‘现的错误结果很具有典型性,所牵涉到的技术也很多,拿æ¥ä½œä¸ºLinux调试的课程挺好的。:-P
整个里é¢å‡è®¾è¯»è€…å·²ç»çŸ¥é“怎么用gdb,如果ä¸çŸ¥é“,请å‚è§GDB Manual
首先,很幸è¿çš„是,这个问题是å¯ä»¥å¾ˆå®¹æ˜“é‡çŽ°çš„,而且更é‡è¦çš„,有core dump。
拿到core dump之åŽï¼Œæƒ¯ä¾‹æ˜¯æŸ¥çœ‹ä¸€ä¸‹è°ƒç”¨æ ˆï¼šï¼ˆä¸ºäº†é¿å…泄æ¼å•†ä¸šç§˜å¯†ï¼Œæ‰€æœ‰å‡½æ•°å,文件å什么的都用foo啊,bar啊,foobar啊,blablaå•Šç‰ç‰ä»£æ›¿ï¼‰ã€‚
(gdb) bt
#0 0x000000eb in ?? ()
#1 0x3aa1d941 in ?? ()
#2 0x000001f8 in ?? ()
#3 0x080cf888 in foo (range=10000) at foo/foo.c:18
#4 0x080c1f29 in bar () at bar/bar.c:423
[....]
(gdb) info f 0
Stack frame at 0xbfc42548:
eip = 0xeb; saved eip 0x3aa1d941
called by frame at 0xbfc4254c
Arglist at 0xbfc42540, args:
Locals at 0xbfc42540, Previous frame's sp is 0xbfc42548
Saved registers:
eip at 0xbfc42544
(gdb) f 3
#3 0x080cf888 in foo (range=10000) at foo/foo.c:18
18 return ((u32)random()) % range;
相当的çµå¼‚ï¼Œæ ˆä¸Šçš„0,1,2都是,一个返回地å€æ€Žä¹ˆå¯èƒ½æ˜¯0x1f8,而且,core dumpçš„åŽŸå› æ˜¯å› ä¸ºeip跑飞到了0xeb。到frame 3的时候看起æ¥æ£å¸¸äº†ï¼Œä½†æ˜¯å‡ºé”™çš„地方在randomè¿™ç§ç®€å•çš„库函数上。ä¸è¿‡æ—¢ç„¶frame 3往下的部分都是好的,我们有ç†ç”±è®¤ä¸ºæ ˆå¹¶æ²¡æœ‰è¢«æžåã€‚å› ä¸ºGDBåœ¨æ˜¾ç¤ºè°ƒç”¨æ ˆçš„æ—¶å€™å¯èƒ½ä¼šæŠŠä¸€äº›ä¸æ£ç¡®çš„è°ƒç”¨æ ˆä¹Ÿæ˜¾ç¤ºå‡ºæ¥ï¼Œæˆ‘们干脆直接看内å˜ï¼š
(gdb) x/10w $esp
0xbfc42544: 0x3aa1d941 0x000001f8 0x080cf888 0x09582930
0xbfc42554: 0x0833f038 0xbfc42678 0x080c1f29 0x00002710
0xbfc42564: 0x0823747f 0xb7ca168c
çœ‹åŠ ç²—çš„éƒ¨åˆ†ï¼šè¿™é‡Œå°±æ˜¯frame 3返回地å€ï¼Œè€Œä¸Šé¢çš„东西,就是bt显示出æ¥çš„frame 1å’Œframe 2了,而frame 0就是当å‰çš„eip了:它跑飞到了0xeb。
GDBæžœç„¶åœ¨æ˜¾ç¤ºæ ˆçš„æ—¶å€™åšäº†æ‰‹è„šã€‚
OK,我们å汇编看看这个返回地å€ï¼Œåˆ°åº•å¹²äº†ä»€ä¹ˆï¼š
(gdb) disassemble 0x080cf888
Dump of assembler code for function _Z6foom:
0x080cf85c <_Z6foom+0>: push %ebp
0x080cf85d <_Z6foom+1>: mov %esp,%ebp
0x080cf85f <_Z6foom+3>: push %ebx
0x080cf860 <_Z6foom+4>: sub $0x4,%esp
[…]
0x080cf87a <_Z6foom+30>: movl $0x0,0xfffffff8(%ebp)
0x080cf881 <_Z6foom+37>: jmp 0x80cf893 <_Z6foom+55>
0x080cf883 <_Z6foom+39>: call 0x8051f90
0x080cf888 <_Z6foom+44>: mov $0x0,%edx
[…]
0x080cf899 <_Z6foom+61>: pop %ebx
0x080cf89a <_Z6foom+62>: pop %ebp
0x080cf89b <_Z6foom+63>: ret
End of assembler dump.
看起æ¥ä¹Ÿæ²¡å¹²å•¥ï¼Œç»§ç»çœ‹è°ƒç”¨çš„地å€æ˜¯å•¥å§ï¼š
(gdb) disassemble 0x8051f90
Dump of assembler code for function random@plt:
0x08051f90 : jmp *0x833f140
0x08051f96 : push $0x1f8
0x08051f9b : jmp 0x8051b90 <_init+24>
有点æ„æ€å•Šï¼Œrandom@plt,PLT是什么?PLT是Linux ELFæ ¼å¼å¯æ‰§è¡Œæ–‡ä»¶å½“ä¸çš„一个部分,称为Procedure Linkage Table。PLT是程åºä¸ºäº†å®žçŽ°Linux共享库的动æ€è¿Ÿç»‘定(Lazy Binding)而引入的一ç§æœºåˆ¶ã€‚关于 ELFæ ¼å¼ã€PLT,和下é¢è¦æ到的GOT(Global Offset Table)的资料,å¯ä»¥å‚è§ä¸‹é¢è¿™äº›é“¾æŽ¥ï¼š
The ELF Object File Format: Introduction
http://www.linuxjournal.com/article/1060
UNIX ELF File Format (PPT)
好了,言归æ£ä¼ ï¼Œå°±ç®—ä½ ä¸çŸ¥é“PLTï¼Œä½ ä¹Ÿå¯ä»¥ç»§ç»å¾€ä¸‹çœ‹ã€‚最土的办法,既然jump到一个地方,就看看这个地方是什么å§ã€‚
(gdb) p/x *0x833f140
$6 = 0x8051f96
显然这个é‡æ–°è·³å›žåˆ°äº†PLT当ä¸ï¼Œçœ‹ä¸‹é¢ï¼š
(gdb) disassemble 0x8051f96
Dump of assembler code for function random@plt:
0x08051f90 : jmp *0x833f140
0x08051f96 : push $0x1f8 // è¿™æ˜¯æ ˆä¸Šçš„ 0x1f8
0x08051f9b : jmp 0x8051b90 <_init+24>
åˆä¸€ä¸ªjmp,这次jmp到了什么地方呢?
(gdb) disassemble 0x8051b90
No function contains specified address.
è¿™æ˜¯æ€Žä¹ˆå›žäº‹ï¼Ÿå› ä¸ºæ²¡æœ‰å¯¹åº”çš„ç¬¦å·ï¼Œæˆ–者这一段代ç ä¸åœ¨.text里é¢ã€‚ä¸è¿‡æˆ‘们还å¯ä»¥ç”¨å…¶ä»–的方法看到指令:
(gdb) x/10i 0x8051b90
0x8051b90 <_init+24>: pushl 0x833f03c // è¿™æ˜¯æ ˆä¸Šçš„å¦ä¸€ä¸ªä¸œè¥¿çš„æ¥æº
0x8051b96 <_init+30>: jmp *0x833f040
0x8051b9c <_init+36>: add %al,(%eax)
0x8051b9e <_init+38>: add %al,(%eax)
åˆæœ‰ä¸€ä¸ªjmp,继ç»ã€‚
(gdb) p/x *0x833f040
$7 = 0xeb
æ€Žä¹ˆä¼šè¿™æ ·ï¼Ÿè¿™ä¸ªæŒ‡é’ˆè¢«è¦†å†™äº†ï¼ŒJump到了0xeb,然åŽå°±core了。
看08833f040这个地å€ï¼Œå±žäºŽGOT(Global Offset Table):
(gdb) x/10w 0x833f040
0x833f040 <_GLOBAL_OFFSET_TABLE_+8>: 0x000000eb 0x00000002 0x00000006 0xb7f4401c
0x833f050 <_GLOBAL_OFFSET_TABLE_+24>: 0x08051bd6 0xb7d8e9b0 0xb7d85260 0x08051c06
0x833f060 <_GLOBAL_OFFSET_TABLE_+40>: 0xb7d32ae0 0xb7da88d0
我们从GOT头上开始看:
(gdb) x/10w 0x833f040-8
0x833f038 <_GLOBAL_OFFSET_TABLE_>: 0x00000000 0x3aa1d941 0x000000eb 0x00000002
0x833f048 <_GLOBAL_OFFSET_TABLE_+16>: 0x00000006 0xb7f4401c 0x08051bd6 0xb7d8e9b0
0x833f058 <_GLOBAL_OFFSET_TABLE_+32>: 0xb7d85260 0x08051c06
ä»ç„¶è¯´Lazy Bindingç›¸å…³çš„ä¸œè¥¿ï¼Œå¦‚æžœä½ äº†è§£ELFçš„Lazy Bindingï¼Œä½ ä¼šçŸ¥é“GOTçš„å‰é¢å››ä¸ªå—是特殊的,而其ä¸çš„GOT+8的地方是_dl_runtime_resolve,而上é¢çš„最åŽä¸€ä¸ªjmp就是跳到这里。但是,这里å´å˜æˆäº†0xeb。
é”™è¯¯çš„åŽŸå› æ‰¾åˆ°äº†ï¼Œä½†æ˜¯ï¼Œç©¶ç«Ÿæ˜¯è°å¹²çš„?我们知é“gdb有一个很好的featureå«åšwatchpoint,这个在VS当ä¸è¢«å«åšData Breakpointã€‚å®ƒä»¬çš„æœ¬è´¨æ˜¯ä¸€æ ·çš„ï¼Œéƒ½æ˜¯é€šè¿‡ç¡¬ä»¶æ¥å®žçŽ°å¯¹æŸä¸ªåœ°å€çš„ç›‘æŽ§ï¼Œä½ å¯ä»¥è®¾å®šå½“è¿™å—内å˜è¢«è¯»ã€å†™ã€æˆ–者被访问(包括读写)的时候触å‘该æ–ç‚¹ã€‚å¾ˆå¥½ï¼Œæˆ‘ä»¬å°±åœ¨è¿™é‡ŒåŠ ä¸Šä¸€ä¸ªwatchpoint。注æ„watchpointçš„è¯æ³•ï¼Œä½ 需è¦åœ¨åœ°å€å‰é¢åŠ 一个*。
我们é‡æ–°å¯åŠ¨æœåŠ¡å™¨ï¼Œç„¶åŽattachåˆ°è¿™ä¸ªè¿›ç¨‹ï¼Œå¯¹å‡ºçŽ°é”™è¯¯çš„åœ°å€ 0x0833f040åŠ äº†watchpoint。为什么å¯ä»¥è¿™ä¹ˆåšï¼Ÿå› 为GOT在内å˜å½“ä¸çš„ä½ç½®æ˜¯å›ºå®šçš„,我们知é“ï¼Œæ— è®ºä½ ç¬¬å‡ æ¬¡å¯åŠ¨ä¸€ä¸ªç¨‹åºï¼ŒGOTéƒ½ä¼šè¢«åŠ è½½åˆ°åŒä¸€ä¸ªåœ°å€ã€‚
(gdb) watch *0x0833f040
Hardware watchpoint 2 added
然åŽç»§ç»æ‰§è¡Œ
(gdb) c
Continuing.
然åŽï¼Œå到一边å–茶去å§ï¼Œç‰å¾…ä½ çœ‹åˆ°gdb被æ–进去。
然åŽï¼Œç»ˆäºŽï¼Œç‰åˆ°äº†ã€‚
Hardware watchpoint 2: *137621568
Old value = -1208695104
New value = 235
CFoo::foobar (this=0xb1df5810) at foo/foobar.c:477
477 blablabla_func(0, sth->data.id, tmpData);
Current language: auto; currently c++
好了,我们看看这个时候的GOT:
(gdb) x/w 0x833f040
0x833f040 <_GLOBAL_OFFSET_TABLE_+8>: 0x000000eb
è¿™ä¸ªå€¼çš„çš„ç¡®ç¡®è¢«ä¿®æ”¹äº†ï¼Œé‚£ä¹ˆï¼Œæˆ‘ä»¬çœ‹çœ‹æ ˆã€‚
(gdb) bt
#0 CFoo::foobar (this=0xb1df5810) at foo/foobar.c:477
#1 0x0812fa69 in CFoo::doSomthing(this=0xb1df5810, action=0xbfb93378, event=0xbfb934d0) at foo/foobar.c:1100
[...]
#10 0x08067a60 in main (argc=0, argv=0x3aa1d95a) at server.c:1289
很好,这是我们的代ç ,过去看看:
对应代ç :
LPSOME_DATA_TYPE tmpData;
u64 some_array[MAX_SOME_ARRAY_SIZE];
size_t array_sz = MAX_SOME_ARRAY_SIZE;
[...]
tmpData->iRet = 0;
tmpData->iField01 = someVar;
tmpData->iField02 = SOME_CONSTRANT; // iField02çš„offset是8,也就是结构的第三个æˆå‘˜ã€‚
tmpData->iId = some->unit_id;
foobar(); // line 477
(gdb) p tmpData
0x833f038
OKï¼Œå› ä¸ºä»£ç å·²ç»è¢«å¤„ç†è¿‡ï¼Œæ‰€ä»¥é€»è¾‘å¯èƒ½çœ‹çš„ä¸æ˜¯å¾ˆæ¸…楚,但是很明显,在这里,问题在tmpData,这是一个指针(请å‚è§ç±»åž‹ï¼Œä½†æ˜¯å˜é‡å竟然没有任何的表明这个是个指针),没有åˆå§‹åŒ–就用了。éžå¸¸éžå¸¸çš„å·§åˆï¼Œè¿™ä¸ªå˜é‡æ‰€åœ¨çš„地方æ£å¥½æ˜¯GOT的地å€ï¼Œç„¶åŽï¼Œåœ¨å¯¹iField02å˜é‡èµ‹å€¼çš„时候,_dl_runtime_resolve的地å€è¢«è¦†ç›–掉了。
至æ¤ï¼Œå‡¶æ‰‹å·²ç»æ‰¾åˆ°äº†ï¼Œä½†æ˜¯è¿™ä¸ªç»™æˆ‘们什么å¯ç¤ºå‘¢ï¼Ÿ
计算机没有çµå¼‚事件。