arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值

发布者:HarmonyJoy最新更新时间:2024-08-01 来源: cnblogs关键字:arm  汇编  C函数  返回值 手机看文章 扫描二维码
随时随地手机看文章

环境及代码介绍

环境和源码

由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程。这里不使用编译器自动生成的这部分汇编代码,因为编译器自动生成的代码会涉及环境变量的传递,参数的传递等等一系列问题。以ARM汇编来进行分析。使用一个启动汇编文件和一个main.c的文件,在ARM 2440板子上调试这段程序,使用JLinkExe借助jlink来调试:


init.s:



1 .text

2 .global _start

3 _start:

4         ldr sp,=4096  @设置堆栈指针以便调用C函数

5         bl main

6 loop:

7         b loop


main.c:


1 void main(void)

2 {

3 }


为什么main函数没有使用 int main(int argc,char **argv) 这种形式?因为我这里是使用的自己写的启动汇编文件,由它来完成从汇编到C代码的进入。


寄存器介绍  

ARM在任何一种模式下,都可以访问16个通用寄存器(R0-R15)和1-2个状态寄存器(CPSR,SPSR),只是有些寄存器是每种模式下都共用的(R0-R7),另外一些是同名但是使用的是不同硬件单元(其他,每种模式下有所不同)。这里的寄存器有些有特定用途:


    R15--PC:程序计数器,指向要取指的那条指令


    R14--LR:链接寄存器,保存发生跳转时,下一条指令的地址,方便使用BL跳回


    R13--SP:堆栈指针


    R12--IP:暂存SP值


    R11--FP: 保存堆栈frame的地址


    后面的IP, FP可能需要结合实际代码来理解。


另外,编译器在处理C程序的时候,R0通常用作传递返回值,R1-R4用来传递函数参数。


 


稍微解释下这段汇编代码的 ldr sp,=4096 ,为什么设置为4096?有2个原因:


    1.我这里使用的是nand启动,代码在内部4K SRAM里面执行。


    2.ARM压栈时采用的是满递减堆栈。


我觉得更准确的讲是由编译器决定的,其实ARM指令里面有各种类型的堆栈操作指令而不是单单的满递减。满递减就是指堆栈的增长方向向下,堆栈指针指向堆栈的顶端。如果是空递减,它会指向堆栈顶端的下一个地址,这个地址未存放有效堆栈数据。其实这里sp = 4096这个内存地址是无法访问的,4K最大的地址是4096-4,因此进行数据压栈时,要先调整堆栈指针,然后再压入数据,这也是所有满类型堆栈要遵循的原则。


反汇编分析压栈出栈      


使用 arm-linux-objdump -DS main.elf > dump 进行反汇编


 1 00000000 <_start>:

 2 .text

 3 .global _start

 4 _start:

 5         ldr sp,=4096

 6    0:    e3a0da01     mov    sp, #4096    ; 0x1000

 7         bl main

 8    4:    eb000000     bl    c

 9 

10 00000008 :

11 loop:

12         b loop

13    8:    eafffffe     b    8

14 

15 0000000c

:

16 void main(void)

17 {

18    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)

19   10:    e28db000     add    fp, sp, #0

20 }

21   14:    e28bd000     add    sp, fp, #0

22   18:    e8bd0800     pop    {fp}

23   1c:    e12fff1e     bx    lr


可以看到进入C函数第一步就是压栈操作,出C函数里面出栈操作,然后跳转返回。关于push,pop  ARM官方的文档给出的说明:


PUSH is a synonym for STMDB sp!, reglist and POP is a synonym for LDMIA sp! reglist. PUSH and POP are the preferred mnemonics in these cases.


仅仅是个别名而已,并且是针对sp寄存器进行操作。


由于我这里的main过于简单,所有并看不出说明名堂,在main中增加点东西:


1 int main(void)

2 {

3     int a;

4     a = 3;

6     return 0;

7 }


继续反汇编,只关注main:


 1 0000000c

:

 2 int main(void)

 3 {

 4    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)

 5   10:    e28db000     add    fp, sp, #0

 6   14:    e24dd00c     sub    sp, sp, #12

 7     int a;

 8     a = 3;

 9   18:    e3a03003     mov    r3, #3

