国产成人精品三级麻豆,色综合天天综合高清网,亚洲精品夜夜夜,国产成人综合在线女婷五月99播放,色婷婷色综合激情国产日韩

當(dāng)前位置:首頁(yè) > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > Linux內(nèi)核的棧回溯與妙用

Linux內(nèi)核的;厮菖c妙用 時(shí)間:2019-04-02      來(lái)源:華清遠(yuǎn)見(jiàn)

linux內(nèi)核調(diào)試時(shí)Linux驅(qū)動(dòng)工程師的必備技能,當(dāng)內(nèi)核出現(xiàn)比較嚴(yán)重的錯(cuò)誤時(shí),比如Oops錯(cuò)誤或者內(nèi)核認(rèn)為系統(tǒng)運(yùn)行狀態(tài)異常,內(nèi)核就會(huì)打印出當(dāng)前進(jìn)程的;厮菪畔ⅲ渲邪(dāng)前執(zhí)行代碼的位置以及相鄰的指令、產(chǎn)生錯(cuò)誤的原因、關(guān)鍵寄存器的值以及函數(shù)調(diào)用關(guān)系等信息,這些信息對(duì)于調(diào)試內(nèi)核錯(cuò)誤非常有用。

示例:

注:本示例基于Linux-3.14.0的內(nèi)核,平臺(tái)為FS4412

首先人為制造內(nèi)核錯(cuò)誤,修改drivers/net/ethernet/davicom/dm9000.c,在dm9000_probe的函數(shù)中添加自己的信息,

比如,在 1450行,解析設(shè)備樹(shù)之后對(duì)申請(qǐng)到的資源手動(dòng)賦值為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); //手動(dòng)添加了三行打印信息

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; //手動(dòng)給申請(qǐng)到的資源的地址賦值為NULL

備注: 設(shè)備樹(shù)信息如下:

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];

};

};

那么接下來(lái)我們編譯內(nèi)核和設(shè)備樹(shù)然后拷貝啟動(dòng)內(nèi)核:

$ make uImage

$ make dtbs

系統(tǒng)啟動(dòng)時(shí)的內(nèi)核打印信息如下:

[ 5.075000] brd: module loaded

[ 5.085000] loop: module loaded

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

[ 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、自己添加的內(nèi)核打印信息位置:

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

[ 5.090000] db->data_res :0xee927e9c.

[ 5.095000] db->irq_res :0xee927eb8.

2、內(nèi)核Oops信息

空指針異常造成的錯(cuò)誤---很常見(jiàn)

[ 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、寄存器信息:關(guān)鍵PC指針的值

[ 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)

當(dāng)前異常時(shí)由于運(yùn)行在CPU0上的任務(wù)引發(fā)的異常。那么如果你希望快速定位錯(cuò)誤信息,那么只需要獲取PC指針?biāo)诘暮瘮?shù)和PC指針指向的地址就可以了,操作如下:

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

[ 5.115000] pc : [<c029bb7c>]

快速定位: 在Linux內(nèi)核的頂層目錄下有一個(gè)生成的未壓縮的內(nèi)核vmlinux,反匯編打開(kāi)它:

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

文件 vmlinux.dis 非常大打開(kāi)需要一定時(shí)間, 從反匯編代碼定位到 C 代碼并不會(huì)如此容易,需要有較強(qiáng)的閱讀匯編代碼的能力。 你加油。

另外一種方法是通過(guò) addr2line 去定位

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

$ arm-none-linux-gnueabi-addr2line -f -e vmlinux c029bb7c•

4、棧回溯信息

;厮菪畔⑹菑南峦峡矗

[ 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 ]---

盡可能引導(dǎo)讀者將;厮莸墓δ苡糜趯(shí)際項(xiàng)目調(diào)試,;厮莸墓δ芎軓(qiáng)大。

