APCS – ARM汇编指令(五)

  • Post author:
  • Post category:其他


ARM procedure call standard(APCS),ARM过程调用标准提供了紧凑编写历程的一种机制,方便C语言和汇编或者其他类型的语言之间相互进行调用。基本的ATPCS规则包括寄存器的使用规则、数据栈使用规则、参数传递规则。

1. APCS规则

1.1. 寄存器的使用规则

ARM处理器中有r0 – r15共16个寄存器,寄存器用途给定约定的使用规则,并给这些寄存器命名了相应的别名。寄存器具体的使用规则总结:

  • 子程序通过寄存器r0 ~ r3来传递参数,这是可以使用他们的别名a0 ~ a3,被调用的子程序返回时无需回复r0 ~ r3的内容。函数返回值可以使用r0寄存器来完成。
  • r4 ~ r10寄存器来保存局部变量,如果子程序中大量使用这些寄存器,在子函数中调用子函数也大量使用这些寄存区,这个时候这些局部变量在函数调用时也需要和r11 ~ r15一起压栈。
  • r11 ~ r15寄存器用作函数栈调用,分别去了不同的名字完成特定的功能。

Reg # 寄存器

APCS别名
意义与使用规则

R0

a1
工作寄存器,函数调用参数

R1

a2

R2

a3

R3

a4

R4

v1
必须保护

R5

v2

R6

v3

R7

v4

R8

v5

R9

v6

R10

sl
栈限制,数据栈限制指针,局部变量寄存器

R11

fp
桢指针,局部变量信息获取靠这个指针计算移动

R12

ip
子程序内部调用,用来保存当前函数的栈的最顶端地址

R13

sp
栈指针,当前函数栈的最低位置地址

R14

lr
连接寄存器,保存函数调用函数下一条指令地址,用于返回

R15

pc
程序计数器,记录程序当前执行地址

ATPCS规则寄存器命令与功能说明表

1.2. 数据栈使用规则

数据栈有两个增长方向:向内存地址减小的方向增长时,称为descending栈;向内存地址增加的方向增长时,称为ascending栈。所谓数据栈的增长就是移动栈指针。当栈指针指向栈顶元素(最后一个入栈的数据)时,称为FULL栈;当栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元时,称为empty栈。综合这两个点,数据栈共有4种组合:

  • FD: full descending,满递减
  • ED:empty descending,空递减
  • FA: full ascending,满递增
  • EA: empty ascending,空递减

APCS规定数据栈为FD栈类型,并且对数据栈的操作是8字节对齐的,使用stmdb/ldmia批量内存访问指令来操作FD数据栈。使用stmdb命令往数据占中保存内容时,先递减sp指针,在保存数据,使用ldmia命令从数据栈中恢复数据时,先获取数据,在递增sp指针,sp指针总是指向栈顶元素,这刚好是FD栈的定义。

2. C代码调用分析

代码结构首先通过start.S函数进行启动引导,然后跳转到C代码处理函数中。在汇编引导函数中,必须根据自己实际的硬件情况指定sp寄存器的栈地址,这个地址保证数据是可以实现存取,主要是用来进行压栈和临时变量的存取功能。

.text
.global _start

_start:
	/* 设置内存: sp 栈 */
	ldr sp, =4096  /* nand启动 */
//	ldr sp, =0x40000000+4096  /* nor启动 */

	/* 调用main */
	bl main

halt:
	b halt
int copy_code_to_sdram(unsigned char *buf, unsigned long start_addr, int size)
{
	int ret = 0;
	int i = 0;
	if (0 != buf)
	{
		for (i = 0; i < size; i++)
			*buf = *((char *)start_addr);
	}

	return ret;
}

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

	unsigned int *SDRAM_BASE = (unsigned int *)0x30000000;
	/* 配置GPF4为输出引脚 */
	*pGPFCON = 0x100;

	/* 可执行文件内容拷贝 */
	const long start_addr = 0x0;
	const int file_size = 1024;
	int ret = copy_code_to_sdram((char *)SDRAM_BASE, start_addr, file_size);
	if (ret != 0)
		return ret;

	/* 设置GPF4输出0 */
	*pGPFDAT = 0;

	return 0;
}

产生的汇编代码


led.elf:     file format elf32-littlearm

Disassembly of section .text:

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

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