10   1c:    e50b3008     str    r3, [fp, #-8]

11 

12     return 0;

13   20:    e3a03000     mov    r3, #0

14 }

15   24:    e1a00003     mov    r0, r3

16   28:    e28bd000     add    sp, fp, #0

17   2c:    e8bd0800     pop    {fp}

18   30:    e12fff1e     bx    lr


可以看到有一对互为逆向操作的指令组合 push {fp}; add fp, sp, #0 <-------> add sp, fp, #0;pop {fp},在这对组合指令之间的代码是不会去修改fp的值的,这样就实现了恢复调用前fp sp的值,而在它们之间的指令是通过修改sp来访问堆栈。但是这里有个问题,此处我仅定义了一个int型变量,为何堆栈向下偏移了12个字节?按道理sp-4即可。未找到原因,虽然对于堆栈,Procedure Call Standard for the ARM Architecture,要求遵守几个约定,比如堆栈指针必须是4字节对齐,此外,对于public interface即全局的接口,要求sp 8字节对齐。这里我的main算是个public interface,因此8字节对齐必须遵守,但是sp-4也是8字节对齐啊,搞不清为什么-12。增加局部变量可以很明细看出8字节对齐的约定。


传参   


 1 int foo(int a, int b, int c, int d)

 2 {

 3     int A,B,C,D;

 4     A = a;

 5     B = b;

 6     C = c;

 7     D = d;

 8 

 9     return 0;

10 }

11 void main(void)

12 {

13     int a;    

14     a = foo(1,2,3,4);

15 }


反汇编:


 1 00000000 <_start>:

 2 .text

 3 .global _start

 4 _start:

 5         ldr sp,=4096

 6    0:    e3a0da01     mov    sp, #4096    ; 0x1000

 7         bl main

 8    4:    eb000014     bl    5c

 9 

10 00000008 :

11 loop:

12         b loop

13    8:    eafffffe     b    8

14 

15 0000000c :

16 int foo(int a, int b, int c, int d)

17 {

18    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)

19   10:    e28db000     add    fp, sp, #0

20   14:    e24dd024     sub    sp, sp, #36    ; 0x24

