ARM汇编指令2 - 通过汇编分析C语言函数本质

一. 前言

        相信大家在刚刚学习C语言函数的时候,老师会跟我们讲,C语言函数在运行时,系统首先会给其创建一个栈,函数中的代码都是在栈中运行,当函数运行完,栈也会被销毁。以及C语言函数中的变量都是临时变量,函数退出后,这些变量不能再被访问等等。本文通过将C语言函数的汇编代码进行分析,学习C语言的本质。本文采用的开发板是S3C2440,参考的韦东山老师的案例。

二. 代码实现

1. 汇编代码 - start.S

.text
.global _start
_start:
	ldr sp, =4096
	
	bl main
	
halt:
	b halt

.text :代码首先通过.text定义了代码段。

_start :_start是汇编语言的入口,必须要有。

ldr sp, =4096 :将栈设置在4096的位置。

bl main :跳转到main函数运行,并将lr设置位置下一跳指令的地址。

2. C语言代码 - main.c

int main()
{
	unsigned int *pGPFCON = (unsigned int *)0x56000050;
	unsigned int *pGPFDAT = (unsigned int *)0x56000054;
	
	*pGPFCON = 0x100;
	
	*pGPFDAT = 0;
	
	return 0;
}

pGPFCON :指向内存0x56000050的位置。

pGPFDAT :指向内存0x56000054的位置。

*pGPFCON = 0x100 :在内存0x56000050位置写入0x100。

*pGPFDAT = 0 :在内存0x56000054位置写入0。

3. 代码编译

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 start.o led.o -o led.elf
	arm-linux-objcopy -O binary -S led.elf led.bin
	arm-linux-objdump -D led.elf > led.dis

clean:
	rm *.bin *.o *.elf

        led.c和start.S分别编译为led.o和start.o,led.o和start.o链接生成led.elf,led.elf通过objdump处理生成led.bin,led.bin可以烧写到板子上运行。通过objump反编译led.elf生成led.dis,接下来分析led.dis文件。

三. 反汇编代码分析

        反汇编代码led.dis文件内容如下,接下来通过图示一行行分析代码,体会C语言函数栈。

led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:	e3a0da01 	mov	sp, #4096	; 0x1000
   4:	eb000000 	bl	c <main>

00000008 <halt>:
   8:	eafffffe 	b	8 <halt>

0000000c <main>:
   c:	e1a0c00d 	mov	ip, sp
  10:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  14:	e24cb004 	sub	fp, ip, #4	; 0x4
  18:	e24dd008 	sub	sp, sp, #8	; 0x8
  1c:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  20:	e2833050 	add	r3, r3, #80	; 0x50
  24:	e50b3010 	str	r3, [fp, #-16]
  28:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  2c:	e2833054 	add	r3, r3, #84	; 0x54
  30:	e50b3014 	str	r3, [fp, #-20]
  34:	e51b2010 	ldr	r2, [fp, #-16]
  38:	e3a03c01 	mov	r3, #256	; 0x100
  3c:	e5823000 	str	r3, [r2]
  40:	e51b2014 	ldr	r2, [fp, #-20]
  44:	e3a03000 	mov	r3, #0	; 0x0
  48:	e5823000 	str	r3, [r2]
  4c:	e3a03000 	mov	r3, #0	; 0x0
  50:	e1a00003 	mov	r0, r3
  54:	e24bd00c 	sub	sp, fp, #12	; 0xc
  58:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:

00000000 <.comment>:
   0:	43434700 	cmpmi	r3, #0	; 0x0
   4:	4728203a 	undefined
   8:	2029554e 	eorcs	r5, r9, lr, asr #10
   c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}
  10:	Address 0x10 is out of bounds.

        反汇编代码的第一列是代码在内存的运行的位置,例如0;第二列是机器码,例如e3a0da01;第三列是汇编指令,例如mov    sp, #4096;第四列是注释,例如"; 0x1000"。

0:    e3a0da01     mov    sp, #4096    ; 0x1000

        将栈指针指针内存4096的位置。

4:    eb000000     bl    c <main>

        跳转到main函数运行,并将lr设置为下一条指令的地址8。lr = 8

c:    e1a0c00d     mov    ip, sp

        ip = sp = 4096

10:    e92dd800     stmdb    sp!, {fp, ip, lr, pc}

        stmdb是按寄存器的序号从高地址到低地址压入sp指向的内存的,!表示sp最终的值会改变。如下图所示。其中pc等于当前地址加8,所以pc = 18;lr = 8已知;ip = 4096已知;fp未知,sp会递减四次,sp = 4080。

14:    e24cb004     sub    fp, ip, #4    ; 0x4

        fp = ip - 4 = 4096 - 4 = 4092

18:    e24dd008     sub    sp, sp, #8    ; 0x8

        sp = sp - 8 = 4080 - 8 = 4072

1c:    e3a03456     mov    r3, #1442840576    ; 0x56000000

        r3 = 0x56000000

20:    e2833050     add    r3, r3, #80    ; 0x50

        r3 = r3 + 80 = 0x56000000 + 0x40 = 0x56000050

24:    e50b3010     str    r3, [fp, #-16]

        fp - 16 = 4092 - 16 = 4076,也就是将r3的值存入到内存4076。

28:    e3a03456     mov    r3, #1442840576    ; 0x56000000

        r3 = 0x56000000

2c:    e2833054     add    r3, r3, #84    ; 0x54

        r3 = 0x56000054

30:    e50b3014     str    r3, [fp, #-20]

        r3  => [ 4092 - 20 ] = [ 4072 ]

 34:    e51b2010     ldr    r2, [fp, #-16]

        [ 4092 - 16 ] = [ 4076 ] => r2 = 0x56000050

38:    e3a03c01     mov    r3, #256    ; 0x100

        r3 = 0x100

3c:    e5823000     str    r3, [r2]

        r3 = 0x100 => [r2] = [ 0x56000050 ],对应C语言代码*pGPFCON = 0x100;

40:    e51b2014     ldr    r2, [fp, #-20]

        [ fp - 20 ] = [ 4092 - 20 ] = [ 4072 ] => r2,r2 = 0x56000054

44:    e3a03000     mov    r3, #0    ; 0x0

        r3 = 0

48:    e5823000     str    r3, [r2]

        r3 = 0 => [ 0x56000054 ]

4c:    e3a03000     mov    r3, #0    ; 0x0

        r3 = 0

50:    e1a00003     mov    r0, r3

        r0 = r3 = 0

54:    e24bd00c     sub    sp, fp, #12    ; 0xc

        sp = fp - 12 = 4092 - 12 = 4080

58:    e89da800     ldmia    sp, {fp, sp, pc}        

         程序的pc指针为8,所以下一条执行的指令是 b    8 <halt>

四. 总结

        通过指定sp寄存器的值,为C语言函数准备栈,然后通过stmdb初始化栈,然后运行函数的处理逻辑,最后调用ldmia恢复栈,栈中的数据都是临时数据,恢复栈时,栈中的数据并不会保存下来。通过这个示例,可以更加深入的了解C语言的函数的本质。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值