棧存放的是什麼?
棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。描述的是函數的調用關係。
一般來說,arm linux中的棧幀有三種結構,取決於在編譯時所使用的編譯選項。
1. APCS標準結構
+——–+
| PC |-不是我們一般所說的cpu pc指針(也就是R15寄存器),僅僅是壓棧時候的pc=sp最後的地址。
+——–+
| LR |
+——–+
| SP |-----》往往是暫存的ip值,不是幀的sp
+——–+
| FP |
+——–+
| … |
+——–+
如果在編譯中使用了-mapcs選項,那麼函數調用中的棧幀就爲該結構。對應的彙編代碼如下:
00008458 :
8458: e1a0c00d mov ip, sp
845c: e92dd800 push {fp, ip, lr, pc}
r0-r3都是可選的,是用於傳遞函數前面四個參數。
r4-r10也是可選的,是編譯器根據具體情況(使用局部變量的數目),來決定使用那個寄存器,就保存那些寄存器的。
上圖對應如下的stack frame layout:
/*
*Stack frame layout:(地址從低到高)
* optionally saved caller registers (r4- r10)
* saved fp
* saved sp
* saved lr
* frame => saved pc
* optionally saved arguments (r0 - r3)
*saved sp => <next word>
*/
例如:AL850的KE異常爲例:
kernel棧的stack:
APCS的棧數據結構是:FD 滿遞減,所以高地址是棧首(也就是PC在高地址,順序自高到低就是PC,lr,ip,,,,,rxxx)
[ 278.554080]-(1)[861:Compiler]Modules linkedin:
[ 278.554098]-(1)[861:Compiler]CPU: 1 PID: 861Comm: Compiler Tainted: G W 3.10.48 #1
[ 278.554108]-(1)[861:Compiler]task: de1fa980ti: dd404000 task.ti: dd404000
[ 278.554118]-(1)[861:Compiler]PC is at update_curr+0x17c/0x1f0
[ 278.554126]-(1)[861:Compiler]LR is at 0x0
[ 278.554137]-(1)[861:Compiler]pc : [<c009ff1c>] lr : [<00000000>] psr: 80030193
[ 278.554137]sp : dd405e58 ip : 0000003f fp : dd405ea4
[ 278.554148]-(1)[861:Compiler]r10:155a5456 r9 : dd404018 r8 : de1fa9b8
[ 278.554158]-(1)[861:Compiler]r7 :00000000 r6 : 00002241 r5 : 00000001 r4 : 4d77d4d1
[ 278.554167]-(1)[861:Compiler]r3 :00000000 r2 : dd404000 r1 : 00000003 r0 : dd405e58
[ 278.554178]-(1)[861:Compiler]Flags: Nzcv IRQs off FIQs on Mode SVC_32 ISA ARM Segment user
[ 278.554187]-(1)[861:Compiler]Control:10c5383d Table: 5d91c06a DAC: 00000015
………………...
[861:Compiler]Stack: (0xdd405e58 to 0xdd406000)//從5e58(不把包括5e58)開始dump,也就是 5e59
因爲是32位機器,一個地址4個字節,所以每個地址都是+4,如下:
[861:Compiler]5e40: 5e44 5e48 5e4c 5e50 5e54 5e58 c009cb20 c00a2de8
[861:Compiler]5e60: 00013edf 00000000 00000000 0000003fde989240 de9dd9b8 de9dd9c0 c0cc9b38(R4)
[861:Compiler]5e80: c0ca3cc0(R5) de98f840(R6) c0cf9988(r7) de1fa980(r8) c18c5cc0(r9) de1fa9b8(sl) dd405f1c(fp) dd405ea8(ip)
[861:Compiler]5ea0: c00a3878(lr) c009fdac(pc) dd405ef4 dd405eb8 c0092634c0009278 dd405ed4 00000002
[861:Compiler]5ec0:00000000 dda7a000 dd405ef4 dda7a000 00000002 c0db1510 c0cc9b38 c0ca3cc0
[861:Compiler]5ee0:dd405efc dd405ef0 c08f787c c009872c dd405f1c de1fac8c 00000001 c0cc9b38
[861:Compiler]5f00: dd404020 de1fa980 c18c5cc0 00000047dd405f7c dd405f20 c08f6074(LR) c00a34b4(PC)
[861:Compiler]5f20:15595153 0000003f 00000001 00000001 c0ca3cc0 c0ca3cc0 c0ca3cc0 c0ca3cc0
[861:Compiler]5f40:c0ca3cc0 c0cc9240 c0ca3cc0 c0ca3cc0 c0e02848 dd404010 00000000 dd404000
[861:Compiler]5f60:dd405fb0 00000000 dd404000 00000047 dd405f8c dd405f80 c08f66ec c08f5f10
[861:Compiler]5f80:dd405fac dd405f90 c00124d8 c08f66b8 400f0764 60030010 f0222000 00000000
[861:Compiler]5fa0:00000000 dd405fb0 c000e840 c00124b0 00000001 00000000 0000001f 00000000
[861:Compiler]5fc0:403243ec 00000000 00000000 00000000 00000002 60e1b060 00000047 68a9a270
[861:Compiler]5fe0:40323ef4 60e1afe8 4031cb25 400f0764 60030010 ffffffff 00000000 00000000
[861:Compiler]Backtrace:
[861:Compiler][<c009fda0>] (update_curr+0x0/0x1f0) from[<c00a3878>](put_prev_task_fair+0x3d0/0x5b8)
[861:Compiler][<c00a34a8>](put_prev_task_fair+0x0/0x5b8) from [<c08f6074>](__schedule+0x170/0x7a8)
[861:Compiler][<c08f5f04>](__schedule+0x0/0x7a8) from [<c08f66ec>](schedule+0x40/0x80)
[861:Compiler][<c08f66ac>](schedule+0x0/0x80) from [<c00124d8>] (do_work_pending+0x34/0xac)
[861:Compiler][<c00124a4>](do_work_pending+0x0/0xac) from [<c000e840>] (work_pending+0xc/0x20)
[861:Compiler]r7:00000000 r6:f0222000 r5:60030010 r4:400f0764
下面進行棧回溯:
首先PC is at update_curr+0x17c/0x1f0 找到所在的函數幀:
:(使用標準的APCS),紅色部分代碼是壓棧的彙編指令代碼:
c009fda0<update_curr>:
c009fda0: e1a0c00d mov ip,sp ---》ip=sp
c009fda4: e92ddff0 push {r4,r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}這11 個寄存器都保存在棧中
c009fda8: e24cb004 sub fp,ip, #4 --->fp=ip-4
c009fdac: e24dd024 sub sp,sp, #36 ; 0x24---》sp = sp-36 擴展36個字節=9*4字節
c009fdb0: e52de004 push {lr} ;(str lr, [sp, #-4]!)
c009fdb4: ebfdbacd bl c000e8f0<__gnu_mcount_nc>
c009fdb8: e5908030 ldr r8,[r0, #48] ; 0x30
c009fdbc: e1a09000 mov r9,r0
c009fdc0: e590309c ldr r3,[r0, #156] ; 0x9c
c009fdc4: e3580000 cmp r8,#0
c009fdc8: e593a4b0 ldr sl,[r3, #1200] ; 0x4b0
c009fdcc: e593c4b4 ldr ip,[r3, #1204] ; 0x4b4
c009fdd0: 0a000027 beq c009fe74<update_curr+0xd4>
c009fdd4: e5980020 ldr r0,[r8, #32]
c009fdd8: e05a0000 subs r0,sl, r0
……
…..出棧指令:
c009ff84: 0affffc3 beq c009fe98<update_curr+0xf8>
c009ff88: eb215a85 bl c08f69a4<preempt_schedule>
c009ff8c: eaffffc1 b c009fe98<update_curr+0xf8>
因此知道//棧大小=11+9 --》20個字,且其dump stack的起始地址是0xdd405e58 ,所以從
[861:Compiler]5e40: 5e44 5e48 5e4c 5e50 5e54 5e58 c009cb20(起始) c00a2de8 ,數20個字(32bit)
則update_curr函數幀一直到[861:Compiler]5ea0: c00a3878(lr) c009fdac(pc)……...如上黑體部分爲update_curr函數的當前棧幀結構。大小是20個字節。從幀數據中得知 LR:c00a3878。
然後接着根據update_curr函數幀的c00a3878(lr) 去查看這個地址出於那個函數幀:(推算下來與Backtrace是對應的)
Grep -rin "c00a3878" vmlinux.dis
得知位於put_prev_task_fair的函數幀:
首先看該函數彙編,壓棧部分:
c00a34a8 <put_prev_task_fair>:(put_prev_task_fair+0x3d0/0x5b8)>>>>c00a34a8 ++0x3d0 等於update_curr裏面的lr:c00a3878
c00a34a8: e1a0c00d mov ip,sp
c00a34ac: e92ddff0 push {r4,r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
c00a34b0: e24cb004 sub fp,ip, #4
c00a34b4: e24dd04c sub sp,sp, #76 ; 0x4c
c00a34b8: e52de004 push {lr} ;(str lr, [sp, #-4]!)
c00a34bc: ebfdad0b bl c000e8f0<__gnu_mcount_nc>
c00a34c0: e291a038 adds sl,r1, #56 ; 0x38
c00a34c4: 0a0000ec beq c00a387c<put_prev_task_fair+0x3d4>
c00a34c8: e3097988 movw r7,#39304 ; 0x9988
put_prev_task_fair棧大小:76/4+11 =30個字。得出該函數put_prev_task_fair的函數幀:以上綠色部分。
因此得知該幀的lr=c08f6074
同樣道理,根據c08f6074,去查看該地址出於那個函數幀,依次循環。
問題:爲何“PC is atupdate_curr+0x17c/0x1f0 ”與 update_curr函數棧幀裏面的c009fdac(pc) 兩者地址不一樣的呢?
因爲》》
前者:PC is at update_curr+0x17c 是當前cpu在執行的指令地址,這個地址就是update_curr函數內的指令,取自:cpu的R15寄存器,代碼是: __show_regs(regs);
後者:是這個函數在壓棧時候的填充的pc值,往往指向sp,這個值是存在棧空間。不是我們一般所說的cpu pc指針(也就是R15寄存器)。