美文网首页
C函数在Cortex-M4处理器中的运行过程

C函数在Cortex-M4处理器中的运行过程

作者: 一只爱运动的猪 | 来源:发表于2018-11-01 13:35 被阅读0次

    最近离职了,找工作必然要经历面试。而每个面试官的考察,或者关心的点可能都不一样。有一个面试官很关心函数在处理器中的运行过程。当然,我这个问题没有回答好,所以才有了今天这篇文章了吧。如果有讲的不对的地方还希望多多指正!

    0x00 汇编基础语法

    PUSH和POP

    PUSH {R0, R4-R7, R9} ; 把 R0, R4, R5, R6, R7, R9 压入栈中(stack) 
    POP {R2, R3} ; 从栈(stack)中弹出数据到R2,R3
    

    使用PUSH和POP涉及到栈指针(SP)的相关操作。例如,PUSH是先让SP = SP - N,其中N是压入栈中的寄存器数据总长度。然后把想要存储的寄存器存储到SP指向的位置。
    而POP正好相反,先把栈内的数据弹出到相应寄存器,然后SP = SP + N。


    栈操作

    MOV和MOVS

    MOV指令主要用于内核的寄存器间的数据传递。MOVS与MOV类似,但是它会改变APSR中的标志位。

    MOV R4, R0;把R0拷贝到R4
    

    LDR和STR

    LDR和STR用于内存和寄存器之间的数据传递。LDR把数据从内存搬运到寄存器中,STR把数据从寄存器中搬运到内存中。

    LDR R0, [R1, #0x3] ; 从地址 R1+0x3读出32bit长度的数据,把它存储到R0中
    STR R0, [R1, #0x3] ; 把R0存储到地址 R1+0x3
    

    CBNZ

    CBNZ(Compare and Branch if NonZero),从字面意思理解可以看出他是比较然后不为零就跳转。

    CBNZ r0,0x080010B4
    

    如果r0不等于0,那么PC指针就跳转到0x080010B4。

    B和B.W

    跳转指令,如果跳转到的位置不超过+/-2KB,可以使用B。否则就要是用B.W这个32bit版本的指令。

    SUBS和MULS

    SUBS          r0,r4,#1
    

    r0 的r4 减去 #1

    MULS          r0,r4,r0
    

    把r0的等于r4乘以r0

    示例1-函数调用

    c函数编译成汇编

    //c函数
    void foo(int bar,int *baz)
    {
        char sink[4];
        short *why;
        why = (short *)(sink + 2);
        * why = 50;
    }
    void helloWorld(void)
    {
        int i = 4;
        foo(i,&i);
        return ;
    }
    
    ;汇编片段
    foo:
    0x080010BE B508      PUSH          {r3,lr}
    0x080010C0 4602      MOV           r2,r0
    0x080010C2 F10D0002  ADD           r0,SP,#0x02
    0x080010C6 2332      MOVS          r3,#0x32
    0x080010C8 8003      STRH          r3,[r0,#0x00]
    0x080010CA BD08      POP           {r3,pc}
    helloWorld:
    0x080010CC B508      PUSH          {r3,lr}
    0x080010CE 2004      MOVS          r0,#0x04
    0x080010D0 9000      STR           r0,[sp,#0x00]
    0x080010D2 4669      MOV           r1,sp
    0x080010D4 9800      LDR           r0,[sp,#0x00]
    0x080010D6 F7FFFFF2  BL.W          foo (0x080010BE)
    0x080010DA BD08      POP           {r3,pc}
    

    汇编分析

    helloWorld函数

    0x080010CC: 把r3和lr,压入栈中。压入lr用于函数返回,压入r3是为了开辟栈空间,用于保存局部变量i。此时的sp减少了8,因为压入了两个32bit的寄存器。
    0x080010CE: 把r0赋值为0x04
    0x080010D0: 把r0存到sp指向的内存地址。也就存到开辟的栈空间里。
    0x080010D2: 把sp赋值r1。r1作为foo(i,&i)的第二个参数&i。
    0x080010D4: 把sp指向内存的值赋值给r0,即把i赋值给r0。r0作为foo(i,&i)的第一个参数。
    0x080010D6: 跳转到函数foo,即汇编的位置0x080010BE
    0x080010DA: 弹出栈中一个值到r3,另一个栈中的lr值没有给lr寄存器,而是直接给了pc值,完成跳转。

    foo函数

    0x080010BE: 先把lr和r3保存在栈中,sp = sp - 8;lr用于函数返回,即跳转到位置0x080010DA但是实际的值是0x080010DB。这与处理器的流水线架构有关,编译器自动计算出了实际应该跳转到的位置。编译器只为sink[4]申请了栈空间,而没有给why申请空间。想必是认为没有必要,因为why最终是一个地址,且该地址是为了给sink赋值。
    0x080010C0: 把r0赋值给r2,即把参数i赋值给r2。
    0x080010C2: 把sp+2 赋值给r0。即把&sink[2]赋值给r0。这里为什么用了r0呢,r0难道不是用于保存参数i的么。这里编译器可能是知道在foo中并没有用到输入的参数,所以用于保存输入参数的r0被覆盖也没有关系。
    0x080010C6:赋值0x32给r3。
    0x080010C8:把r3存到r0指向的内存地址中。也就是把0x32存到&sink[2]的地址中。该指令没有用STR而是用了STRH。这和c代码中的强制转换(short*)有关。因为把&sink[2]转换成了两个字节的short地址类型,所以在存数据到&sink[2]用的STRH存的是半字(half word)即16bit。
    0x080010CA:把栈内的数据弹出到r3,把之前PUSH指令存储的链接地址(跳转到0x080010DA,但是实际的值是0x080010DB)直接弹出给PC寄存器,完成函数返回。栈指针SP = SP + 8,回收分配给foo函数的栈空间。函数接着从0x080010DA处运行。

    结尾

    示例1展示的是一个常规的函数调用过程,其中主要涉及到栈相关的操作。其实理解以后也并不是很难,但是却不是一个常用技能。面试过程中如果没有做这方面的准备,一时还是很难把问题把握好的。下一次将介绍递归的调用过程,尽力做一个小程序展现栈,寄存器的运行过程,这样能方便理解函数的运行过程。

    相关文章

      网友评论

          本文标题:C函数在Cortex-M4处理器中的运行过程

          本文链接:https://www.haomeiwen.com/subject/yefxxqtx.html