栈回溯技术arm_v5t_le版
棧回溯技術arm_v5t_le版
From:韋東山?2007.04.03棧回溯技術及uClibc的堆實現原理.doc
1.??? 前言
段錯誤、非法地址訪問等問題導致程序崩潰的現象屢屢發生,如果能找到發生錯誤的函數,往往一眼就能看出BUG所在——對于這類比較簡單的問題,比如使用空指針進行讀寫等,利用棧回溯技術可以很快定位。但是對于數組溢出、內存泄漏等問題導致的程序錯誤,往往隱藏很深,它們并不當場發作,即使我們一步一步跟蹤到發生錯誤的語句時,也經常會讓人覺得“這個地方根本不可能出錯啊”——錯誤在很早以前就隱藏下來了,只不過是這個“不可能出錯的語句”觸發了它。了解棧的作用、堆的實現,可以讓我們腦中對程序的運行、函數的調用、變量的操作有個感官的了解,對解決這類問題會有所幫助。
?
???????關鍵詞:堆棧回溯堆實現棧作用
2.??? 棧的作用
一個程序包含代碼段、數據段、BSS段、堆、棧;其中數據段用來中存儲初始值不為0的全局數據,BSS段用來存儲初始值為0的全局數據,堆用于動態內存分配,棧用于實現函數調用、存儲局部變量。比如對于如下程序:
程序1 section.c
01 #include <stdlib.h>
02 #include <string.h>
03 #include <stdio.h>
04
05 int *g_pBuf;
06 int g_iCount = 10;
07
08 int main(int argc, char **argv)
09 {
10 ????char str[2];
11 ????g_pBuf = malloc(g_iCount);
12? ???printf("Address of main?????? = 0x%08x\n", (unsigned int)main);
13? ???printf("Address of g_pBuf???? = 0x%08x\n", (unsigned int)&g_pBuf);
14 ????printf("Address of g_iCount?? = 0x%08x\n", (unsigned int)&g_iCount);
15 ????printf("Address of malloc buf ?= 0x%08x\n", (unsigned int)g_pBuf);
16 ????printf("Address of local buf str ?= 0x%08x\n", (unsigned int)str);
17
18?? ??return 0;
19 }
使用如下命令編譯得到可執行文件section,反匯編文件section.dis:
arm_v5t_le-gcc? -o? section? section.c -static
arm_v5t_le-objdump -D section > section.dis
?
在PU S980上的linux環境下,這個程序的輸出結果為:
Address of main?????? = 0x000082b8
Address of g_pBuf???? = 0x00082998
Address of g_iCount?? = 0x00080bd0
Address of malloc buf? = 0x00083f80
Address of local buf str? = 0xbec80b36
?
????????其中main函數的地址為0x000082b8,它處于代碼段中;全局變量g_pBuf位于BSS段;全局變量g_iCount位于數據段;使用malloc分配出來的內存地址為0x00083f80,它位于堆中;局部變量str數組的開始地址為0xbec80b36,位于棧中。它們的分布圖示如下:
圖1?程序各段示意圖
?
棧的作用有二:
①?????保存調用者的環境——某些寄存器的值、返回地址
②?????存儲局部變量
?
現在通過一個簡單的例子來說明棧的作用:
程序2 call.c
01 #include <stdlib.h>
02 #include <string.h>
03 #include <stdio.h>
04
05 void A(int a);
06 void B(int b);
07 void C(int c);
08
09 void A(int a)
10 {
11???? printf("%d: A call B\n", a);
12???? B(2);
13 }
14
15 void B(int b)
16 {
17???? printf("%d: B call C\n", b);
18???? C(3);
19 }
20
21 void C(int c)
22 {
23???? printf("%d: function C\n", c);
24 }
25
26 int main(int argc, char **argv)
27 {??
28???? A(1);
29???? return 0;
30 }
?
使用如下命令編譯得到可執行文件call,反匯編文件call.dis:
arm_v5t_le-gcc -o? call? call.c? -static
arm_v5t_le-objdump -D call > call.dis
?
此程序的調用關系為main > A > B > C,現在來看看棧如何變化:
?
圖2?函數調用中棧的變化
?
???????上圖中,main、A、B、C四個函數的棧大小都是16字節,返回地址都存在棧偏移地址為16的地方。我們是如何知道這點的呢?需要閱讀反匯編代碼:
……
000082b8 <A>:
????82b8:?????? e92d4800??????? push??? {fp, lr}
??? 82bc:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????82c0:?????? e24dd008??????? sub???? sp, sp, #8????? ; 0x8
??? 82c4:?????? e50b0008??????? str???? r0, [fp, #-8]
??? 82c8:?????? e59f0014??????? ldr???? r0, [pc, #20]?? ; 82e4 <A+0x2c>
??? 82cc:?????? e51b1008??????? ldr???? r1, [fp, #-8]
??? 82d0:?????? eb0003f8??????? bl????? 92b8 <_IO_printf>
??? 82d4:?????? e3a00002??????? mov???? r0, #2? ; 0x2
??? 82d8:?????? eb000002??????? bl????? 82e8 <B>
??? 82dc:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
????82e0:?????? e8bd8800??????? pop???? {fp, pc}
??? 82e4:?????? 00064584??????? .word?? 0x00064584
?
000082e8 <B>:
????82e8:?????? e92d4800??????? push??? {fp, lr}
??? 82ec:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????82f0:?????? e24dd008??????? sub???? sp, sp, #8????? ; 0x8
??? 82f4:?????? e50b0008??????? str???? r0, [fp, #-8]
??? 82f8:?????? e59f0014??????? ldr???? r0, [pc, #20]?? ; 8314 <B+0x2c>
??? 82fc:?????? e51b1008??????? ldr???? r1, [fp, #-8]
??? 8300:?????? eb0003ec??????? bl????? 92b8 <_IO_printf>
??? 8304:?????? e3a00003??????? mov???? r0, #3? ; 0x3
??? 8308:?????? eb000002??????? bl????? 8318 <C>
??? 830c:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
????8310:?????? e8bd8800??????? pop???? {fp, pc}
??? 8314:?????? 00064594??????? .word?? 0x00064594
?
00008318 <C>:
????8318:?????? e92d4800??????? push??? {fp, lr}
??? 831c:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????8320:??? ???e24dd008??????? sub???? sp, sp, #8????? ; 0x8
??? 8324:?????? e50b0008??????? str???? r0, [fp, #-8]
??? 8328:?????? e59f000c??????? ldr???? r0, [pc, #12]?? ; 833c <C+0x24>
??? 832c:?????? e51b1008??????? ldr???? r1, [fp, #-8]
??? 8330:?????? eb0003e0?? ?????bl????? 92b8 <_IO_printf>
??? 8334:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
????8338:?????? e8bd8800??????? pop???? {fp, pc}
??? 833c:?????? 000645a4??????? .word?? 0x000645a4
?
00008340 <main>:
????8340:?????? e92d4800??????? push??? {fp, lr}
??? 8344:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????8348:?????? e24dd008??????? sub???? sp, sp, #8????? ; 0x8
??? 834c:?????? e50b0008??????? str???? r0, [fp, #-8]
??? 8350:?????? e50b100c??????? str???? r1, [fp, #-12]
8354:?????? e3a00001??????? mov???? r0, #1? ; 0x1
8358:?????? ebffffd6??????? bl????? 82b8 <A>
835c:?????? e3a03000??????? mov???? r3, #0? ; 0x0
8360:?????? e1a00003??????? mov???? r0, r3
8364:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
8368:???????e8bd8800??????? pop???? {fp, pc}
?
???????上面紅色的指令:
“push????{fp, lr}”表示將fp,lr?寄存器進行壓棧,同時?sp – 4×2,即向下移動?8?個字節。
“sub?????sp, sp, #8”表示將?sp?寄存器的值減?8,即向下移動?8?個字節。
“pop?????{fp, pc}”表示彈出棧頂值到?fp,?lr?寄存器,同時?sp+4×2,即向上移動?8?個字節。
???????局部變量也是存儲在棧中,當一個函數的局部變量越多,它的棧越大。
?
???????從上圖可知:棧中保存著函數的返回地址、局部變量等,那么我們可以從這些返回地址來確定函數的調用關系、調用順序。這就是下節介紹的棧回溯。
3.??? 棧回溯
將程序2稍加修改,見程序3第23、24、30、32四行:
程序3 stack.c
01 #include <stdlib.h>
02 #include <string.h>
03 #include <stdio.h>
04
05 void A(int a);
06 void B(int b);
07 void C(int c);
08
09 void A(int a)
10 {
11???? printf("%d: A call B\n", a);
12???? B(2);
13 }
14
15 void B(int b)
16 {
17???? printf("%d: B call C\n", b);
18???? C(3);
19 }
20
21 void C(int c)
22 {
23?????char *p = (char *)c;
24?????*p = ‘A’;
25???? printf("%d: function C\n", c);
26 }
27
28 int main(int argc, char **argv)
29 {? ?
30??????char a;
31???? A(1);
32?????C(&a);
33???? return 0;
34 }
?
???????第23、24兩行必然導致段錯誤而使得程序崩潰,linux內核當發現發生段錯誤時,會打印出棧信息。我們可以使用棧回溯的方法找到發生錯誤的原因。
使用如下命令編譯得到可執行文件stack,反匯編文件stack.dis:
?????? arm_v5t_le-gcc -g -o stack stack.c -static
?????? arm_v5t_le-objdump -dS stack > stack.dis
?
運行結果如下(注意:如果你是通過telnet來運行程序,可以使用dmesg命令看到這些棧信息):
/ # ./stack
1: A call B
2: B call C
<2>stack: unhandled page fault (11) at 0x00000003, code 0x817
<1>pgd = c3ca0000
<1>[00000003] *pgd=81b86031, *pte=00000000, *ppte=00000000
<4>
<4>Pid: 662, comm:??????????????? stack
<4>CPU: 0
<4>PC is at 0x8338
<4>LR is at 0x830c
<4>pc : [<00008338>]??? lr : [<0000830c>]??? Not tainted
<4>sp : be897af0? ip : 00000001? fp : be897b04
<4>r10: 00000000? r9 : 00008ac8? r8 : 00008b10
<4>r7 : 00000000? r6 : 00000000? r5 : 00081fe8? r4 : 00000000
<4>r3 : 00000041? r2 : 00000003? r1 : 00000000? r0 : 00000003
<4>Flags: nZCv? IRQs on? FIQs on? Mode USER_32? Segment user
<4>Control: 5317F
<4>Table: 83CA0000? DAC: 00000015
<4>usr stack info
<4>stack mem: sp = be897af0
4> be897b24 03000000 b4450600 03000000 147b89be 0000830c a4450600 02000000
?? 247b89be 000082dc 00000000 01000000 3c7b89be 00008370 647e89be 01000000
?? 00000000 108b0000 00000000 000085b4 00000000 647e89be 01000000 00008354
?? 00004c69 6e757800 000000
<4> be897b24 03be897b 0003be89 000003be 00000003 b4000000 45b40000 0645b400
<4> 000645b4 03000645 00030006 00000300 00000003 14000000 7b140000 897b1400
<4> be897b14 0cbe897b 830cbe89 00830cbe 0000830c a4000083 45a40000 0645a400
……
<4>Backtrace: [<000082fc>] (0x82fc) from [<be897b14>] (0xbe897b14)
<4>Backtrace aborted due to bad frame pointer <000645b4>
<4>Code: e51b3010 e50b3008 e51b2008 e3a03041 (e5c23000)
~ $
?
上面的藍色部分
<4>PC is at 0x8338
<4>LR is at 0x830c
表示導致崩潰的指令的地址為0x8338,返回地址為0x830c。一個典型的進程的地址空間為:
~ $ cat /proc/660/maps
00008000-00112000 r-xp 00000000 1f:07 27???????? /bin/busybox
0011a000-0011b000 rw-p 0010a000 1f:07 27???????? /bin/busybox
0011b000-001ce000 rwxp 0011b000 00:00 0????????? [heap]
40000000-40001000 rw-p 40000000 00:00 0
bea24000-bea4e000 rwxp bea24000 00:00 0????????? [stack]
?
linux-S980:/home/sanya/yjt/bt # arm_v5t_le-addr2line 0x8338 -f -e stack
C
/home/sanya/yjt/bt/stack.c:24
linux-S980:/home/sanya/yjt/bt #
?
結合此程序的反匯編程序進行回溯:
將PC is at 0x8338所在函數的部分反匯編代碼摘錄如下:
00008318 <C>:
?
void C(int c)
{
????8318:?????? e92d4800??????? push??? {fp, lr}
??? 831c:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????8320:???????e24dd010??????? sub???? sp, sp, #16???? ; 0x10
??? 8324:?????? e50b0010??????? str???? r0, [fp, #-16]
??? char *p = (char *)c;
??? 8328:?????? e51b3010??????? ldr???? r3, [fp, #-16]
??? 832c:?????? e50b3008??????? str???? r3, [fp, #-8]
??? *p = 'A';
??? 8330:?????? e51b2008??????? ldr???? r2, [fp, #-8]
??? 8334:?????? e3a03041??????? mov???? r3, #65 ; 0x41
PC->8338:???????e5c23000??????? strb??? r3, [r2]
??? printf("%d: function C\n", c);
??? 833c:?????? e59f000c??????? ldr???? r0, [pc, #12]?? ; 8350 <C+0x38>
??? 8340:?????? e51b1010??????? ldr???? r1, [fp, #-16]
??? 8344:?????? eb0003e3??????? bl????? 92d8 <_IO_printf>
}
??? 8348:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
??? 834c:?????? e8bd8800??????? pop???? {fp, pc}
8350:?????? 000645c4??????? .word?? 0x000645c4
?
下面對涉及的每個函數進行分析:
1.??函數C的堆棧:
??8318:?????? e92d4800??????? push??? {fp, lr}
? 831c:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
??8320:?????? e24dd010??????? sub???? sp, sp, #16???? ; 0x10
be897b24 03000000 b4450600 03000000 147b89be 0000830c?a4450600 02000000
247b89be 000082dc 00000000 01000000 3c7b89be 00008370 647e89be 01000000
00000000 108b0000 00000000 000085b4 00000000 647e89be 01000000 54830000
00004c69 6e757800 000000
?
可知 lr 為 0x0000830c,根據這個地址值,
linux-S980:/home/sanya/yjt/bt # arm_v5t_le-addr2line 0x830c -f -e stack
B
/home/sanya/yjt/bt/stack.c:19
linux-S980:/home/sanya/yjt/bt #
在stack.dis中可以再次找到這個地址處于函數B的范圍內。
?
2. 函數B的堆棧:
函數B的匯編指令如下:
void B(int b)
{
????82e8:???????e92d4800??????? push??? {fp, lr}
??? 82ec:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????82f0:???????e24dd008??????? sub???? sp, sp, #8????? ; 0x8
??? 82f4:?????? e50b0008??????? str???? r0, [fp, #-8]
??? printf("%d: B call C\n", b);
??? 82f8:?????? e59f0014??????? ldr???? r0, [pc, #20]?? ; 8314 <B+0x2c>
??? 82fc:?????? e51b1008??????? ldr???? r1, [fp, #-8]
??? 8300:?????? eb0003f4??????? bl????? 92d8 <_IO_printf>
??? C(3);
??? 8304:?????? e3a00003??????? mov???? r0, #3? ; 0x3
??? 8308:?????? eb000002??????? bl????? 8318 <C>
}
????830c:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
??? 8310:?????? e8bd8800??????? pop???? {fp, pc}
8314:?????? 000645b4??????? .word?? 0x000645b4
?
?
???????由
82e8:???????e92d4800??????? push??? {fp, lr}
??? 82ec:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
82f0:???????e24dd008??????? sub???? sp, sp, #8????? ; 0x8
可知函數B的堆棧大小為16字節,函數B執行完后的返回地址?lr?存儲在其堆棧偏移地址最外層。函數B棧的的數據緊挨著函數C的棧,取出羅列如下:
be897b24 03000000 b4450600 03000000 147b89be 0000830c?a4450600 02000000
247b89be 000082dc?00000000 01000000 3c7b89be 00008370 647e89be 01000000
00000000 108b0000 00000000 000085b4 00000000 647e89be 01000000 54830000
00004c69 6e757800 000000
從上面信息可以知道,函數B的返回地址lr 為?000082dc,根據這個地址值,
linux-S980:/home/sanya/yjt/bt # arm_v5t_le-addr2line 0x82dc -f -e stack
A
/home/sanya/yjt/bt/stack.c:13
從stack.dis可知處于函數A的地址范圍內。
?
3. 函數A的堆棧:
函數A的匯編指令如下:
void A(int a)
{
????82b8:?????? e92d4800??????? push??? {fp, lr}
??? 82bc:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????82c0: ??????e24dd008??????? sub???? sp, sp, #8????? ; 0x8
??? 82c4:?????? e50b0008??????? str???? r0, [fp, #-8]
??? printf("%d: A call B\n", a);
??? 82c8:?????? e59f0014??????? ldr???? r0, [pc, #20]?? ; 82e4 <A+0x2c>
??? 82cc:?????? e51b1008??????? ldr???? r1, [fp, #-8]
??? 82d0:?????? eb000400??????? bl????? 92d8 <_IO_printf>
??? B(2);
??? 82d4:?????? e3a00002??????? mov???? r0, #2? ; 0x2
??? 82d8:?????? eb000002??????? bl????? 82e8 <B>
}
????82dc:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
??? 82e0:?? ????e8bd8800??????? pop???? {fp, pc}
??? 82e4:?????? 000645a4??????? .word?? 0x000645a4
?
???????由
82b8:???????e92d4800??????? push??? {fp, lr}
??? 82bc:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????82c0:???????e24dd008??????? sub???? sp, sp, #8????? ; 0x8
可知函數A的堆棧大小為16字節,函數B執行完后的返回地址?lr?存儲在其堆棧偏移地址最外層。函數A棧的的數據緊挨著函數B的棧,取出羅列如下:
be897b24 03000000 b4450600 03000000 147b89be 0000830c?a4450600 02000000
247b89be 000082dc?00000000 01000000 3c7b89be 00008370?647e89be 01000000
00000000 108b0000 00000000 000085b4 00000000 647e89be 01000000 54830000
00004c69 6e757800 000000
???????從上面信息可以知道,函數A的返回地址為0x00008370,
linux-S980:/home/sanya/yjt/bt # arm_v5t_le-addr2line 0x8370 -f -e stack
main
/home/sanya/yjt/bt/stack.c:32
linux-S980:/home/sanya/yjt/bt #
從stack.dis可知處于函數main的地址范圍內。
?
???????至此,可以知道main調用A、A調用B、B再調用C時,在函數C中導致程序崩潰。現在認真看一下函數C:
21 void C(int c)
22 {
23?????char *p = (char *)c;
24?????*p = ‘A’;
25???? printf("%d: function C\n", c);
26 }
???????這個函數太過簡單,可以一眼就知道第23、24行代碼有問題。但是如果函數C有上千行代碼呢?除了睜大眼睛檢查C代碼外,我們還可以根據它的反匯編碼來差錯——內核打印出來的棧信息的前面還有些有用的信息:
<2>stack: unhandled page fault (11) at 0x00000003, code 0x817
<1>pgd = c3ca0000
<1>[00000003] *pgd=81b86031, *pte=00000000, *ppte=00000000
<4>
<4>Pid: 662, comm:??????????????? stack
<4>CPU: 0
<4>PC is at 0x8338
<4>LR is at 0x830c
<4>pc : [<00008338>]??? lr : [<0000830c>]??? Not tainted
<4>sp : be897af0? ip : 00000001? fp : be897b04
<4>r10: 00000000? r9 : 00008ac8? r8 : 00008b10
<4>r7 : 00000000? r6 : 00000000? r5 : 00081fe8? r4 : 00000000
<4>r3 : 00000041? r2 : 00000003? r1 : 00000000? r0 : 00000003
<4>Flags: nZCv? IRQs on? FIQs on? Mode USER_32? Segment user
<4>Control: 5317F
?
???????現在回過頭來看看函數C的反匯編碼,從中找出出錯的原因:
00008318 <C>:
?
void C(int c)
{
????8318:?????? e92d4800??????? push??? {fp, lr}
??? 831c:?????? e28db004??????? add???? fp, sp, #4????? ; 0x4
????8320:?????? e24dd010??????? sub???? sp, sp, #16???? ; 0x10
??? 8324:?????? e50b0010??????? str???? r0, [fp, #-16]
??? char *p = (char *)c;
??? 8328:?????? e51b3010??????? ldr???? r3, [fp, #-16]
??? 832c:?????? e50b3008??????? str???? r3, [fp, #-8]
??? *p = 'A';
??? 8330:?????? e51b2008??????? ldr???? r2, [fp, #-8]
??? 8334:?????? e3a03041??????? mov???? r3, #65 ; 0x41
????8338:?????? e5c23000??????? strb??? r3, [r2]
??? printf("%d: function C\n", c);
??? 833c:?????? e59f000c??????? ldr???? r0, [pc, #12]?? ; 8350 <C+0x38>
??? 8340:?????? e51b1010??????? ldr???? r1, [fp, #-16]
??? 8344:?????? eb0003e3??????? bl????? 92d8 <_IO_printf>
}
??? 8348:?????? e24bd004??????? sub???? sp, fp, #4????? ; 0x4
??? 834c:?????? e8bd8800??????? pop???? {fp, pc}
8350: ??????000645c4??????? .word?? 0x000645c4
?
???????出錯的指令為“8338:???????e5c23000??????? strb??? r3, [r2]”,它將寄存器r3的值存到地址(r2)中,只存儲1個字節。根據內核打印出來的寄存器值可知r3 : 00000041? r2 : 00000003,寫地址為0x03,當然<2>stack: unhandled page fault (11) at?0x00000003, code 0x817
——這不是可寫的地址。
???????閱讀匯編代碼是件困難的事情,沒有其他辦法時再用這方法吧。
總結
以上是生活随笔為你收集整理的栈回溯技术arm_v5t_le版的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python检测图像中的矩形_检测图像中
- 下一篇: kotlin_08:wlan直连/wif