打印函數(shù)調(diào)用關(guān)系的函數(shù)就是dump_stack(),該函數(shù)不僅可以用在系統(tǒng)出問(wèn)題的時(shí)候,我們?cè)谡{(diào)試內(nèi)核的時(shí)候,可以通過(guò)dump_stack()函數(shù)的打印信息更方便的了解內(nèi)核代碼執(zhí)行流程。

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

隨著內(nèi)核啟動(dòng),先是系統(tǒng)相關(guān)的核心部分的初始化,比如CPU,時(shí)鐘,內(nèi)存等,然后不太重要的初始化操作放到rest_init的初始化當(dāng)中,后來(lái)開(kāi)啟內(nèi)核的第一個(gè)內(nèi)核線程kernel_init,在內(nèi)核初始化線程當(dāng)中會(huì)去加載Linux下的不同的段當(dāng)中的內(nèi)容,比如驅(qū)動(dòng)的初始化調(diào)用do_one_initcall,在驅(qū)動(dòng)被初始化時(shí),需要將驅(qū)動(dòng)添加到對(duì)應(yīng)的驅(qū)動(dòng)鏈表當(dāng)中driver_register,然后將驅(qū)動(dòng)放到對(duì)應(yīng)的總線上(當(dāng)前實(shí)例的dm9000屬于platform設(shè)備總線)bus_add_driver,然后由總線負(fù)責(zé)遍歷設(shè)備設(shè)備鏈表bus_for_each_dev,當(dāng)遍歷到設(shè)備時(shí),由__driver_attach函數(shù)負(fù)責(zé)將設(shè)備和驅(qū)動(dòng)進(jìn)行關(guān)聯(lián),驅(qū)動(dòng)端的probe函數(shù)指向,然后依次回調(diào)到對(duì)應(yīng)驅(qū)動(dòng)的probe函數(shù)dm9000_probe,這個(gè)過(guò)程是一個(gè)總的框架,那么問(wèn)題發(fā)生在dm9000_probe函數(shù)當(dāng)中,我們可以對(duì)dm9000_probe做進(jìn)一步深入的分析,比如借助dump_stack()函數(shù)。

dump_stack()函數(shù)的實(shí)現(xiàn)和系統(tǒng)結(jié)構(gòu)緊密相關(guān),本文介紹ARM體系中dump_stack()函數(shù)的實(shí)現(xiàn)。該函數(shù)定義在arch/arm/kernel/traps.c文件中,調(diào)用dump_stack()函數(shù)不需要添加頭文件,基本上在內(nèi)核代碼任何地方都可以直接使用該函數(shù)。

關(guān)鍵寄存器介紹:

寄存器含義

r0-r3用作函數(shù)傳參,例如函數(shù)A調(diào)用函數(shù)B,如果A需要向B傳遞參數(shù),則將參數(shù)放到寄存器r0-r3中,如果參數(shù)個(gè)數(shù)大于4,則需要借用函數(shù)的?臻g。

r4-r11變量寄存器,在函數(shù)中可以用來(lái)保存臨時(shí)變量。

r9(SB)靜態(tài)基址寄存器。

r10(SL)棧界限寄存器。

r11(FP)幀指針寄存器,通常用來(lái)訪問(wèn)函數(shù)棧,幀指針指向函數(shù)棧中的某個(gè)位置。

r12(IP)內(nèi)部過(guò)程調(diào)用暫存寄存器。

r13(SP)棧指針寄存器,用來(lái)指向函數(shù)棧的棧頂。

r14(LR)鏈接寄存器,通常用來(lái)保存函數(shù)的返回地址。

內(nèi)核中的函數(shù)棧 內(nèi)核中,一個(gè)函數(shù)的代碼最開(kāi)始的指令都是如下形式:

mov ip, sp

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

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

……

從其中兩條stmfd(壓棧)指令可以看出,一個(gè)函數(shù)的函數(shù)棧的棧底(高地址)的結(jié)構(gòu)基本是固定的,如下圖:

Linux內(nèi)核,linux內(nèi)核學(xué)習(xí)

首先我們約定被調(diào)用的函數(shù)稱為callee函數(shù),而調(diào)用者函數(shù)稱為caller函數(shù)。 在進(jìn)行函數(shù)調(diào)用的回溯時(shí),內(nèi)核中的dump_stack()函數(shù)需要做以下嘗試:

1、首先讀取系統(tǒng)中的FP寄存器的值,我們知道幀指針是指向函數(shù)棧的某個(gè)位置的,所以通過(guò)FP的值可以直接找到當(dāng)前函數(shù)的函數(shù)棧的地址。 2、得到當(dāng)前函數(shù)的代碼段地址,這個(gè)很容易,因?yàn)楫?dāng)前正在執(zhí)行的代碼(可通過(guò)PC寄存器獲得)就處在函數(shù)的代碼段中。在函數(shù)棧中保存了一個(gè)PC寄存器的備份,通過(guò)這個(gè)PC寄存器的值可以定位到函數(shù)的第一條指令,即函數(shù)的入口地址。 3、得到當(dāng)前函數(shù)的入口地址后,內(nèi)核中保存了所有函數(shù)地址和函數(shù)名的對(duì)應(yīng)關(guān)系,所以可以打印出函數(shù)名(詳見(jiàn)另一篇博客:內(nèi)核符號(hào)表的查找過(guò)程)。 4、在當(dāng)前函數(shù)的函數(shù)棧中還保存了caller函數(shù)的幀指針(FP寄存器的值),所以我們就可以找到caller函數(shù)的函數(shù)棧的位置。 5、繼續(xù)執(zhí)行2-4步,直到某個(gè)函數(shù)的函數(shù)棧中保存的幀指針(FP寄存器的值)為0或非法。 發(fā)生函數(shù)調(diào)用時(shí),函數(shù)棧和代碼段的關(guān)系如下圖所示:

dump_stack()函數(shù) 接下來(lái)我們就來(lái)看一下dump_stack()函數(shù)的實(shí)現(xiàn)。 dump_stack()主要是調(diào)用了下面的函數(shù)

c_backtrace(fp, mode);

兩個(gè)參數(shù)的含義為: fp : current進(jìn)程棧的fp寄存器。 mode: ptrace 用到的PSR模式,在這里我們不關(guān)心。dump_stack傳入的值為0x10。 這兩個(gè)參數(shù)分別賦值給r0, r1寄存器傳給c_backtrace()函數(shù)。 c_backtrace函數(shù)定義如下(arch/arm/lib/backtrace.S):