0000000c <copy_code_to_sdram>:
   c:	e1a0c00d 	mov	ip, sp
  10:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  14:	e24cb004 	sub	fp, ip, #4	; 0x4
  18:	e24dd014 	sub	sp, sp, #20	; 0x14
  1c:	e50b0010 	str	r0, [fp, #-16]
  20:	e50b1014 	str	r1, [fp, #-20]
  24:	e50b2018 	str	r2, [fp, #-24]
  28:	e3a03000 	mov	r3, #0	; 0x0
  2c:	e50b301c 	str	r3, [fp, #-28]
  30:	e3a03000 	mov	r3, #0	; 0x0
  34:	e50b3020 	str	r3, [fp, #-32]
  38:	e51b3010 	ldr	r3, [fp, #-16]
  3c:	e3530000 	cmp	r3, #0	; 0x0
  40:	0a00000d 	beq	7c <copy_code_to_sdram+0x70>
  44:	e3a03000 	mov	r3, #0	; 0x0
  48:	e50b3020 	str	r3, [fp, #-32]
  4c:	e51b2020 	ldr	r2, [fp, #-32]
  50:	e51b3018 	ldr	r3, [fp, #-24]
  54:	e1520003 	cmp	r2, r3
  58:	aa000007 	bge	7c <copy_code_to_sdram+0x70>
  5c:	e51b2010 	ldr	r2, [fp, #-16]
  60:	e51b3014 	ldr	r3, [fp, #-20]
  64:	e5d33000 	ldrb	r3, [r3]
  68:	e5c23000 	strb	r3, [r2]
  6c:	e51b3020 	ldr	r3, [fp, #-32]
  70:	e2833001 	add	r3, r3, #1	; 0x1
  74:	e50b3020 	str	r3, [fp, #-32]
  78:	eafffff3 	b	4c <copy_code_to_sdram+0x40>
  7c:	e51b301c 	ldr	r3, [fp, #-28]
  80:	e1a00003 	mov	r0, r3
  84:	e24bd00c 	sub	sp, fp, #12	; 0xc
  88:	e89da800 	ldmia	sp, {fp, sp, pc}

0000008c <main>:
  8c:	e1a0c00d 	mov	ip, sp
  90:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  94:	e24cb004 	sub	fp, ip, #4	; 0x4
  98:	e24dd01c 	sub	sp, sp, #28	; 0x1c
  9c:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  a0:	e2833050 	add	r3, r3, #80	; 0x50
  a4:	e50b3010 	str	r3, [fp, #-16]
  a8:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  ac:	e2833054 	add	r3, r3, #84	; 0x54
  b0:	e50b3014 	str	r3, [fp, #-20]
  b4:	e3a03203 	mov	r3, #805306368	; 0x30000000
  b8:	e50b3018 	str	r3, [fp, #-24]
  bc:	e51b2010 	ldr	r2, [fp, #-16]
  c0:	e3a03c01 	mov	r3, #256	; 0x100
  c4:	e5823000 	str	r3, [r2]
  c8:	e3a03000 	mov	r3, #0	; 0x0
  cc:	e50b301c 	str	r3, [fp, #-28]
  d0:	e3a03b01 	mov	r3, #1024	; 0x400
  d4:	e50b3020 	str	r3, [fp, #-32]
  d8:	e51b0018 	ldr	r0, [fp, #-24]
  dc:	e51b101c 	ldr	r1, [fp, #-28]
  e0:	e51b2020 	ldr	r2, [fp, #-32]
  e4:	ebffffc8 	bl	c <copy_code_to_sdram>
  e8:	e1a03000 	mov	r3, r0
  ec:	e50b3024 	str	r3, [fp, #-36]
  f0:	e51b3024 	ldr	r3, [fp, #-36]
  f4:	e3530000 	cmp	r3, #0	; 0x0
  f8:	0a000002 	beq	108 <main+0x7c>
  fc:	e51b3024 	ldr	r3, [fp, #-36]
 100:	e50b3028 	str	r3, [fp, #-40]
 104:	ea000004 	b	11c <main+0x90>
 108:	e51b2014 	ldr	r2, [fp, #-20]
 10c:	e3a03000 	mov	r3, #0	; 0x0
 110:	e5823000 	str	r3, [r2]
 114:	e3a03000 	mov	r3, #0	; 0x0
 118:	e50b3028 	str	r3, [fp, #-40]
 11c:	e51b0028 	ldr	r0, [fp, #-40]
 120:	e24bd00c 	sub	sp, fp, #12	; 0xc
 124:	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.

2.2. 具体分析

根据上面的和调用结构,进行函数调用结果的分析。

  1. 入口首先完成栈pc寄存器4096地址设置,然后通过bl跳转到main函数,bl跳转自动将当前bl指令的下一条指令地址赋值给lr寄存器。
  2. 进入main函数,计算栈之后的一个地址并给ip寄存器,使用降序栈stmdb指令进行pc、lr、ip、fp四个寄存器共16直接数据压栈。
  3. 函数中局部变量栈顶16后字节开始,也就是现在sp寄存器保存的地址开始为局部变量预留空间。main函数中共6个局部变量,其中包含了一个返回值复用的变量同28个字节数值。这个就是入口sp = sp-28原因。
  4. 中间调用copy_code_to_sdram子函数,子函数的sp指针在4096 -16 – 28位置,使用bl将当前指令下一条指令放在当前的lr寄存器。
  5. 之后子函数copy_codeto_sdram开始压栈,共16个字节。子函数共3个入参,之后是2个局部变量,共20字节数据,这个函数调用过程中sp寄存器现在计算结果是4096 – 44 – 36字节位置。
  6. 进行完逻辑处理以后,将返回值赋值给r0寄存器,然后将子函数的栈保存数据恢复给寄存器fp,sp,pc,这个和开始压栈命令是前三个进行数据对应。也就是入口位置的pc寄存器保存的内容不需要了。由于pc指针恢复成立了main函数调用是的lr寄存器值,所以进入main函数中调用copy_code_to_sdram的下一条指令进行代码执行。
  7. main函数继续往下执行,执行结束类似于子函数一样进行栈恢复,然后跳转到start.S文件中bl main函数下一条指令进行执行。

另外一幅比较经典的调用关系图像



版权声明:本文为u012294613原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。