21   18:    e50b0018     str    r0, [fp, #-24]

22   1c:    e50b101c     str    r1, [fp, #-28]

23   20:    e50b2020     str    r2, [fp, #-32]

24   24:    e50b3024     str    r3, [fp, #-36]    ; 0x24

25     int A,B,C,D;

26     A = a;

27   28:    e51b3018     ldr    r3, [fp, #-24]

28   2c:    e50b3014     str    r3, [fp, #-20]

29     B = b;

30   30:    e51b301c     ldr    r3, [fp, #-28]

31   34:    e50b3010     str    r3, [fp, #-16]

32     C = c;

33   38:    e51b3020     ldr    r3, [fp, #-32]

34   3c:    e50b300c     str    r3, [fp, #-12]

35     D = d;

36   40:    e51b3024     ldr    r3, [fp, #-36]    ; 0x24

37   44:    e50b3008     str    r3, [fp, #-8]

38 

39     return 0;

40   48:    e3a03000     mov    r3, #0

41 }

42   4c:    e1a00003     mov    r0, r3

43   50:    e28bd000     add    sp, fp, #0

44   54:    e8bd0800     pop    {fp}

45   58:    e12fff1e     bx    lr

46 

47 0000005c

:

48 void main(void)

49 {

50   5c:    e92d4800     push    {fp, lr}

51   60:    e28db004     add    fp, sp, #4

52   64:    e24dd008     sub    sp, sp, #8

53     int a;    

54     a = foo(1,2,3,4);

55   68:    e3a00001     mov    r0, #1

56   6c:    e3a01002     mov    r1, #2

57   70:    e3a02003     mov    r2, #3

58   74:    e3a03004     mov    r3, #4

59   78:    ebffffe3     bl    c

60   7c:    e1a03000     mov    r3, r0

61   80:    e50b3008     str    r3, [fp, #-8]

62 }

63   84:    e24bd004     sub    sp, fp, #4

64   88:    e8bd4800     pop    {fp, lr}

65   8c:    e12fff1e     bx    lr


可以看到参数通过R0-R3寄存器传递过去,函数里面将寄存器值压栈,要用时从栈里面取出值即可。当寄存器不够用时,总共超过4个字长度,就会通过堆栈传递了:


void main(void)

{

  64:    e92d4800     push    {fp, lr}

  68:    e28db004     add    fp, sp, #4

  6c:    e24dd010     sub    sp, sp, #16

    int a;    

    a = foo(1,2,3,4,5);

  70:    e3a03005     mov    r3, #5

  74:    e58d3000     str    r3, [sp]  @通过堆栈传递多出来的参数

  78:    e3a00001     mov    r0, #1

  7c:    e3a01002     mov    r1, #2

  80:    e3a02003     mov    r2, #3

  84:    e3a03004     mov    r3, #4

  88:    ebffffdf     bl    c

  8c:    e1a03000     mov    r3, r0

  90:    e50b3008     str    r3, [fp, #-8]

}

  94:    e24bd004     sub    sp, fp, #4

  98:    e8bd4800     pop    {fp, lr}

  9c:    e12fff1e     bx    lr


返回值好像也是通过寄存器或者堆栈传递。


关键字:arm  汇编  C函数  返回值 引用地址:arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值

上一篇:MMU 和 MPU的区别
下一篇:Jlink 软件断点和硬件断点

推荐阅读最新更新时间:2024-11-17 12:06

ARM更新中端产品线:A17打头阵
ARM处理器的世界,早已拥有了众多的型号,而今天,该公司公布了更强一点的Cortex-A17处理器内核,随之而来的还有一款新的视频处理器和显示控制器。不过,鉴于ARM提供了三种不同的Cortex-A系列CPU微架构,并且均面向于消费电子设备。而为了让大家了解Cortex-A17的定位,我们决定概述一下。 首先,是最小的Cortex-A7。该核心具有非常低的功耗要求,而ARM甚至还发布了基于A7的变种——Cortex-A53——并且兼容64位的ARM v8指令集架构。 其次,是位于两者中间的Cortex-A12。作为流行的Cortex-A9——这款32位内核支撑了铺天盖地的智能手机和平板电脑——的继任者,A12的到来
[物联网]
x86手机要来了!大神在Lumia 830上成功运行ARM版Win 10
对很多软粉来说,在手机上运行完整的Windows 10系统一直以来都是个梦想,传闻中能运行.exe程序的x86手机——Surface Phone就是很多流言和爆料的主题。 尽管可能至少要到今年年底才能看到微软的Windows 10手机,但已经有黑客大神提前向我们展示了可能性,成功在一款Lumia手机上跑上了桌面Windows 10系统。 前不久,最新版本的Windows Phone Internals软件已经可以支持在Lumia上安装任何ROM,于是就有人决定那桌面版的Windows试试看。 日前,国外大神Gustave M.在自己的Lumia 830上成功运行了Windows 10的ARM版本,这是我们第一次看到Lu
[手机便携]
B001-Atmega16-汇编-地址空间分配
地址空间规划 打开m16def.inc、可以看到如下面的定义,它和手册里面描述的FlashROM、SRAM、EEPROM等的地址空间一一对应。 ; ***** DATA MEMORY DECLARATIONS ***************************************** .equ FLASHEND = 0x1fff ; Note: Word address、这里一共是8K word的flash ROM .equ IOEND = 0x003f ; IO寄存器的地址空间是0x0000-0x003F、但它们被映射到0x0020-0x005F .equ SRAM_START = 0x0060
[单片机]
B001-Atmega16-<font color='red'>汇编</font>-地址空间分配
ARM单片机超声波监测预警系统电路设计
随着信息化、智能化、网络化的发展,嵌入式系统技术获得广阔的发展空间,工业控制领域也进行着一场巨大的变革,以32位高端处理器为平台的实时嵌入式软硬件技术将应用在工业控制的各个角落。嵌入控制器因其体积小、可靠性高、功能强、灵活方便等许多优点,其应用已深入到工业、农业、教育、国防、科研以及日常生活等各个领域,对各行各业的技术改造、产品更新换代、加速自动化化 进程、提高生产率等方面起到了极其重要的推动作用。 障碍物距离检测电路的设计 在本系统中超声波测距电路是由MICROCHIP的PIC16C57设计而成的,选用的超声波传感器是T/R40-16压电陶瓷传感器。在工作中,主控器PIC16C57发出信号使发射端的超声波换能器发出加以电压激
[电源管理]
<font color='red'>ARM</font>单片机超声波监测预警系统电路设计
基于ARM平台的心电信号处理系统设计
据统计,我国目前有县及县级以上医院1.3万家,医疗机械总数达17.5万台,加上一些专业心脏疾病治疗机构,我国目前每年心脏疾病的门诊量约在一千万人次以上。根据国家卫生部《全国卫生信息化发展规划纲要》的目标,在2010年要基本实现医院的数字化和信息化。所以未来医疗器械市场对新型医疗设备的市场空间巨大,特别是拥有数字化和信息化特征的心电信号处理系统具有广阔的应用前景和实用价值。本文就是介绍的一种基于ARM的心电信号处理系统设计。 系统总体设计 本文所介绍的系统的主要功能是对心电信号进行实时的处理和传输,系统原理框图如图1所示。 心电信号通过电极提取进入模拟处理模块,在模拟处理部分经过放大和滤波处理后,提高了信号的强度和
[单片机]
基于<font color='red'>ARM</font>平台的心电信号处理系统设计
分析师称设计问题影响ARM在服务器市场的普及
北京时间11月9日消息,据国外媒体报道,分析师指出,尽管Marvell半导体(以下简称“Marvell”)公布了一款采用ARM架构的服务器芯片,但客户接受度和设计问题将影响ARM蚕食英特尔、AMD市场份额的机会。 Marvell当地时间周一公布的四核芯片Armada XP采用ARM架构,使得ARM成为英特尔和AMD在服务器芯片市场上的直接竞争对手。 ARM向Marvell、德州仪器、三星和高通等芯片厂商许可处理器设计。大多数智能手机采用ARM架构芯片,ARM正在进军平板电脑和服务器芯片市场。自2008年以来,ARM曾多次表示要进军服务器市场,Marvell是最早公布ARM架构服务器芯片的公司之一。 市场研究公
[嵌入式]
痞子衡嵌入式:ARM Cortex-M文件那些事(1)- 源文件(.c/.h/.s)
  众所周知,嵌入式开发属于偏底层的开发,主要编程语言是C和汇编。所以本文要讲的source文件主要指的就是c文件和汇编文件。   尽管在平常开发中,我们都只会关注自己创建的.c/.h/.s源文件,但实际上我们不知不觉中也跟很多不是我们创建的源文件在打交道,那么问题来了,一个完整的嵌入式工程(以基于ARM Cortex-M控制器的工程为例)到底会包含哪些source文件呢?   现在就到了痞子衡的show time了,痞子衡将这些文件按来源分为五类十种,下面痞子衡按类别逐一分析这些文件: 第一类:Provided by Committee   第一类文件由C标准委员会提供,该类文件伴随着标准的发布而逐渐壮大。该类文件主
[单片机]
基于ARM控制器的渗炭炉温度控制系统的设计
渗碳过程工件质量主要取决于对温度的控制,当今市场中温度控制成型的产品均以单片机为控制器。由于一般单片机的速度比较慢,更重要的是其ROM和RAM空间比较小,不能运行较大程序,而基于多任务的操作系统需要的任务堆栈很多,需要的RAM空间很大,故其在发展上受到了很大限制。其欢在开发环境上,DSP需要开发用的仿真器,其价格比较贵,因此本设计排除了使用DSP。ARM系列的ARM7TDM1核嵌入式处理器目前应用得较多,价格比较低,性价比较好,还有免费的开发工具ARM SDT,再配以简单的JTAG仿真器,就可以运行嵌入式开发,因此本设计选用韩国三星公司的S3C44BOX芯片作为主控制器。 1 Samsung S3C4480X芯片简介 Samsu
[工业控制]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved