美文网首页
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处理器中的运行过程

    最近离职了,找工作必然要经历面试。而每个面试官的考察,或者关心的点可能都不一样。有一个面试官很关心函数在处理器中的...

  • 第九章 内联函数

    简介:C++中预处理器宏存在的问题,在C++中如何用内联函数解决这些问题以及使用内联函数的方针和内联函数的工作机制...

  • 宏定义函数与内联函数

    宏定义函数 在c程序中,可以使用宏定义函数代替简单的函数,这样提高程序效率,预处理器用复制宏代码的方式代替函数调用...

  • C++ 面向对象

    C++ 与 C语言 区别 C++ 面向对象、标准特性; C 面向过程,函数+结构体 C++可以运行C语言,反之就不...

  • runtime总结

    runtime概念 一套比较底层的c语言API,包含了很多c语言函数,平时所写的OC代码,在运行过程中,其实转成了...

  • C++语言学习之面向对象

    1.C语言与C++语言的区别 C++面向对象 C 面向过程 函数+结构体 C++可以运行调用C语言 反之 C语言无...

  • Python3 & “if __name__=='__main_

    在Java、C、C++中,每次运行一个程序,都必须有一个主函数作为程序的入口,即main函数。下面HelloWor...

  • RunLoop 源码阅读

    获取runloop的函数 创建runloop的函数 运行runloop的函数 Runloop 运行的核心函数__C...

  • C++ 函数(3)

    1. 宏函数 宏函数的缺点: 宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。 在C++中,预处理器不允许...

  • Go语言 - Hello world

    与C语言类似,Go程序也是从main函数开始运行,但是这个main函数必须定义在main package中。(Go...

网友评论

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

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