【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)

摘要:
作者:韩树亮博客地址:http://blog.csdn.net/shulianghan/article/details/42408137请从以下著名来源下载本博客的相关文档:--ARM组装手册:http://download.csdn.net/detail/han1202012/8328375--ARM手册:http://download.csdn.net/detail/han1202012/8324

作者 : 韩曙亮

博客地址http://blog.csdn.net/shulianghan/article/details/42408137 

转载请著名出处


本博客相关文档下载

--ARM 汇编手册http://download.csdn.net/detail/han1202012/8328375

-- ARM 手册 http://download.csdn.net/detail/han1202012/8324641

-- ARM 9 芯片文档 : http://download.csdn.net/detail/han1202012/8332389

-- ARM 11 芯片文档 http://download.csdn.net/detail/han1202012/8332403





一. ARM 汇编概述



1. 汇编使用位置


汇编位置

-- 启动代码 Bootloader 初始化时对 CPU 和 协处理器 等进行初始化, 此时没有建立起 C 语言运行环境, 这个时候使用汇编语言执行初始化操作;

-- 效率要求 : 汇编效率高, Linux 内核中, 对效率有特殊要求的地方需要汇编;



2. 汇编分类



(1) ARM 标准汇编


ARM 标准汇编简介

-- 使用场景 : 适用于ARM公司的汇编器, 适合在 Windows 平台使用, 如ADS;



(2) GNU汇编


GNU 汇编简介

-- 使用场景 : 适用于 Linux 平台交叉编译工具链的汇编器;



3. ARM 汇编程序框架



ARM 汇编框架

-- ARM 汇编框架示例

.section .data
	< 初始化的数据>
.section .bss
	< 未初始化的数据>
.section .text
.global _start
_start:
	<汇编代码>
-- 程序入口 : "_start:" 是汇编程序的入口, 相当于 main();

-- 标注入口 : 使用 ".global _start" 标注程序入口, 外部才可以识别这是程序入口;

-- 标明代码段 : ".section .text" 标明这是一个代码段;

-- 标明 bss 段 : 使用 ".section .bss" 标明bss段, 如果没有 bss 段 和 数据段, 直接从 .text 开始;



4. 搭建汇编开发调试环境




(1) 汇编程序准备


程序代码

-- 定义代码段 : .text ;

-- 定义程序入口 : .globl _start;

-- 代码示例

.text
.globl _start
_start:
	mov r1,#1
	mov r2,#2
	mov r3,#3

Makefile 代码 :

-- 链接 elf 格式文件 : 设置程序起始位置 6410板子是 0x50008000 地址;

-- 在 arm-linux-ld 指定程序起始地址 : 在 -Ttext 50008000 即可;

-- 如果使用链接器脚本指定地址 : 注意第三行指定程序起始地址;

SECTIONS
{
	. = 0x50008000;

	. = ALIGN(4);
	.text :
	{
		led.o	(.text)
		*(.text)
	}

	. = ALIGN(4);
	.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

	. = ALIGN(4);
	.data : { *(.data) }


	. = ALIGN(4);
	.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
}

-- 代码示例 : 

all:start.o
	arm-linux-ld -Ttext 0x50008000 -o start.elf $^

%.o:%.S
	arm-linux-gcc -g -o $@ $^ -c

clean:
	rm -rf *.o *.elf



(2) 启动 JLink 调试



JLink 调试启动

-- 确保驱动安装 : 注意 要安装 Windows 驱动;

-- 连接 JLink : 虚拟机右下角连接 JLink;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第1张

-- 启动 JLinkGDBServer

[root@localhost JLink_Linux_V434a]# ./JLinkGDBServer 
SEGGER J-Link GDB Server V4.34a

JLinkARM.dll V4.34a (DLL compiled Aug 31 2011 11:51:40)

Listening on TCP/IP port 2331

J-Link connected
Firmware: J-Link ARM V8 compiled Aug 24 2011 17:23:32
Hardware: V8.00
S/N: 17935099
Feature(s): RDI,FlashDL,FlashBP,JFlash

J-Link found 2 JTAG devices, Total IRLen = 5
JTAG ID: 0x07B76F0F (ARM11)



(2) eclipse 调试环境


搭建 eclipse 调试环境

-- 导入工程 : 选择 Makefile Project With Existing Code;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第2张

-- 选择导入的代码位置

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第3张

-- clean 代码 : 选择 "Project" --> "Clean";

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第4张

