• 环境及代码介绍

    • 环境和源码

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

init.s:

 .text
.global _start
_start:
ldr sp,=4096  @设置堆栈指针以便调用C函数
bl main
loop:
b loop

main.c:

 void main(void)
{
}

  为什么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?有2个原因:

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

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

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

    • 反汇编分析压栈出栈      

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

  <_start>:
.text
.global _start
_start:
ldr sp,=
: e3a0da01 mov sp, # ; 0x1000
bl main
: eb000000 bl c <main> <loop>:
loop:
b loop
: eafffffe b <loop> 0000000c <main>:
void main(void)
{
c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
: e28db000 add fp, sp, #
}
: e28bd000 add sp, fp, #
: e8bd0800 pop {fp}
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中增加点东西:

 int main(void)
{
int a;
a = ; return ;
}

  继续反汇编,只关注main:

 0000000c <main>:
int main(void)
{
c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
: e28db000 add fp, sp, #
: e24dd00c sub sp, sp, #
int a;
a = ;
: e3a03003 mov r3, #
1c: e50b3008 str r3, [fp, #-] return ;
: e3a03000 mov r3, #
}
: e1a00003 mov r0, r3
: e28bd000 add sp, fp, #
2c: e8bd0800 pop {fp}
: e12fff1e bx lr

可以看到有一对互为逆向操作的指令组合 push {fp}; add fp, sp, #0 <-------> add sp, fp, #;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字节对齐的约定。

    • 传参   
 int foo(int a, int b, int c, int d)
{
int A,B,C,D;
A = a;
B = b;
C = c;
D = d; return ;
}
void main(void)
{
int a;
a = foo(,,,);
}

  反汇编:

  <_start>:
.text
.global _start
_start:
ldr sp,=
: e3a0da01 mov sp, # ; 0x1000
bl main
: eb000014 bl 5c <main> <loop>:
loop:
b loop
: eafffffe b <loop> 0000000c <foo>:
int foo(int a, int b, int c, int d)
{
c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
: e28db000 add fp, sp, #
: e24dd024 sub sp, sp, # ; 0x24
: e50b0018 str r0, [fp, #-]
1c: e50b101c str r1, [fp, #-]
: e50b2020 str r2, [fp, #-]
: e50b3024 str r3, [fp, #-] ; 0x24
int A,B,C,D;
A = a;
: e51b3018 ldr r3, [fp, #-]
2c: e50b3014 str r3, [fp, #-]
B = b;
: e51b301c ldr r3, [fp, #-]
: e50b3010 str r3, [fp, #-]
C = c;
: e51b3020 ldr r3, [fp, #-]
3c: e50b300c str r3, [fp, #-]
D = d;
: e51b3024 ldr r3, [fp, #-] ; 0x24
: e50b3008 str r3, [fp, #-] return ;
: e3a03000 mov r3, #
}
4c: e1a00003 mov r0, r3
: e28bd000 add sp, fp, #
: e8bd0800 pop {fp}
: e12fff1e bx lr 0000005c <main>:
void main(void)
{
5c: e92d4800 push {fp, lr}
: e28db004 add fp, sp, #
: e24dd008 sub sp, sp, #
int a;
a = foo(,,,);
: e3a00001 mov r0, #
6c: e3a01002 mov r1, #
: e3a02003 mov r2, #
: e3a03004 mov r3, #
: ebffffe3 bl c <foo>
7c: e1a03000 mov r3, r0
: e50b3008 str r3, [fp, #-]
}
: e24bd004 sub sp, fp, #
: e8bd4800 pop {fp, lr}
8c: e12fff1e bx lr

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

void main(void)
{
: e92d4800 push {fp, lr}
: e28db004 add fp, sp, #c: e24dd010 sub sp, sp, #
int a;
a = foo(,,,,);
: e3a03005 mov r3, #
: e58d3000 str r3, [sp]  @通过堆栈传递多出来的参数
: e3a00001 mov r0, #c: e3a01002 mov r1, #
: e3a02003 mov r2, #
: e3a03004 mov r3, #
: ebffffdf bl c <foo>
8c: e1a03000 mov r3, r0
: e50b3008 str r3, [fp, #-]
}
: e24bd004 sub sp, fp, #
: e8bd4800 pop {fp, lr}
9c: e12fff1e bx lr

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

arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值的更多相关文章

  1. 初识python 函数(定义,传参,返回值)

    python基础(二): 菜鸟教程基础知识讲解的非常全面,内容选择我认为的重点输出一遍 函数: 定义一个函数: 你可以定义一个由自己想要功能的函数,以下是简单的规则: def fun(arg): pa ...

  2. day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...

  3. 论JS函数传参时:值传递与引用传递的区别

    什么是值传递:值传递是指在调用函数时将实际参数(实参)复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数. 值传递的总结:也就是说,将实参复制到函数中的这个过程叫值传递 什么是 ...

  4. 编写一个js函数,该函数有一个n(数字类型),其返回值是一个数组,该数组内是n个随机且不重复的整数,且整数取值范围是[2,32]

    首先定义个fn用来返回整数的取值范围: function getRand(a,b){ var rand = Math.ceil(Math.random()*(b-a)+a); return rand; ...

  5. string函数分析

    string函数分析string函数包含在string.c文件中,经常被C文件使用.1. strcpy函数原型: char* strcpy(char* str1,char* str2);函数功能: 把 ...

  6. 常用string函数分析

    string函数分析string函数包含在string.c文件中,经常被C文件使用.1. strcpy函数原型: char* strcpy(char* str1,char* str2);函数功能: 把 ...

  7. linux C函数之strdup函数分析【转】

    本文转载自:http://blog.csdn.net/tigerjibo/article/details/12784823 linux C函数之strdup函数分析 一.函数分析 1.函数原型: #i ...

  8. linux recv函数返回值分析

    函数原型: ssize_t recv(int sockfd, void *buf, size_t len, int flags); 该函数第一个参数制定接收端套接字描述符; 第二个参数指明一个缓冲区, ...

  9. arm汇编笔记

    ARM汇编(非虫笔记) 1.ARM汇编的目的: 分析elf文件的需要. 2.原生程序生成过程. (1)预处理,编译器处理c代码中的预处理指令. gcc -E hello.c -o hello.i (2 ...

随机推荐

  1. java集合-集合大家族

    在编写 Java 程序中,我们最常用的除了八种基本数据类型,String 对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!Java 中集合大家族的成员实在是太丰富了,有常用的 Array ...

  2. ASP.NET 多语言的实现(后台消息+前台消息+页面自动绑定)

    一 前言 界面支持多种语言,在使用ASP.NET自带的多语言方案时遇到下列问题: 在做管理类的功能时,有添加.修改和查看页面,需要支持多语言的控件基本相同,但要维护多处,产生冗余(ASP.NET有共享 ...

  3. Oracle EBS Form 发布到Server端的注意事项

    前段时间在本地XP系统上测试了一些整合javabean的Form例子,想着发布到服务器段去看看能否运行正常,一开始以为会和本地XP系统一样,部署到相关的目录下进行一些配置就可以了,但实际过程却和想象的 ...

  4. jQuery 制作逼真的日历翻转效果的倒计时

    在开发中,一些功能需要用到倒计时,例如最常见的活动开始.结束的倒计时.使用最流行的 JavaScript 库来制作这个效果很简单.下面就是一个 jQuery 制作的逼真的日历翻转效果的倒计时功能. 在 ...

  5. NodeJS API简介

    简单介绍下NodeJS现有API. Assert(断言):该模块用于编写程序的单元测试用例. Buffer(缓冲块) :处理二进制数据. C/C++ Addons(拓展):Addons插件就是动态连接 ...

  6. 高性能的JavaScript库---Lodash

    上周在仿做Nodejs社区的时候,遇到了lodash这个javascript库,很惭愧,那也是我第一次听说lodash.人嘛,对于新鲜的事物总是会或多或少感到些好奇的,于是就毫不犹豫地去lodash官 ...

  7. 用JS描述的数据结构及算法表示——栈和队列(基础版)

    前言:找了上课时数据结构的教程来看,但是用的语言是c++,所以具体实现在网上搜大神的博客来看,我看到的大神们的博客都写得特别好,不止讲了最基本的思想和算法实现,更多的是侧重于实例运用,一边看一边在心里 ...

  8. Kafka主要参数详解(转)

    原文档地址:http://kafka.apache.org/documentation.html ############################# System ############## ...

  9. 2016京东Android研发校招笔试题

    一.选择题汇总,具体的记不住啦.. 1.计网:ip的网络前缀.SNMP(报文组成):http://blog.csdn.net/shanzhizi/article/details/11606767 参考 ...

  10. View的onSaveInstanceState和onRestoreInstanceState过程分析

    为什么要介绍这2个方法呢?这是因为在我们的开发中最近遇到了一个很诡异的bug.大体是这样的:在我们的ViewPager中 有2页的root view都是ScrollView,我们在xml里面都用了an ...