久久婷婷香蕉热狠狠综合,精品无码国产自产拍在线观看蜜,寡妇房东在做爰3,中文字幕日本人妻久久久免费,国产成人精品三上悠亚久久

當前位置:首頁 > 嵌入式培訓 > 嵌入式學習 > 講師博文 > Linux內核的(de)棧回溯(su)與妙用

Linux內核的棧回溯與妙用 時間:2019-04-02   ;   來源(yuan):華清遠見

linux內核調(diao)試時Linux驅動工程(cheng)師的必備(bei)技能,當內核出(chu)現比較嚴(yan)重的錯(cuo)誤(wu)(wu)時,比如Oops錯(cuo)誤(wu)(wu)或者內核認為系統運行(xing)狀態(tai)異常,內核就會打印出(chu)當前進(jin)程(cheng)的棧回溯信息(xi),其中包含當前執行(xing)代碼的位置以(yi)及相鄰的指令(ling)、產生錯(cuo)誤(wu)(wu)的原因、關(guan)鍵寄存器(qi)的值(zhi)以(yi)及函數調(diao)用關(guan)系等信息(xi),這些信息(xi)對(dui)于調(diao)試內核錯(cuo)誤(wu)(wu)非常有用。

示例:

注:本(ben)示例基于(yu)Linux-3.14.0的內(nei)核,平臺為FS4412

首先人為制造內核錯誤,修改drivers/net/ethernet/davicom/dm9000.c,在dm9000_probe的(de)函(han)數(shu)中(zhong)添加自己的(de)信息,

比如,在 1450行,解(jie)析設備(bei)樹之后對申(shen)請到的資源手動賦(fu)值為NULL,如下:

1440 db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

1441 db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);

1442 db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

1443

1444 if (db->;addr_res == NULL || db->data_res == NULL ||

1445 db->irq_res == NULL) {

1446 dev_err(db->dev, "insufficient resources\n");

1447 ret = -ENOENT;

1448 goto out;

1449 }

 1450 printk("db->addr_res :%#x.\n",db->addr_res); //手動(dong)添加了(le)三行(xing)打印(yin)信息

1451 printk("db->data_res :%#x.\n",db->data_res);

 1452 printk("db->irq_res :%#x.\n",db->irq_res);

1453 db->addr_res = NULL; //手動(dong)給(gei)申請到(dao)的資源的地址賦值為(wei)NULL

備注(zhu): 設備樹信息如下:

srom-cs1@5000000 {

compatible = "simple-bus";

#address-cells = <1>;

#size-cells = <1>;

reg = <0x5000000 0x1000000>;

ranges;

ethernet@5000000 {

compatible = "davicom,dm9000";

reg = <0x5000000 0x2 0x5000004 0x2>;

interrupt-parent = <&gpx0>;

interrupts = <6 4>;

davicom,no-eeprom;

mac-address = [00 0a 2d a6 55 a2];

};

};

那么接下來我們編譯內(nei)核和(he)設(she)備樹然后(hou)拷貝(bei)啟動內(nei)核:

$ make uImage

$ make dtbs

系(xi)統啟動時的內核(he)打印信息如下:

[ 5.075000] brd: module loaded

[ 5.085000] loop: module loaded

[ 5.090000] db->addr_res :0xee927e80. //這(zhe)里是我們手(shou)動添加的打印信(xin)息,打印之后就是我們的內(nei)核Oops信(xin)息

[ 5.090000] db->data_res :0xee927e9c.

[ 5.095000] db->irq_res :0xee927eb8.

[ 5.100000] Unable to handle kernel NULL pointer dereference at virtual address 00000000

[ 5.105000] pgd = c0004000

[ 5.110000] [00000000] *pgd=00000000