-- build 工程 : 选择 "菜单" --> Project --> Build All 选项即可;

-- 配置 Debug 调试参数

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第5张

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第6张

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第7张

-- 执行调试 : F6 单步调试走两步, 可以再 Register 视图中查看寄存器的值, 可以看到 r1 和 r2 被赋值为 1 和 2 了;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第8张




二. ARM 指令分类

 

ARM 汇编手册

-- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8328375.

转载请著名出处

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第9张


GNU 汇编 与 ARM 标准汇编区别上面的手册是 ARM 标准汇编手册, 我们写的是 GNU 汇编手册, 有一定区别;

-- 大小写区别 : ARM 标准汇编 都是大写的, GNU 汇编可以是小写字母;



1. 算术和逻辑指令



(1) MOV 指令


MOV 指令简介 : 赋值操作;

-- 语法格式 : MOV <dest>, <op1>;

-- 语法解析 : dest 是目的寄存器, op1 可以是立即数, 也可以是寄存器, 地址等, 等价于 dest = op1;


汇编程序注释 : 汇编中使用 "@" 符号添加注释;


示例代码

.text
.global _start
_start:

@mov 指令范例
mov r1, #8	@将 8 赋值给 r1
mov r2, r1	@将 r1 中的值赋值给 r2
mov r3, #10	@将 10 赋值给 r3 寄存器



(2) MVN 指令


MVN 指令简介 : 取反赋值操作;

-- 语法格式 : MVN <dest>, <op1>;

-- 语法解析 : 将操作数 op1 取反后 赋值给 dest;


指令示例

-- 代码

.text
.global _start
_start:

@mvn 指令范例
mvn r1, #0b10	@0b10 二进制数取反, 赋值给 r1
mvn r2, #5	@5 十进制数取反, 赋值给 r2
mvn r3, r1	@将 r1 寄存器的值, 赋值给 r3



(3) SUB 指令


SUB 指令简介 : 减法操作;

-- 语法格式 : SUB <dest>, <op1>, <op2>;

-- 语法解析 : dest 存放减法结果, op1 是减数, op2 是被减数, dest = op1 - op2;

-- 注意 : dest op1 都不能使用立即数, op2 可以使用立即数;


代码示例

.text
.global _start
_start:

@sub 指令范例
@sub r1, #4, #2 错误示例, 减数不能是立即数, 必须是寄存器
mov r2, #4
sub r1, r2, #4
mov r0, #1
sub r3, r1, r0


(4) ADD 指令


ADD 指令简介 : 加法操作;

-- 语法格式 : ADD <dest>, <op1>, <op2>;

-- 语法解析 : dest 存放加法结果, op1 和 op2 是相加的两个数, dest = op1 + op2;

-- 注意 dest op1 都不能使用立即数, op2 可以使用立即数;


代码示例

@add 指令范例
mov r2, #1
add r1, r2, #3


(5) AND 指令


AND 指令简介 : 逻辑与操作;

-- 语法格式 : AND <dest>, <op1>, <op2>;

-- 语法解析 : dest 存放逻辑与结果, op1 和 op2 是相与的两个数, dest = op1 & op2;

-- 注意 dest op1 都不能使用立即数, 必须使用寄存器, op2 可以使用立即数;


代码示例

.text
.global _start
_start:

@and 指令范例
mov r1, #5
and r2, r1, #0

mov r1, #5
mov r2, r1, #1


(6) BIC 指令


BIC 指令简介 : 位清除指令操作;

-- 语法格式 : AND <dest>, <op1>, <op2>;

-- 语法解析 : dest 存放位清除结果, op1 是被清除的对象, op2 是掩码;

-- 示例 : "bic r0, r0, #0b1011", 清除 r0 中的 第0, 1, 3 位, 其余位保持不变, 结果放入 r0 中;

-- 注意 dest op1 都不能使用立即数, 必须使用寄存器, op2 可以使用立即数; 

-- 二进制表示 : 掩码中 % 在标准汇编中表示二进制, 但是在 GNU 汇编中无法使用, GNU 汇编中使用 0b 代表二进制;


代码示例

.text
.global _start
_start:

@bic 指令范例
mov r1, #0b101011
bic r2, r1, #0b101	@将r1 的 0, 2 位清除



2. 比较指令



(1) CMP 指令


CMP 指令简介 : 比较指令;

-- 语法格式 : CMP <op1>, <op2>;

-- 语法解析 : 比较结果有三种 op1 > op2 (CPSR N = 0), op1 = op2 (CPSR Z = 1), op1 < op2 (CPSR N = 1), 结果放入 CPSR 寄存器;


代码示例

.text
.global _start
_start:

@cmp 指令范例
mov r1, #2
cmp r1, #1

mov r1, #2
cmp r1, #3

mov r1, #2
cmp r1, #2


(2) TST 指令


TST 指令简介 : 比较指令;

-- 语法格式 : TST <op1>, <op2>;

-- 语法解析 : op1 和 op2 按位与操作, 结果影响 CPSR 寄存器, 如果结果 不为 0, CPSR 的 Z = 0, 如果结果为0, Z = 1;


代码示例 

.text
.global _start
_start:

@cmp 指令范例
mov r1, #0b101
tst r1, #0b001	@按位与结果是 0b1, 结果不为0, CPSR Z = 0

mov r1, #0b101
tst r1, #0b10	@按位与结果是 0, 结果不为




3. 分支指令



(1) B 指令


B 指令简介 : 分支指令;

-- 语法格式 : B{条件} 地址;

-- 语法解析 : 如果满足条件, 就跳转到 地址 位置, 如果不满足条件, 就执行下面的语句, 如果没有条件, 就是 100% 执行;;


代码示例 

-- 条件分析 : gt 是大于条件, 如果 r1 > r2 就走条件分支, 否则就继续执行下一条;

.text
.global _start
_start:

@b 分支指令范例
mov r1, #6
mov r2, #5
cmp r1, r2	@比较 r1 和 r2 中的值
@b 后可以跟一个条件, {条件} 在 {} 中就是可加可不加, 如果没有条件就是无条件100%执行
@gt 是大于条件指令, 如果条件满足会跳转到 branch1, 如果不满足就执行下面的指令
bgt branch1
add r3, r1, r2
b end	@这里为了不执行 branch1 操作, 直接跳转到 end 执行

branch1:
sub r3, r1, r2

end:
nop


(2) BL 指令


BL 指令简介 : 带连接的分支指令;

-- 语法格式 : BL{条件} 地址;

-- 语法解析 如果满足条件, 就跳转到 地址 位置, 如果不满足条件, 就执行下面的语句, 如果没有条件, 就是 100% 执行;;


代码示例 

.text
.global _start
_start:

@bl 带连接的分支指令范例
mov r1, #2
cmp r1, #1
@此时跳转到 func1, func1 执行完程序无法返回, 如果 使用 bl 跳转, 程序会返回
@b func1
@此时使用 bl 跳转到 func1 执行, func1 执行完毕后会返回执行下面的语句
bl func1		

mov r1, #2
cmp r1, #3

func1:
mov r1, #2
cmp r1, #2

mov r1, #4
cmp r1, #6



4. 移位指令


(1) LSL 指令


LSL 指令简介 : 逻辑左移指令;

-- 语法格式 : Rx, LSL#2;

-- 语法解析 : 将 Rx 寄存器中的值, 左移2 位;


代码示例 :

.text
.global _start
_start:

@lsl 左移指令范例
mov r1, #0b1
@将 r1 中的值, 左移 2 位, 放入 r1 寄存器中
mov r1, r1, lsl#2


(2) ROR 指令


ROR 指令简介 : 循环右移指令;

-- 语法格式 : Rx, ROR#2;

-- 语法解析 : 将 Rx 寄存器中的值 循环右移 2 位;


代码示例 

.text
.global _start
_start:

@ror 循环右移指令范例
mov r1, #0b11
@结果是 ob1000...0001
mov r1, r1, ror#1



5. 程序状态字访问指令


程序状态字 : CPSRSPSR;

-- 注意 : 程序状态字 不能使用 通用寄存器的语句 如 MOV 等访问, 必须使用 程序状态寄存器的 专用指令 读写;

转载请著名出处


代码示例 

.text
.global _start
_start:

@mrs 指令范例
@rs 是 将 s -> r, sr 是 r -> s
mrs r0, cpsr	@将 cpsr 中的数据搬移到 r0 中
orr r0, #0b100	@将 cpsr 中的第三位置为1
msr cprs, r0



6. 存储器访问指令



(1) STR 指令


STR 指令简介 : 将 寄存器中的值 保存到 内存中;

-- 语法格式 : str r0, 地址;

-- 语法解析 : 将 R0 寄存器中的值 保存到 内存地址中;;


代码示例

