栈存放的是什么?
栈区(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 =>
*/
例如: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 : [
[ 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][
[861:Compiler][
[861:Compiler][
[861:Compiler][
[861:Compiler]r7:00000000 r6:f0222000 r5:60030010 r4:400f0764
下面进行栈回溯:
首先PC is at update_curr+0x17c/0x1f0 找到所在的函数帧:
:(使用标准的APCS),红色部分代码是压栈的汇编指令代码:
c009fda0
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
c009fdd4: e5980020 ldr r0,[r8, #32]
c009fdd8: e05a0000 subs r0,sl, r0
……
…..出栈指令:
c009ff84: 0affffc3 beq c009fe98
c009ff88: eb215a85 bl c08f69a4
c009ff8c: eaffffc1 b c009fe98
因此知道//栈大小=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
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
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寄存器)。