[ 5.115000] Internal error: Oops: 5 [#1] PREEMPT SMP ARM

[ 5.115000] Modules linked in:

[ 5.115000] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 3.14.0 #15

[ 5.115000] task: ee8c0000 ti: ee8be000 task.ti: ee8be000

[ 5.115000] PC is at dm9000_probe+0x254/0x900

[ 5.115000] LR is at dm9000_probe+0x204/0x900

[ 5.115000] pc : [<c029bb7c>] lr : [<c029bb2c>] psr: a0000153

[ 5.115000] sp : ee8bfe50 ip : 00000003 fp : 00000000

[ 5.115000] r10: 00000000 r9 : c05f94d0 r8 : ee0a7150

[ 5.115000] r7 : ee9d0200 r6 : ee9d0210 r5 : eead5c80 r4 : eead5800

[ 5.115000] r3 : 00000000 r2 : 00000003 r1 : 00000000 r0 : fffffffa

[ 5.115000] Flags: NzCv IRQs on FIQs off Mode SVC_32 ISA ARM Segment kernel

[ 5.115000] Control: 10c5387d Table: 4000404a DAC: 00000015

[ 5.115000] Process swapper/0 (pid: 1, stack limit = 0xee8be240)

[ 5.115000] Stack: (0xee8bfe50 to 0xee8c0000)

[ 5.115000] fe40: ee0a6f78 00000001 c05f94d0 ee0a8048

[ 5.115000] fe60: 00000000 ee9d0210 c062a554 ee9d0210 00000000 c062a554 c05f94d0 c05c14fc

[ 5.115000] fe80: 00000000 c026b2e4 c026b2cc c067478c c062a554 c02699d0 ee9d0210 c062a554

[ 5.115000] fea0: ee9d0244 00000000 c05da4f4 c0269b6c c062a554 c0269ae0 00000000 c0268324

[ 5.115000] fec0: ee804c78 ee927dc0 c062a554 ee072780 c06286d8 c0269190 c0548ae0 c062a554

[ 5.115000] fee0: 00000000 c062a554 00000000 c05e5c74 c063a5c0 c026a184 00000000 ee8be000

[ 5.115000] ff00: 00000000 c00087b4 ee90ef00 c065f090 60000153 c0609c40 60000100 c0609c40

[ 5.115000] ff20: 00000000 00000000 c0609c3c 00000000 c0599df0 ef7fc8bd 0000009f c0034c6c

[ 5.115000] ff40: c0550640 c0599400 00000006 00000006 00000000 c05e5c90 c05e5c94 00000006

[ 5.115000] ff60: c05e5c74 c063a5c0 0000009f c05c14fc 00000000 c05c1c4c 00000006 00000006

[ 5.115000] ff80: c05c14fc c003e0dc 00000000 c040f808 00000000 00000000 00000000 00000000

[ 5.115000] ffa0: 00000000 c040f810 00000000 c000e4b8 00000000 00000000 00000000 00000000

[ 5.115000] ffc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

[ 5.115000] ffe0: 00000000 00000000 00000000 00000000 00000013 00000000 ff7fffff ffdfdfff

[ 5.115000] [<c029bb7c>] (dm9000_probe) from [<c026b2e4>] (platform_drv_probe+0x18/0x48)

[ 5.115000] [<c026b2e4>] (platform_drv_probe) from [<c02699d0>] (driver_probe_device+0x100/0x210)

[ 5.115000] [<c02699d0>] (driver_probe_device) from [<c0269b6c>] (__driver_attach+0x8c/0x90)

[ 5.115000] [<c0269b6c>] (__driver_attach) from [<c0268324>] (bus_for_each_dev+0x58/0x88)

[ 5.115000] [<c0268324>] (bus_for_each_dev) from [<c0269190>] (bus_add_driver+0xd8/0x1cc)

[ 5.115000] [<;c0269190>] (bus_add_driver) from [<c026a184>] (driver_register+0x78/0xf4)

[ 5.115000] [<c026a184>] (driver_register) from [<c00087b4>] (do_one_initcall+0x30/0x144)

[ 5.115000] [<c00087b4>] (do_one_initcall) from [<c05c1c4c>] (kernel_init_freeable+0xfc/0x1c8)

[ 5.115000] [<c05c1c4c>] (kernel_init_freeable) from [<c040f810>] (kernel_init+0x8/0xe4)

[ 5.115000] [<c040f810>] (kernel_init) from [<c000e4b8>] (ret_from_fork+0x14/0x3c)

[ 5.115000] Code: e59f1640 ebff2c17 e59434b4 e3a0a000 (e8930202)

[ 5.395000] ---[ end trace cbd2f1e374620c53 ]---

[ 5.400000] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b

[ 5.400000]

分析:

1、自己添加的(de)內核打印信息位置:

 [ 5.090000] db->addr_res :0xee927e80. //這里是(shi)我們手動(dong)添加的打印信息,打印之后就是(shi)我們的內核Oops信息

[ 5.090000] db->data_res :0xee927e9c.

[ 5.095000] db->irq_res :0xee927eb8.

2、內核Oops信息

空(kong)指針異常造成(cheng)的(de)錯誤(wu)---很常見

[ 5.100000] Unable to handle kernel NULL pointer dereference at virtual address 00000000

[ 5.105000] pgd = c0004000

[ 5.110000] [00000000] *pgd=00000000

[ 5.115000] Internal error: Oops: 5 [#1] PREEMPT SMP ARM

3、寄存(cun)器(qi)信(xin)息:關鍵(jian)PC指針(zhen)的值

[ 5.115000] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 3.14.0 #15

[ 5.115000] task: ee8c0000 ti: ee8be000 task.ti: ee8be000

[ 5.115000] PC is at dm9000_probe+0x254/0x900

[ 5.115000] LR is at dm9000_probe+0x204/0x900

[ 5.115000] pc : [<c029bb7c>] lr : [<c029bb2c>] psr: a0000153

[ 5.115000] sp : ee8bfe50 ip : 00000003 fp : 00000000

[ 5.115000] r10: 00000000 r9 : c05f94d0 r8 : ee0a7150

[ 5.115000] r7 : ee9d0200 r6 : ee9d0210 r5 : eead5c80 r4 : eead5800

[ 5.115000] r3 : 00000000 r2 : 00000003 r1 : 00000000 r0 : fffffffa

[ 5.115000] Flags: NzCv IRQs on FIQs off Mode SVC_32 ISA ARM Segment kernel

[ 5.115000] Control: 10c5387d Table: 4000404a DAC: 00000015

[ 5.115000] Process swapper/0 (pid: 1, stack limit = 0xee8be240)

當前異(yi)常時由于(yu)運行在CPU0上的(de)任務引(yin)發的(de)異(yi)常。那么(me)如(ru)果你希望快速定位錯誤信息,那么(me)只需要獲取PC指(zhi)(zhi)針(zhen)所在的(de)函數和PC指(zhi)(zhi)針(zhen)指(zhi)(zhi)向的(de)地址(zhi)就(jiu)可以(yi)了,操作如(ru)下(xia):

[ 5.115000] PC is at dm9000_probe+0x254/0x900

[ 5.115000] pc : [<c029bb7c>]

快(kuai)速定位: 在(zai)Linux內核(he)的(de)頂層目錄下有一(yi)個生成的(de)未壓縮的(de)內核(he)vmlinux,反匯編打開(kai)它:

arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis

文件 vmlinux.dis 非常大(da)打開需要一定(ding)時間, 從反匯編代碼定(ding)位到 C 代碼并不(bu)會如此容易,需要有較強的閱讀匯編代碼的能(neng)力。 你加油(you)。

另外一種(zhong)方法是通過 addr2line 去定(ding)位(wei)

參考鏈接://elinux.org/Addr2line_for_kernel_debugging

$ arm-none-linux-gnueabi-addr2line -f -e vmlinux c029bb7c&bull;

4、棧(zhan)回溯信息(xi)

棧回溯信息是(shi)從(cong)下(xia)往上看,

[ 5.115000] [<c029bb7c>] (dm9000_probe) from [<c026b2e4>] (platform_drv_probe+0x18/0x48)

[ 5.115000] [<c026b2e4>] (platform_drv_probe) from [<c02699d0>] (driver_probe_device+0x100/0x210)

[ 5.115000] [<c02699d0>] (driver_probe_device) from [<c0269b6c>] (__driver_attach+0x8c/0x90)

[ 5.115000] [<c0269b6c>] (__driver_attach) from [<c0268324>] (bus_for_each_dev+0x58/0x88)

[ 5.115000] [<c0268324>] (bus_for_each_dev) from [<c0269190>] (bus_add_driver+0xd8/0x1cc)

[ 5.115000] [<c0269190&gt;] (bus_add_driver) from [<c026a184>] (driver_register+0x78/0xf4)

 [ 5.115000] [<c026a184>] (driver_register) from [<c00087b4>] (do_one_initcall+0x30/0x144)

[ 5.115000] [<c00087b4&gt;] (do_one_initcall) from [<c05c1c4c>] (kernel_init_freeable+0xfc/0x1c8)

[ 5.115000] [<c05c1c4c>] (kernel_init_freeable) from [<c040f810>] (kernel_init+0x8/0xe4)

[ 5.115000] [<c040f810>] (kernel_init) from [<c000e4b8>] (ret_from_fork+0x14/0x3c)

[ 5.115000] Code: e59f1640 ebff2c17 e59434b4 e3a0a000 (e8930202)

[ 5.395000] ---[ end trace cbd2f1e374620c53 ]---

盡可能引導讀者將棧回溯的(de)功能用于實際(ji)項目調試,棧回溯的(de)功能很強(qiang)大(da)。

打印函(han)(han)數(shu)調用(yong)關(guan)系的(de)函(han)(han)數(shu)就是dump_stack(),該函(han)(han)數(shu)不(bu)僅可(ke)以用(yong)在系統(tong)出問題的(de)時候,我(wo)們在調試內核(he)的(de)時候,可(ke)以通過dump_stack()函(han)(han)數(shu)的(de)打印信息更方便(bian)的(de)了解內核(he)代碼執行流程(cheng)。

start_kernel-->rest_init--->kernel_thread(kernel_init,xxx)--->kernel_init--->kernel_init_freeable--->do_one_initcall--->driver_register--->bus_add_driver--->bus_for_each_dev--->__driver_attach--->driver_probe_device--->platform_drv_probe---->dm9000_probe

隨著內(nei)(nei)(nei)核(he)啟動(dong),先(xian)是系統相關(guan)的(de)(de)(de)核(he)心(xin)部(bu)分的(de)(de)(de)初(chu)(chu)始化(hua)(hua),比如CPU,時(shi)(shi)鐘,內(nei)(nei)(nei)存等(deng),然(ran)(ran)后(hou)不太(tai)重要的(de)(de)(de)初(chu)(chu)始化(hua)(hua)操作放到(dao)rest_init的(de)(de)(de)初(chu)(chu)始化(hua)(hua)當中(zhong),后(hou)來(lai)開啟內(nei)(nei)(nei)核(he)的(de)(de)(de)第一個(ge)內(nei)(nei)(nei)核(he)線(xian)(xian)(xian)(xian)程kernel_init,在(zai)內(nei)(nei)(nei)核(he)初(chu)(chu)始化(hua)(hua)線(xian)(xian)(xian)(xian)程當中(zhong)會去加載(zai)Linux下的(de)(de)(de)不同的(de)(de)(de)段當中(zhong)的(de)(de)(de)內(nei)(nei)(nei)容(rong),比如驅動(dong)的(de)(de)(de)初(chu)(chu)始化(hua)(hua)調用do_one_initcall,在(zai)驅動(dong)被初(chu)(chu)始化(hua)(hua)時(shi)(shi),需要將(jiang)驅動(dong)添加到(dao)對(dui)應的(de)(de)(de)驅動(dong)鏈(lian)表當中(zhong)driver_register,然(ran)(ran)后(hou)將(jiang)驅動(dong)放到(dao)對(dui)應的(de)(de)(de)總(zong)線(xian)(xian)(xian)(xian)上(當前實例的(de)(de)(de)dm9000屬于platform設(she)(she)(she)(she)備總(zong)線(xian)(xian)(xian)(xian))bus_add_driver,然(ran)(ran)后(hou)由總(zong)線(xian)(xian)(xian)(xian)負責遍歷(li)設(she)(she)(she)(she)備設(she)(she)(she)(she)備鏈(lian)表bus_for_each_dev,當遍歷(li)到(dao)設(she)(she)(she)(she)備時(shi)(shi),由__driver_attach函數(shu)負責將(jiang)設(she)(she)(she)(she)備和驅動(dong)進行關(guan)聯,驅動(dong)端的(de)(de)(de)probe函數(shu)指(zhi)向,然(ran)(ran)后(hou)依次回調到(dao)對(dui)應驅動(dong)的(de)(de)(de)probe函數(shu)dm9000_probe,這(zhe)個(ge)過程是一個(ge)總(zong)的(de)(de)(de)框架(jia),那么問題發生在(zai)dm9000_probe函數(shu)當中(zhong),我們可(ke)以對(dui)dm9000_probe做進一步深(shen)入(ru)的(de)(de)(de)分析(xi),比如借助dump_stack()函數(shu)。

dump_stack()函(han)數的(de)實現和系統結構緊密相關,本文(wen)介紹ARM體(ti)系中dump_stack()函(han)數的(de)實現。該函(han)數定義在arch/arm/kernel/traps.c文(wen)件(jian)中,調用dump_stack()函(han)數不需要添加頭(tou)文(wen)件(jian),基(ji)本上在內核代碼任何(he)地方(fang)都可以(yi)直接使用該函(han)數。

關鍵寄存器(qi)介紹(shao):

寄存器含義

r0-r3用(yong)作函(han)數傳參,例如函(han)數A調用(yong)函(han)數B,如果(guo)A需要(yao)向B傳遞(di)參數,則(ze)將參數放到寄存器r0-r3中,如果(guo)參數個(ge)數大于4,則(ze)需要(yao)借用(yong)函(han)數的棧空間。

r4-r11變(bian)量寄存器,在函(han)數中可(ke)以用來保存臨時(shi)變(bian)量。

r9(SB)靜態基(ji)址寄存器。

r10(SL)棧界(jie)限寄存器。

r11(FP)幀指(zhi)針寄存器,通常用來訪問(wen)函數棧(zhan),幀指(zhi)針指(zhi)向函數棧(zhan)中的某(mou)個位置。

r12(IP)內部過程調用暫存寄存器。

r13(SP)棧指針(zhen)寄存器(qi),用(yong)來指向函數棧的棧頂。

r14(LR)鏈(lian)接寄(ji)存器,通常用(yong)來保(bao)存函數的返回地址。

內核中的函數(shu)棧(zhan) 內核中,一個函數(shu)的代碼最(zui)開始的指(zhi)令都是如(ru)下形式:

mov ip, sp

stmfd sp!, {r0 - r3} (可選的)

stmfd sp!, {..., fp, ip, lr, pc}

……

從其中兩條stmfd(壓棧)指令(ling)可以(yi)看(kan)出(chu),一個函數的函數棧的棧底(高地(di)址)的結構基本是(shi)固定(ding)的,如(ru)下圖:

Linux內核,linux內核學習

首(shou)先我們約定被(bei)調(diao)用(yong)的函(han)(han)數稱為(wei)callee函(han)(han)數,而調(diao)用(yong)者函(han)(han)數稱為(wei)caller函(han)(han)數。 在進行函(han)(han)數調(diao)用(yong)的回溯時(shi),內核中的dump_stack()函(han)(han)數需要做(zuo)以下嘗試(shi):

1、首先讀取系(xi)統中(zhong)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)FP寄存(cun)(cun)(cun)器(qi)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)值(zhi),我(wo)們(men)知(zhi)道幀(zhen)指(zhi)針(zhen)是(shi)指(zhi)向函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)某個位(wei)置的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de),所以(yi)(yi)(yi)通(tong)過FP的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)值(zhi)可以(yi)(yi)(yi)直接找(zhao)到當前函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)地址(zhi)。 2、得(de)到當前函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)代(dai)(dai)碼(ma)(ma)段地址(zhi),這個很(hen)容易,因為(wei)(wei)當前正在執(zhi)行的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)代(dai)(dai)碼(ma)(ma)(可通(tong)過PC寄存(cun)(cun)(cun)器(qi)獲(huo)得(de))就處在函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)代(dai)(dai)碼(ma)(ma)段中(zhong)。在函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)中(zhong)保存(cun)(cun)(cun)了一個PC寄存(cun)(cun)(cun)器(qi)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)備份,通(tong)過這個PC寄存(cun)(cun)(cun)器(qi)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)值(zhi)可以(yi)(yi)(yi)定位(wei)到函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)第(di)一條指(zhi)令,即函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)入口地址(zhi)。 3、得(de)到當前函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)入口地址(zhi)后,內核(he)中(zhong)保存(cun)(cun)(cun)了所有函(han)(han)(han)(han)(han)數(shu)(shu)地址(zhi)和函(han)(han)(han)(han)(han)數(shu)(shu)名(ming)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)對應關系(xi),所以(yi)(yi)(yi)可以(yi)(yi)(yi)打印出函(han)(han)(han)(han)(han)數(shu)(shu)名(ming)(詳見另一篇博(bo)客:內核(he)符號表的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)查(cha)找(zhao)過程)。 4、在當前函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)中(zhong)還保存(cun)(cun)(cun)了caller函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)幀(zhen)指(zhi)針(zhen)(FP寄存(cun)(cun)(cun)器(qi)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)值(zhi)),所以(yi)(yi)(yi)我(wo)們(men)就可以(yi)(yi)(yi)找(zhao)到caller函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)位(wei)置。 5、繼續執(zhi)行2-4步,直到某個函(han)(han)(han)(han)(han)數(shu)(shu)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)中(zhong)保存(cun)(cun)(cun)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)幀(zhen)指(zhi)針(zhen)(FP寄存(cun)(cun)(cun)器(qi)的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)值(zhi))為(wei)(wei)0或非法。 發生函(han)(han)(han)(han)(han)數(shu)(shu)調用(yong)時,函(han)(han)(han)(han)(han)數(shu)(shu)棧(zhan)(zhan)和代(dai)(dai)碼(ma)(ma)段的(de)(de)(de)(de)(de)(de)(de)(de)(de)(de)關系(xi)如下(xia)圖所示(shi):