.text
.global _start
_start:

@str 指令范例
mov r0, #0xff
@将 r1 值改为 50000000 (OK-6410)
str r0, [r1]

-- 调试 : 添加地址监控, 在 Memory 视图中进行监控;



(2) LDR 指令


LDR 指令简介 : 将 寄存器中的值 保存到 内存中;

-- 语法格式 : ldr r0, 地址;

-- 语法解析 : 将 内存地址中 存放的值 加载入 r0 中;


代码示例

@ldr 指令范例
mov r0, #0xff
@将 r1 值改为 50000000 (OK-6410)
str r0, [r1]
ldr r0, [r1]



7. 以上所有代码示例


以上所有代码示例 : 便于调试学习;

.text
.global _start
_start:

@ldr 指令范例
mov r0, #0xff
@将 r1 值改为 50000000 (OK-6410)
str r0, [r1]
ldr r0, [r1]

@str 指令范例
mov r0, #0xff
@将 r1 值改为 50000000 (OK-6410)
str r0, [r1]

@mrs msr 指令范例
@rs 是 将 s -> r, sr 是 r -> s
mrs r0, cpsr	@将 cpsr 中的数据搬移到 r0 中
orr r0, #0b100	 程序入口, 用法 ".globol _start", 注意前面加上点;@将 cpsr 中的第三位置为1
msr cprs, r0

@ror 循环右移指令范例
mov r1, #0b11
@结果是 ob1000...0001
mov r1, r1, ror#1

@lsl 左移指令范例
mov r1, #0b1
@将 r1 中的值, 左移 2 位, 放入 r1 寄存器中
mov r1, r1, lsl#2

@bl 带连接的分支指令范例
mov r1, #2
cmp r1, #1
@此时跳转到 func1, func1 执行完程序无法返回, 如果 使用 bl 跳转, 程序会返回
@b func1
@此时使用 bl 跳转到 func1 执行, func1 执行完毕后会返回执行下面的语句
bl func1		

mov r1, #2
cmp r1, #3

func1:
mov r1, #2
cmp r1, #2

mov r1, #4
cmp r1, #6

@b 分支指令范例
mov r1, #6
mov r2, #5
cmp r1, r2	@比较 r1 和 r2 中的值
@b 后可以跟一个条件, {条件} 在 {} 中就是可加可不加, 如果没有条件就是无条件100%执行
@gt 是大于条件指令, 如果条件满足会跳转到 branch1, 如果不满足就执行下面的指令
bgt branch1
add r3, r1, r2
b end	@这里为了不执行 branch1 操作, 直接跳转到 end 执行

branch1:
sub r3, r1, r2

end:
nop

@cmp 指令范例
mov r1, #0b101
tst r1, #0b001	@按位与结果是 0b1, 结果不为0, CPSR Z = 0

mov r1, #0b101
tst r1, #0b10	@按位与结果是 0, 结果不为

@cmp 指令范例
mov r1, #2
cmp r1, #1

mov r1, #2
cmp r1, #3

mov r1, #2
cmp r1, #2

@bic 指令范例
mov r1, #0b101011
bic r2, r1, #0b101	@将r1 的 0, 2 位清除

@and 指令范例
mov r1, #5
and r2, r1, #0

mov r1, #5
mov r2, r1, #1

@add 指令范例
mov r2, #1
add r1, r2, #3

@mov 指令范例
mov r1, #8	@将 8 赋值给 r1
mov r2, r1	@将 r1 中的值赋值给 r2
mov r3, #10	@将 10 赋值给 r3 寄存器

@mvn 指令范例
mvn r1, #0b10	@0b10 二进制数取反, 赋值给 r1
mvn r2, #5	@5 十进制数取反, 赋值给 r2
mvn r3, r1	@将 r1 寄存器的值, 赋值给 r3

@sub 指令范例
@sub r1, #4, #2 错误示例, 减数不能是立即数, 必须是寄存器
mov r2, #4
sub r1, r2, #4
mov r0, #1
sub r3, r1, r0



三. ARM 伪指令


参考文档 : ARM 文档 Page 110, 上面有提供下载.



1. ARM 机器码



(1) 机器码反汇编示例


汇编程序执行流程 : 汇编代码 --> 汇编器 --> 机器码 --> CPU 运行;


反汇编示例 : 找到一个 elf 文件, 使用 arm-linux-objdump 反汇编;

