处理器:栈结构

栈存放的是什么?

栈区(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>

 */

 

例如:AL850KE异常为例:

kernel栈的stack

APCS的栈数据结构是:FD 满递减,所以高地址是栈首(也就是PC在高地址,顺序自高到低就是PClr,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个字节。从帧数据中得知 LRc00a3878

 

然后接着根据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里面的lrc00a3878

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函数内的指令,取自:cpuR15寄存器,代码是:   __show_regs(regs);

后者:是这个函数在压栈时候的填充的pc值,往往指向sp,这个值是存在栈空间。不是我们一般所说的cpu pc指针(也就是R15寄存器)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值