dump_stack()函(han)數 接下來我們就來看一下dump_stack()函(han)數的實現。 dump_stack()主要是調用了下面的函(han)數

c_backtrace(fp, mode);

兩個參數(shu)的(de)含義(yi)(yi)為(wei): fp : current進程(cheng)棧的(de)fp寄(ji)存器(qi)。 mode: ptrace 用到(dao)的(de)PSR模式,在(zai)這里我們不關心。dump_stack傳入的(de)值(zhi)(zhi)為(wei)0x10。 這兩個參數(shu)分別賦(fu)值(zhi)(zhi)給r0, r1寄(ji)存器(qi)傳給c_backtrace()函數(shu)。 c_backtrace函數(shu)定(ding)義(yi)(yi)如下(arch/arm/lib/backtrace.S):

@ 定義幾個局部變量

#define frame r4

#define sv_fp r5

#define sv_pc r6

#define mask r7

#define offset r8

@ 當前(qian)處于dump_backtrace函數的(de)棧中

ENTRY(c_backtrace)

stmfd sp!, {r4 - r8, lr} @ 將r4-r8和lr壓(ya)(ya)入棧中,我們(men)要使(shi)用r4-r8,所以備(bei)份一(yi)下(xia)原(yuan)來的值。sp指向最后壓(ya)(ya)入的數據