-- 命令 : 使用 arm-linux-objdump -S -D start.elf 命令进行反汇编, 其中 "50008000:e3a01001 movr1, #1; 0x1" 中的 "e3a01001" 就是机器码, 如下图标注部分;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第10张

-- 反汇编部分结果

[root@localhost 04_assembly]# arm-linux-objdump -S -D start.elf 

start.elf:     file format elf32-littlearm

Disassembly of section .text:

50008000 <_start>:
.text
.globl _start
_start:
	mov r1,#1
50008000:	e3a01001 	mov	r1, #1	; 0x1
	mov r2,#2
50008004:	e3a02002 	mov	r2, #2	; 0x2
	mov r3,#3
50008008:	e3a03003 	mov	r3, #3	; 0x3
Disassembly of section .debug_aranges:


(2) 机器码格式



机器码格式 : 截图自 arm 文档 P110;

-- ARM 机器码位数 : 32位;

-- 机器码分段

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第11张



(3) 解析 MOV 指令机器码



代码准备

-- 汇编代码

.text
.globl _start
_start:

	mov r0, r1
	moveq r0, #0xff

-- Makefile 脚本

all:start.o
	arm-linux-ld -Ttext 0x50008000 -o start.elf $^

%.o:%.S
	arm-linux-gcc -g -o $@ $^ -c

clean:
	rm -rf *.o *.elf


反汇编 elf 文件

-- 反汇编内容 : 省略下面的大部分;

[root@localhost 04_assembly]# arm-linux-objdump -S -D start.elf 

start.elf:     file format elf32-littlearm

Disassembly of section .text:

50008000 <_start>:
.text
.globl _start
_start:

	mov r0, r1
50008000:	e1a00001 	mov	r0, r1
	moveq r0, #0xff
50008004:	03a000ff 	moveq	r0, #255	; 0xff
Disassembly of section .debug_aranges:

汇编对应机器码

-- "mov r0, r1" : 十六进制 0xe1a00001, 二进制      11100001101000000000000000000001;

-- "moveq r0, #0xff" : 十六进制 0x03a000ff, 二进制 00000011101000000000000011111111;


机器码解析

第一条 : 1110 00 0 1101 0 0000 0000 000000000001

第二条 : 0000 00 1 1101 0 0000 0000 000011111111

-- 条件位对比 (第一段 31 ~ 28) :  第一条是 1110 对应 AL 总是执行,  第二条是 0000 对应 EQ;

-- 保留位对比 (第二段 27 ~ 26) : 第一条 00, 第二条 00, 明显都一样;

-- I 操作数类型标识位 (第三段 25) : 标志最后一个存立即数 还是寄存器, 如果是 0 表示寄存器, 如果是 1 表示立即数;

-- 操作码位 (第四段 24 ~ 21) : 区分不同指令, 1101 是 MOV 指令;

-- S 状态寄存器改变标识 (第五段 20) : 是否影响 CPSR 寄存器, 如果 S = 0 不影响, 如果 S = 1 影响;

-- Rn 源操作寄存器 (第六段 19 ~ 16) : MOV 和 MVN 不使用 Rn 位, 寄存器编号;

-- Rd 目的操作寄存器 (第七段 15 ~ 12) : 寄存器编号;

-- shifter_operand 源操作书 (第八段 11 ~ 0) : 源操作数, 这个与 I 位结合起来, 如果 I = 0, 该位表示寄存器编号, 如果 I = 1, 该位表示 立即数大小, 立即数是有范围的, 如果超出会报错, 这里就需要使用伪指令了;



(4) 机器码相关文档


相关文档

-- 位数文档 : P116, The ARM Instruction Set, A3.4.1 Instruction encoding;

-- MOV 和 MVN 指令 : 机器码格式 "<opcode1>{<cond>}{S} <Rd>, <shifter_operand>", 没有 Rn 字段, 该字段没用;

转载请著名出处

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第12张

-- 条件位文档 : Page 112, The ARM Instruction Set, A3.2.1 Condition code 0b1111;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第13张





2. 伪指令


伪指令简介 : 伪指令没有对应的机器码, 这种指令只在编译时起作用, 伪指令需要转化成 其它汇编指令运行, 如 定义 宏, 不会产生机器码;



(1) globol 伪指令


globol 伪指令介绍

-- 伪指令作用 : 用于定义 程序入口, 用法 ".globol _start", 注意前面加上点;

-- 代码示例

.text
.global _start
_start:

@lsl 左移指令范例
mov r1, #0b1
@将 r1 中的值, 左移 2 位, 放入 r1 寄存器中
mov r1, r1, lsl#2


(2) data acsii byte word 伪指令


伪指令介绍 : 

-- 伪指令作用 : data 用于定义 数据段, 标明后面的数据存放到数据段中; acsii 标明字符串变量类型, byte 标明 byte 类型变量, word  标明 word 类型变量;


代码示例

-- 汇编代码 : start.S ;

.data	@定义数据变量
hello:	@标明变量地址, 字符串变量
.ascii "Hello World !"
bh:	@标明变量地址, byte 变量
.byte 0x1
ADD:	@标明变量地址, word 变量
.word 0xff

.text
.global _start
_start:

mov r0, #0xff
-- make 脚本 : Makefile;

all: start.o 
	arm-linux-ld -Ttext 0x50008000 -o start.elf start.o
	
start.o : start.S
	arm-linux-gcc -g -o start.o -c start.S
	
.PHONY: clean
clean:
	rm *.o *.elf *.bin


分析 elf 文件 : 使用 arm-linux-readelf -a start.elf 命令分析 start.elf 文件; 

-- .data 段地址 : 注意 [2] 中 .data 地址为 0x50010004;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第14张

-- 数据变量

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第15张

-- elf 文件分析全文

octopus@octopus:~/arm/demo$ arm-linux-readelf -a start.elf 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x50008000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          33100 (bytes into file)
  Flags:                             0x5000002, has entry point, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         11
  Section header string table index: 8

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        50008000 008000 000004 00  AX  0   0  4
  [ 2] .data             PROGBITS        50010004 008004 000012 00  WA  0   0  1
  [ 3] .debug_aranges    PROGBITS        00000000 008018 000020 00      0   0  8
  [ 4] .debug_info       PROGBITS        00000000 008038 000048 00      0   0  1
  [ 5] .debug_abbrev     PROGBITS        00000000 008080 000014 00      0   0  1
  [ 6] .debug_line       PROGBITS        00000000 008094 000037 00      0   0  1
  [ 7] .ARM.attributes   ARM_ATTRIBUTES  00000000 0080cb 000014 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 0080df 00006c 00      0   0  1
  [ 9] .symtab           SYMTAB          00000000 008304 000180 10     10  13  4
  [10] .strtab           STRTAB          00000000 008484 000087 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x008000 0x50008000 0x50008000 0x00004 0x00004 R E 0x8000
  LOAD           0x008004 0x50010004 0x50010004 0x00012 0x00012 RW  0x8000

 Section to Segment mapping:
  Segment Sections...
   00     .text 
   01     .data 

There is no dynamic section in this file.

There are no relocations in this file.

There are no unwind sections in this file.

Symbol table '.symtab' contains 24 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 50008000     0 SECTION LOCAL  DEFAULT    1 
     2: 50010004     0 SECTION LOCAL  DEFAULT    2 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000000     0 SECTION LOCAL  DEFAULT    6 
     7: 00000000     0 SECTION LOCAL  DEFAULT    7 
     8: 50010004     0 NOTYPE  LOCAL  DEFAULT    2 hello
     9: 50010011     0 NOTYPE  LOCAL  DEFAULT    2 bh
    10: 50010011     0 NOTYPE  LOCAL  DEFAULT    2 $d
    11: 50010012     0 NOTYPE  LOCAL  DEFAULT    2 ADD
    12: 50008000     0 NOTYPE  LOCAL  DEFAULT    1 $a
    13: 50008004     0 NOTYPE  GLOBAL DEFAULT  ABS __exidx_end
    14: 50010016     0 NOTYPE  GLOBAL DEFAULT  ABS _bss_end__
    15: 50010016     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start__
    16: 50008004     0 NOTYPE  GLOBAL DEFAULT  ABS __exidx_start
    17: 50010016     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_end__
    18: 50008000     0 NOTYPE  GLOBAL DEFAULT    1 _start
    19: 50010016     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    20: 50010018     0 NOTYPE  GLOBAL DEFAULT  ABS __end__
    21: 50010016     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
    22: 50010018     0 NOTYPE  GLOBAL DEFAULT  ABS _end
    23: 50010004     0 NOTYPE  GLOBAL DEFAULT    2 __data_start

No version information found in this file.
Attribute Section: aeabi
File Attributes
  Tag_CPU_arch: v4
  Tag_ARM_ISA_use: Yes



(3) equ 伪指令


