ARM常用汇编指令与C程序机制

ARM-THUMB子程序调用规则ATPCS

  • ARM处理器中有R0-R15共16个寄存器(可以直接访问
    这里以S3C2440为例,其GPIO控制器有GPFCON、GPFDAT寄存器(以地址访问
寄存器别名使用规则
r15pc程序计数器
r14lr链接寄存器
r13sp数据栈指针
r12ip子程序内部调用的scratch寄存器
r11v8ARM状态局部变量寄存器8
r10v7、slARM状态局部变量寄存器7、在支持数据栈检查的ATPCS中为数据栈限制指针
r9v6、sbARM状态局部变量寄存器6、在支持RWPI的ATPCS中为静态基址寄存器
r8v5ARM状态局部变量寄存器5
r7v4、wrARM状态局部变量寄存器4、Thumb状态工作寄存器
r6v3ARM状态局部变量寄存器3
r5v2ARM状态局部变量寄存器2
r4v1ARM状态局部变量寄存器1
r3a4参数/结果/scratch寄存器4
r2a3参数/结果/scratch寄存器3
r1a2参数/结果/scratch寄存器2
r0a1参数/结果/scratch寄存器1

寄存器的使用规则总结如下:

  • 子程序间通过寄存器 r0~ r3来传递参数,这时可以使用它们的别名a0~ a3. 被调用的子程序返回前无需恢复r0~r3的内容。
  • 在子程序中, 使用r4~ r11来保存局部变量,这时可以使用它们的别名v1~ v8。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器,则不必进行这些操作。在Thumb程序中,通常只能使用寄存器r4~ r7来保存局部变量。
  • 寄存器r12用作子程序间scratch寄存器,别名为ip。
  • 寄存器r13用作数据栈指针,别名为sp在子程序中寄存器r13不能用作其他用途。 它的值在进入、退出子程序时必须相等。
  • 寄存器r14称为连接寄存器,别名为Ir它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将Ir 值保存到数据栈中), r14可以用作其他用途。
  • 寄存器r15是程序计数器,别名为pc。它不能用作其他用途。

对于R15、R14 和R13

  • R15 pc Program Counter:程序计数器=当前指令+8:流水线(当前执行地址A的指令,已经在对地址A+4的指令进行译码,已经在读取地址A+8->[PC的值]的指令)
  • R14 lr Link Register:返回地址(例如使用函数调用的时候返回原来的地方,保留原来的地方)
  • R13 sp:Stack Pointer:栈指针

内存访问指令:ldr、str、ldm、stm

  • LDR(load)读内存:LDR R0,[R1]
    假设R1的值是x,读取地址x上的数据(4字节),保存到R0中,而LDRB不同,B代表Byte 表示读内存1个字节

  • STR(store)写内存命令:STR R0,[R1] 假设R1的值是x,把R0的值写到地址x(4字节)

  • ldr rl, [r2, #4]
    将地址为r2+4的内存单元数据读取到r1中
    ldr rl, [r2]
    将地址为r2的内存单元位数据读取到r1中
    ldr r1,[r2],#4
    将地址为r2的内存单元数据读取到r1中,然后r2=r2+4

    str rl,[r2, #4]
    将r1的数据保存到地址为r2+4的内存单元中
    str r1, [r2]
    将r1的数据保存到地址为r2的内存单元中
    str r1, [r2],#4
    将r1的数据保存到地址为r2的内存单元中,然后r2=r2+4

  • ldr指令从内存中读取数据到寄存器,str指令把寄存器的值存储到内存中,他们操作的数据都是32为的

  • ldm和stm属于批量内存访问指令,只用一条指令就可以读写多个数据。

相对跳转指令:b、bl

  • 这两条指令的不同之处在与bl指令除了跳转之外,还将返回地址(bl的下一条指令的地址)
  • 这两条指令的可跳转范围是当前难治性的前后32MB
  • 它们是位置无关的指令

数据传送指令mov,地址读取伪指令ldr

  • mov指令可以把一个寄存器的值赋给另一个寄存器,或者把一个常数赋给寄存器
  • mov指令传送的常数必须能用立即数来表示
  • 当不知道一个数能否用"立即数"来表示时。可以使用ldr命令来复制。ldr是伪指令,它并不是真实存在的指令,编译器会把他扩展成真正的指令:如果该常数能够用"立即数"来表示,则使用mov指令
  • LDR:LDR R0,=0x12345678,结果R0=0x12345678 伪指令,它会被拆分为几条真正的RAM指令
    MOV R0,#0x12345678 错误 ARM指令32位 里面的某位表示MOV指令、R0剩下的不足32位不能表示任意值,只能表示简单值(被称为立即数)
    所以引入了伪指令 LDR R0,=任意值

加减指令add、sub

  • 例如
    add r1,r2, #1 表示r1=r2+1,即寄存器r1的值等于寄存器r2的值加上1
    sub r1,r2, #1 表示r1=r2-1

程序状态寄存器的访问指令:msr、mrs

  • ARM处理器有一个程序状态寄存器(cpsr),它用来控制处理器的工作模式、设置中断的总开关。
    msr cpsr, r0 复制r0到cpsr中
    mrs r0, cpsr 复制cpsr到r0中

其他伪指令

汇编程序汇总,常常见到下面语句:

.extern main
.text
.global _start
_start:
  • “extern"定义一个外部符号(可以是变量也可以是函数),上面的代码表示本文件中引用main是一个外部函数
  • .text 部分是处理器开始执行代码的地方,指定了后续编译出来的内容放在代码段【可执行】,是arm-gcc编译器的关键词。
  • .global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用;告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】
  • .global _start 让 _start 符号成为可见的标识符,这样链接器就知道跳转到程序中的什么地方并开始执行。
  • _start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序【如加载器】寻找到。
  • linux寻找这个 _start 标签作为程序的默认进入点。

利用改变MOV指令机器码来实现点亮另一个LED

  • C/汇编语言给人类给方便使用,转换为bin文件含有机器码给CPU使用
  • 这里以S3C2440为例
/*
 * 点亮LED1: gpf4
 */

.text
.global _start

_start:

/* 配置GPF4为输出引脚
 * 把0x100写到地址0x56000050
 */
	ldr r1, =0x56000050
	ldr r0, =0x100	/* mov r0, #0x100 */
	str r0, [r1]


/* 设置GPF4输出高电平 
 * 把0写到地址0x56000054
 */
	ldr r1, =0x56000054
	ldr r0, =0	/* mov r0, #0 */
	str r0, [r1]

	/* 死循环 */
halt:
	b halt
  • 其反汇编如下
led_on.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:	e59f1014 	ldr	r1, [pc, #20]	; 1c <.text+0x1c>
   4:	e3a00c01 	mov	r0, #256	; 0x100
   8:	e5810000 	str	r0, [r1]
   c:	e59f100c 	ldr	r1, [pc, #12]	; 20 <.text+0x20>
  10:	e3a00000 	mov	r0, #0	; 0x0
  14:	e5810000 	str	r0, [r1]

00000018 <halt>:
  18:	eafffffe 	b	18 <halt>
  1c:	56000050 	undefined
  20:	56000054 	undefined
  • MOV指令机器码

在这里插入图片描述

 4:	e3a00c01 	mov	r0, #256	; 0x100
  • 用二进制转换如下

在这里插入图片描述

  • bit24-21:1101表示mov指令
    bit15-12:0000表示R0
    bit11-0(立即数,拆分为高四位rotate 低八位immed_8 立即数=immed_8循环右移(2*rotate)位)来表示0x100
    例:立即数1100 0000 0001 immed_8=0000 0001右移2 * 12=24位 为0x100
    则立即数0x400 表示为 0x1011 0000 0001
  • 把mov r0, #256改成mov r0, #0x400 就是修改bit11-0,同时可以使得S3C2440的另一LED亮
  • 假设immed_8也为1,则0x400等于1循环右移22位,rotate=22/2=11
    则立即数bit11-0:1011 0000 0001,则改变mov机器码为0xe3a00b01就可以实现点亮另一个LED

ldmia和stmdb指令

  • 其他形式简单的描述指令的行为,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。
  • stmdb为(Decrement Before)先减后存,基本使用stmdb命令,其他后缀少
  • ldria为(Increment After)先读后增
  • stmdb:假设sp=4096 dp(先减后存 高编号寄存器存在高地址
    stmdb sp!, {fp, ip, lr, pc} fp:r11 ip:r12 lr:r14 pc:r15 //{}无论括号内怎么放都没有影响
    1.先减4096-4=4092
    2.后存:4092~4095存放PC的值 r15
    3.先减:sp’=sp-4=4088
    4.后存:4088~4091存放lr的值 r14
    !:最终的被修改的sp值=4080 如果没有!,sp最终还是=4096
  • ldmia:假设sp=4080(先读后增加 高编号寄存器存在高地址) ldmia sp, {fp, sp, pc} fp:r11
    sp:r13 pc:r15
    1.先读:fp=4080~4083的值=原来保存的fp
    2.后增:sp’=sp+4=4084
    3.先读:sp=4084~4087的值=原来保存的ip
    4.后增:sp’=sp+4=4088
    5.先读:pc=4088~4091的值=原来保存的lr的值
    6.后增:sp’=sp+4=4092 sp后面无!:sp修改后的值不存入sp中

C程序内部机制

  • strat.S:1.设置栈 2.调用main,并把返回地址保存在lr中
    led.c:main 1.定义两个局部变量 2.设置变量 3.return 0
    栈:sp所指向的内存,这段内存可读可写
  • 为何要设置栈?因为C函数要用
  • 怎么使用栈?a.保存局部变量 b.保存lr等寄存器
  • 调用者如何传参数给被调用者 r0-r3
  • 被调用者如何传返回值给调用者 r0-r3
  • 怎么从栈中恢复那些寄存器
    在函数中,r4-r11可能被使用,所以说在入口保存它们,在出口恢复它们
.text
.global _start
_start:

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

	/* 调用main */
	bl main
halt:
	b halt
int main()
{
	unsigned int *pGPFCON = (unsigned int *)0x56000050;
	unsigned int *pGPFDAT = (unsigned int *)0x56000054;

	/* 配置GPF4为输出引脚 */
	*pGPFCON = 0x100;
	
	/* 设置GPF4输出0 */
	*pGPFDAT = 0;

	return 0;
}
	00000000 <_start>:
	   0:	e3a0da01 	mov	sp, #4096	; 0x1000                sp=4096
	   4:	e59fd004 	ldr	sp, [pc, #4]	; 10 <.text+0x10>   
	   8:	eb000001 	bl	14 <main>

	0000000c <halt>:
	   c:	eafffffe 	b	c <halt>
	  10:	40001000 	andmi	r1, r0, r0

	00000014 <main>:
	  14:	e1a0c00d 	mov	ip, sp  	  					ip=sp=4096
	  18:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}		4092:pc=18+8=0x1a 4088:lr=c 4084:ip=4096 4080:fp?
	  1c:	e24cb004 	sub	fp, ip, #4	; 0x4   			fp=ip-4=4096-4=4092
	  20:	e24dd008 	sub	sp, sp, #8	; 0x8               sp=sp-8=4080-8=4072
	  24:	e3a03456 	mov	r3, #1442840576	; 0x56000000    r3=0x56000000
	  28:	e2833050 	add	r3, r3, #80	; 0x50              r3=0x56000050
	  2c:	e50b3010 	str	r3, [fp, #-16]                  r3存入[fp-16]=[4076]  局部变量保存在栈中
	  30:	e3a03456 	mov	r3, #1442840576	; 0x56000000	r3=0x56000000
	  34:	e2833054 	add	r3, r3, #84	; 0x54              r3=0x56000054
	  38:	e50b3014 	str	r3, [fp, #-20]                  r3存入[fp-20]=[4072]    
	  3c:	e51b2010 	ldr	r2, [fp, #-16]                  r2=[4092-16]=[4076]=0x56000050
	  40:	e3a03c01 	mov	r3, #256	; 0x100
	  44:	e5823000 	str	r3, [r2]
	  48:	e51b2014 	ldr	r2, [fp, #-20]
	  4c:	e3a03000 	mov	r3, #0	; 0x0
	  50:	e5823000 	str	r3, [r2]
	  54:	e3a03000 	mov	r3, #0	; 0x0                   //	 调用者可以通过r0-r3传参到被调用者
	  58:	e1a00003 	mov	r0, r3
	  5c:	e24bd00c 	sub	sp, fp, #12	; 0xc               sp=4092-12=4080
	  60:	e89da800 	ldmia	sp, {fp, sp, pc}            从栈中恢复寄存器 fp=[4080]=原来保存的fp sp=[4084]=4096 pc=[4088]=c
	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.
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值