@ 定義幾個(gè)局部變量

#define frame r4

#define sv_fp r5

#define sv_pc r6

#define mask r7

#define offset r8

@ 當(dāng)前處于dump_backtrace函數(shù)的棧中

ENTRY(c_backtrace)

stmfd sp!, {r4 - r8, lr} @ 將r4-r8和lr壓入棧中,我們要使用r4-r8,所以備份一下原來(lái)的值。sp指向最后壓入的數(shù)據(jù)

movs frame, r0 @ frame=r0。r0為傳入的第一個(gè)參數(shù),即fp寄存器的值

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

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

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

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

@ 下面是一段和該函數(shù)無(wú)關(guān)的代碼,用來(lái)計(jì)算pc預(yù)取指的偏移,一般pc是指向下兩條指令,所以offset一般等于8

1: stmfd sp!, {pc} @ 存儲(chǔ)pc的值到棧中,sp指向pc。

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

adr r1, 1b @ r1 = 標(biāo)號(hào)1的地址,即指令 stmfd sp!, {pc} 的地址

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

/*

* Stack frame layout:

* optionally saved caller registers (r4 - r10)

* saved fp

* saved sp

* saved lr

* frame => saved pc @ frame即上面的fp,每個(gè)函數(shù)的fp都指向這個(gè)位置

* 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} //將pc壓棧的指令

*/

@ 函數(shù)主流程:開(kāi)始查找并打印調(diào)用者函數(shù)

for_each_frame: tst frame, mask @ Check for address exceptions

bne no_frame

@ 由sv_pc找到將pc壓棧的那條指令,因?yàn)檫@條指令在代碼段中的位置有特殊性,可用于定位函數(shù)入口。

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

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

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

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

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

1003: ldr r2, [sv_pc, #-4] @ if stmfd sp!, {args} exists, 如果在函數(shù)最開(kāi)始?jí)喝肓藃0-r3

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

teq r3, r2, lsr #10 @ 比較stmfd指令機(jī)器碼是否相同(不關(guān)注是否保存r0-r9),目的是判斷是否為stmfd指令

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

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

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

@ 打印r0地址對(duì)應(yīng)的符號(hào)名,傳給dump_backtrace_entry三個(gè)參數(shù):

@ r0:函數(shù)入口地址,

@ r1:返回值即caller中的地址,

@ 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

@ 打印保存在棧里的寄存器,這跟;厮輿](méi)關(guān)系,本文中不太關(guān)心

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的棧的高地址。

@ 由于參數(shù)的壓棧順序?yàn)閞3,r2,r1,r0,所以這里棧頂實(shí)際上是最后一個(gè)參數(shù)。

bleq .Ldumpstm @ dump saved registers

@ 打印保存在棧里的寄存器,這跟;厮輿](méi)關(guān)系,本文中不太關(guān)心

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

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

teq r3, r1, lsr #10

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

bleq .Ldumpstm @ dump saved registers,打印出來(lái)r4-r10

@ 對(duì)保存在當(dāng)前函數(shù)棧中的caller的fp做合法性檢查

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

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

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

cmp sv_fp, frame @ sv_fp-frame

mov frame, sv_fp @ frame=sv_fp

bhi for_each_frame @ cmp的結(jié)果,如果frame<sv_fp,即當(dāng)前fp小于caller的fp,則繼續(xù)循環(huán)

@ 這時(shí)frame指向caller棧的fp,由于函數(shù)中不會(huì)修改fp的值,所以這個(gè)fp肯定是指向caller保存的pc的位置的。

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

mov r1, frame

bl printk

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

ENDPROC(c_backtrace)

@ c_backtrace函數(shù)結(jié)束。

@ 將上面的代碼放到__ex_table異常表中。其中1001b ... 1006b是指上面的1001-1006標(biāo)號(hào)。

.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:

@ 用來(lái)判斷是否是stmfd sp!指令,并且參數(shù)包含fp, ip, lr, pc,不包含r10

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

@ 用來(lái)判斷是否是stmfd sp!指令,并且參數(shù)不包含r10, fp, ip, lr, pc

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

參考:https://blog.csdn.net/jasonchen_gbd/article/details/45585133

上一篇:C語(yǔ)言:if-else語(yǔ)句

下一篇:linux開(kāi)發(fā):Linux下查看端口占用

熱點(diǎn)文章推薦
華清學(xué)員就業(yè)榜單
高薪學(xué)員經(jīng)驗(yàn)分享
熱點(diǎn)新聞推薦
前臺(tái)專線:010-82525158 企業(yè)培訓(xùn)洽談專線:010-82525379 院校合作洽談專線:010-82525379 Copyright © 2004-2022 北京華清遠(yuǎn)見(jiàn)科技集團(tuán)有限公司 版權(quán)所有 ,京ICP備16055225號(hào)-5,京公海網(wǎng)安備11010802025203號(hào)

回到頂部