equ 伪指令介绍 : 

-- 伪指令作用 : 该指定作用是定义常量;

-- 代码示例 

.text
.global _start
_start:

@定义一个宏变量
.equ DA, 0x68

@将 DA 值赋值给 r0 寄存器
mov r0, #DA



(4) align 伪指令


align 伪指令介绍 : 

-- 伪指令作用 : 标明数据对齐;


对齐代码示例

-- 含有对齐的代码

.data	@定义数据变量
hello:	@标明变量地址, 字符串变量
.ascii "Hello World !"
bh:	@标明变量地址, byte 变量
.byte 0x1
ADD:	@标明变量地址, word 变量
.word 0xff

.text
.global _start
_start:

@定义一个宏变量
.equ DA, 0x68

@将 DA 值赋值给 r0 寄存器
mov r0, #DA

-- 不含对齐的代码

.data	@定义数据变量
hello:	@标明变量地址, 字符串变量
.ascii "Hello World !"
.align 4
bh:	@标明变量地址, byte 变量
.byte 0x1
ADD:	@标明变量地址, word 变量
.word 0xff

.text
.global _start
_start:

@定义一个宏变量
.equ DA, 0x68

@将 DA 值赋值给 r0 寄存器
mov r0, #DA


代码 elf 内容对比 : 这里省略大部分, 只给出内存对应地址, 查看对齐内容;

-- 没有对齐的代码 : 0x50010011 明显不能被 4 整除;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第16张

-- 对齐的代码 : 0x50010020 可以被4整除, 此时已经进行了对齐;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第17张





3. 操作类伪指令



(1) ldr 伪指令


机器码 shifter_operand 段解析

-- 段解析 : 其中 4 位存放位移值, 8 位存放数值, 因此 立即数不能超过 8位, 最大 0xFF;

-- 缺陷 : 无法使用 大的数字;

-- 示例

.text
.global _start
_start:

mov r0, #0xFFF
-- 编译错误

octopus@octopus:~/arm/demo$ make
arm-linux-gcc -g -o start.o -c start.S
start.S: Assembler messages:
start.S:5: Error: invalid constant (fff) after fixup
make: *** [start.o] 错误 1


ldr 伪指令

-- 作用 : 可以 向寄存器中赋值 大立即数;

-- 语法格式 : "ldr r0, =0xFFF", 注意 不使用 # , 使用 = 后面加上立即数;

-- 代码示例 : 此时能编译成功, 0xfff 被赋值给 r0 寄存器;

.text
.global _start
_start:

ldr r0, =0xFFF
-- 反汇编 elf 代码

octopus@octopus:~/arm/demo$ arm-linux-objdump -S -D start.elf 

start.elf:     file format elf32-littlearm

Disassembly of section .text:

50008000 <_start>:
.text
.global _start
_start:

ldr r0, =0xFFF
50008000:	e51f0004 	ldr	r0, [pc, #-4]	; 50008004 <_start+0x4>
50008004:	00000fff 	.word	0x00000fff
Disassembly of section .debug_aranges:

... ...
-- 分析反汇编代码 : "50008000:e51f0004 ldrr0, [pc, #-4]; 50008004 <_start+0x4>" 代码表明 ldr r0, =0xFFF 是使用 ldr 读取内存指令, 从 pc - 4 地址上读取该地址存储的值, "50008004:00000fff .word0x00000fff"  表明 系统将 0xFFF 定义在了 pc -4 内存地址中;



(2) nop 伪指令


nop 伪指令 : 

-- 作用 : 进行延时, 在一些对时序要求较高的程序中, 使用该指令进行一个时钟的延时;

-- 代码示例 : 

.text
.global _start
_start:

nop
-- 反汇编 : nop 伪指令执行了 "mov r0, r0" 这个无意义的操作;

octopus@octopus:~/arm/demo$ arm-linux-objdump -S -D start.elf 

start.elf:     file format elf32-littlearm

Disassembly of section .text:

50008000 <_start>:
.text
.global _start
_start:

nop
50008000:	e1a00000 	nop			(mov r0,r0)
Disassembly of section .debug_aranges:

... ...





三. 协处理器访问指令



1. 协处理器简介



协处理器简介

-- 作用 : 执行特定处理任务, 减轻处理器负担;

-- 数学协处理器 : 主要进行数字处理;

-- 协处理器支持 : ARM 芯片最多支持 16 个协处理器, 最重要的协处理器 是 CP15;


CP15 协处理器作用 : CP15 是系统控制寄存器, 通过这些寄存器, 配置与控制 缓存, MMU, 保护系统, 时钟模式 和 其它系统参数;

-- 如何访问 CP15 : 通过访问 CP15 中的寄存器控制上面的参数, CP15 提供了 16 组寄存器;

-- 文档 : 

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第18张

 



2. 协处理器访问指令



mcr 指令解析 : 详情见 ARM11 文档, P145, 3.2;

-- 作用 : 将本地寄存器中的数据 赋值给 CP15 的寄存器;

-- 语法格式 : "MCR{cond} P15,<Opcode_1>,<Rd>,<CRn>,<CRm>,<Opcode_2>";

-- 语法解析 : CRn 表示 CP15 寄存器属于哪一组, CRm 也是组名;

-- 代码示例

.text
.global _start
_start:

@"MCR{cond} P15,<Opcode_1>,<Rd>,<CRn>,<CRm>,<Opcode_2>"
@读取 MainID 寄存器
mcr p15, 0, r0, c0, c0, 0

-- 文档截图

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第19张

-- CP15 寄存器访问 : 如果读取 MainID 寄存器, 就取前面的哪些 CRn Op1 CRm Op2 等参数;

【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)第20张