movs frame, r0 @ frame=r0。r0為傳入的(de)第一個參數(shu),即(ji)fp寄存器的(de)值

beq no_frame @ 如果frame為0,則退出

tst r1, #0x10 @ 26 or 32-bit mode? 判(pan)斷r1的bit4是否為0

moveq mask, #0xfc000003 @ mask for 26-bit 如果(guo)是,即r1=0x10,則mask=0xfc000003,即pc地址(zhi)只有低26bit有效,且末兩位(wei)為0

movne mask, #0 @ mask for 32-bit 如果不是,即r1!=0x10,則mask=0

@ 下面是一(yi)段和(he)該(gai)函數無關的代碼,用來計算pc預(yu)取(qu)指(zhi)的偏移,一(yi)般(ban)pc是指(zhi)向下兩條指(zhi)令,所以offset一(yi)般(ban)等(deng)于8

1: stmfd sp!, {pc} @ 存儲pc的(de)值到(dao)棧中(zhong),sp指(zhi)向pc。

ldr r0, [sp], #4 @ r0=sp的值(zhi),即剛剛存(cun)的pc的值(zhi)(將要執行的指令(ling)),sp=sp+4即還(huan)原sp

adr r1, 1b @ r1 = 標(biao)號1的地(di)址,即指令 stmfd sp!, {pc} 的地(di)址

sub offset, r0, r1 @ offset=r0-r1,即pc實際指向(xiang)的指令(ling)和讀取pc的指令(ling)之間的偏移

/*

* Stack frame layout:

* optionally saved caller registers (r4 - r10)

* saved fp

* saved sp

* saved lr

* frame => saved pc @ frame即上面(mian)的fp,每個函數的fp都(dou)指向這個位(wei)置

* optionally saved arguments (r0 - r3)

* saved sp => <next word>

*

* Functions start with the following code sequence:

* mov ip, sp

* stmfd sp!, {r0 - r3} (optional)

* corrected pc => stmfd sp!, {..., fp, ip, lr, pc} //將(jiang)pc壓棧的指令

*/

@ 函數主(zhu)流程:開始查(cha)找并(bing)打印調(diao)用者函數

for_each_frame: tst frame, mask @ Check for address exceptions

bne no_frame

@ 由sv_pc找到將(jiang)pc壓棧的(de)那條指令(ling),因為這條指令(ling)在代碼段中的(de)位置有特殊性,可用(yong)于定位函數入口(kou)。

1001: ldr sv_pc, [frame, #0] @ 獲取保存在(zai)callee棧(zhan)里的sv_pc,它指向callee的代(dai)碼段的某個(ge)位(wei)置

 1002: ldr sv_fp, [frame, #-12] @ get saved fp,這個fp就是caller的(de)fp,指(zhi)向caller的(de)棧中(zhong)某個位置

sub sv_pc, sv_pc, offset @ sv_pc減去offset,找到將pc壓棧的(de)那條指令,即上面注釋提到的(de)corrected pc。

bic sv_pc, sv_pc, mask @ mask PC/LR for the mode 清除sv_pc中mask為1的位,例如,mask=0x4,則清除sv_pc的bit2。

@ 定位函數的第一條(tiao)指令,即函數入口地址(zhi)

1003: ldr r2, [sv_pc, #-4] @ if stmfd sp!, {args} exists, 如果在函數最(zui)開始(shi)壓入(ru)了r0-r3

ldr r3, .Ldsi+4 @ adjust saved 'pc' back one. r3 = 0xe92d0000 >> 10

teq r3, r2, lsr #10 @ 比較stmfd指令機器碼是(shi)否相同(不(bu)關注是(shi)否保存(cun)r0-r9),目的(de)是(shi)判斷是(shi)否為(wei)stmfd指令

subne r0, sv_pc, #4 @ allow for mov: 如(ru)果sv_pc前面只有mov ip, sp

subeq r0, sv_pc, #8 @ allow for mov + stmia: 如果sv_pc前面有(you)兩條指令

@ 至此,r0為callee函(han)數的第一條指令的地址(zhi),即callee函(han)數的入口地址(zhi)

@ 打印r0地址對應的符(fu)號名,傳給(gei)dump_backtrace_entry三個參(can)數:

@ r0:函數(shu)入口(kou)地址,

@ r1:返回值即caller中的(de)地址(zhi),

@ r2:callee的fp

ldr r1, [frame, #-4] @ get saved lr

mov r2, frame

bic r1, r1, mask @ mask PC/LR for the mode

bl dump_backtrace_entry

@ 打印保存在棧里的寄(ji)存器,這跟棧回溯沒關(guan)系,本文(wen)中(zhong)不太關(guan)心(xin)

ldr r1, [sv_pc, #-4] @ if stmfd sp!, {args} exists, sv_pc前一條指令是否是stmfd指令

ldr r3, .Ldsi+4

teq r3, r1, lsr #10

ldreq r0, [frame, #-8] @ get sp。frame-8指向保存的IP寄存器,由于mov ip, sp,所以caller的sp=ip

@ 所以r0=caller的棧的低地址。

subeq r0, r0, #4 @ point at the last arg. r0+4就是callee的棧的高地址。

@ 由于參(can)數的壓棧(zhan)順序為r3,r2,r1,r0,所以(yi)這里棧(zhan)頂實際上是最后一(yi)個(ge)參(can)數。

bleq .Ldumpstm @ dump saved registers

@ 打印保(bao)存在棧(zhan)里的寄存器,這跟棧(zhan)回溯沒關系,本文中不太關心

1004: ldr r1, [sv_pc, #0] @ if stmfd sp!, {..., fp, ip, lr, pc}

ldr r3, .Ldsi @ instruction exists, 如果指(zhi)(zhi)令(ling)為frame指(zhi)(zhi)向的指(zhi)(zhi)令(ling)為stmfd sp!, {..., fp, ip, lr, pc}

teq r3, r1, lsr #10

subeq r0, frame, #16 @ 跳過fp, ip, lr, pc,即(ji)找到保(bao)存的(de)r4-r10

 bleq .Ldumpstm @ dump saved registers,打印出來r4-r10

@ 對保存(cun)在當前(qian)函(han)數棧中的caller的fp做合法性檢查

teq sv_fp, #0 @ zero saved fp means 判斷獲取的(de)caller的(de)fp的(de)值(zhi)

beq no_frame @ no further frames 如果caller fp=0,則(ze)停(ting)止循環

@ 更新frame變量指向caller函數棧的位置,將(jiang)上面(mian)注(zhu)釋中的Stack frame layout

cmp sv_fp, frame @ sv_fp-frame

mov frame, sv_fp @ frame=sv_fp

bhi for_each_frame @ cmp的結果(guo)(guo),如(ru)果(guo)(guo)frame<sv_fp,即當前fp小(xiao)于caller的fp,則(ze)繼(ji)續循環

@ 這時frame指向caller棧的(de)fp,由于函數(shu)中不(bu)會修(xiu)改fp的(de)值,所以這個fp肯定是指向caller保存的(de)pc的(de)位置的(de)。

1006: adr r0, .Lbad @ 否則就(jiu)打印(yin)bad frame提示

mov r1, frame

bl printk

no_frame: ldmfd sp!, {r4 - r8, pc}

ENDPROC(c_backtrace)

@ c_backtrace函數結束(shu)。

@ 將上(shang)面的代碼放(fang)到__ex_table異常表中。其中1001b ... 1006b是(shi)指上(shang)面的1001-1006標號。

.section __ex_table,"a"

.align 3

.long 1001b, 1006b

.long 1002b, 1006b

.long 1003b, 1006b

.long 1004b, 1006b

.previous

#define instr r4

#define reg r5

#define stack r6

@ 打印寄存器值

.Ldumpstm: stmfd sp!, {instr, reg, stack, r7, lr}

mov stack, r0

mov instr, r1

mov reg, #10

mov r7, #0

1: mov r3, #1

tst instr, r3, lsl reg

beq 2f

add r7, r7, #1

teq r7, #6

moveq r7, #1

moveq r1, #'\n'

movne r1, #' '

ldr r3, [stack], #-4

mov r2, reg

adr r0, .Lfp

bl printk

2: subs reg, reg, #1

bpl 1b

teq r7, #0

adrne r0, .Lcr

blne printk

ldmfd sp!, {instr, reg, stack, r7, pc}

.Lfp: .asciz "%cr%d:%08x"

.Lcr: .asciz "\n"

.Lbad: .asciz "Backtrace aborted due to bad frame pointer <%p>\n"

.align

.Ldsi:

@ 用來判(pan)斷是否是stmfd sp!指令,并且(qie)參數包(bao)含(han)fp, ip, lr, pc,不包(bao)含(han)r10

.word 0xe92dd800 >> 10 @ stmfd sp!, {... fp, ip, lr, pc}

@ 用來判斷是(shi)否是(shi)stmfd sp!指(zhi)令(ling),并且參數不(bu)包含r10, fp, ip, lr, pc

.word 0xe92d0000 >> 10 @ stmfd sp!, {}

參(can)考://blog.csdn.net/jasonchen_gbd/article/details/45585133

上一篇:C語言:if-else語句

下一篇:linux開發:Linux下查看端口占用

熱點文(wen)章(zhang)推薦
華清學員就業榜(bang)單
高薪學員經驗分享
熱點新聞(wen)推薦
前臺專線:010-82525158 企業培(pei)訓洽談專(zhuan)線(xian):010-82525379 院校(xiao)合作(zuo)洽談專線:010-82525379 Copyright © 2004-2022 北京華清遠見科技集團有限公司 版權所有 ,,京公海網安備11010802025203號

回到頂部