作者 : 韩曙亮

博客地址 : http://blog.csdn.net/shulianghan/article/details/42408137 

转载请著名出处


本博客相关文档下载 

-- ARM 汇编手册 : http://download.csdn.net/detail/han1202012/8328375

-- ARM 手册 http://download.csdn.net/detail/han1202012/8324641

-- ARM 9 芯片文档 : http://download.csdn.net/detail/han1202012/8332389

-- ARM 11 芯片文档 http://download.csdn.net/detail/han1202012/8332403


免责声明:文章转载自《【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇centos7下 PHP添加pdo_myql扩展Django安装和启动下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

用OllyDbg做破解[转]

朋友所托,要帮忙破解一个MFC的小程序,他急等着用 (背景:几个人合伙创业,其中一个负责写这个有点小核心的项目,为了巩固自己的”地位“搞的小把戏,给加了密,要用必须通过他 - 我艹~~~)。 虽说自己搞C++比较多,相对来讲native一点,但是对于汇编与破解,了解相当有限,去年这朋友也找过我,因为当时刚换公司比较忙,是求助另外一好友才搞定的。这次还是自己...

第十章 Call 和 Ret 指令

10.1 ret 和 retf call和ret 指令都是转移指令,它们都修改IP,或同时修改CS和IP。 它们经常被共同用来实现自程序的设计。 这一章,我们讲解call和ret 指令的原理。 (一)ret指令用栈中的数据,修改IP的内容,从而实现近转移! CPU执行ret指令时,进行下面两步操作: (1)(IP)=((ss)*16+(sp)) (2)(s...

bss段为什么需要初始化?

    我们都知道bss段需要初始化,但是这是为什么呢?        通过浏览资料,我们都会发现,bss段是不会出现在程序下载文件(*.bin *.hex)中的,因为全都是0。如果把它们出现在程序下载文件中,会增加程序下载文件的大小。实际应用中,通常只需要把bss段的起始地址和结束地址保存起来,而不需要将程序下载文件中出现bss段(一堆0)将来真正运行程...

Java并发机制和底层实现原理

  Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码转化为汇编指令在CPU上执行。Java中的并发机制依赖于JVM的实现和CPU的指令。      Java语言规范第三版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排它锁单独获得这个变量...

linux下调试C程序

正文:==================GDB是一个强大的命令行调试工具。大家知道命令行的强大就是在于,其可以形成执行序列,形成脚本。UNIX下的软件全是命令行的,这给程序开发提代供了极大的便利,命令行软件的优势在于,它们可以非常容易的集成在一起,使用几个简单的已有工具的命令,就可以做出一个非常强大的功能。于是UNIX下的软件比Windows下的软件更能...

汇编效率优化:指令处理机制

大多数情况下,编写程序都不会使用汇编语言而是使用高级语言,原因大致有以下几点: 花费更多时间。高级语言的一行相当于汇编语言的几行、几十行甚至更多。 不够安全。比如说在进行函数调用时PUSH与POP必须成对出现,高级语言中的函数调用会自动为你执行PUSH与POP的操作,但是汇编语言中就必须由程序员自己保证PUSH与POP一致,否则会导致栈错乱,使得程序出现...