【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)
作者 : 韩曙亮
博客地址 : 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;
-- 启动 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;
-- 选择导入的代码位置 :
-- clean 代码 : 选择 "Project" --> "Clean";
-- build 工程 : 选择 "菜单" --> Project --> Build All 选项即可;
-- 配置 Debug 调试参数 :
-- 执行调试 : F6 单步调试走两步, 可以再 Register 视图中查看寄存器的值, 可以看到 r1 和 r2 被赋值为 1 和 2 了;
二. ARM 指令分类
ARM 汇编手册 :
-- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8328375.
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. 程序状态字访问指令
程序状态字 : CPSR 和 SPSR;
-- 注意 : 程序状态字 不能使用 通用寄存器的语句 如 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 mov r1, #1 ; 0x1" 中的 "e3a01001" 就是机器码, 如下图标注部分;
-- 反汇编部分结果 :
[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位;
-- 机器码分段 :
(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 字段, 该字段没用;
-- 条件位文档 : Page 112, The ARM Instruction Set, A3.2.1 Condition code 0b1111;
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;
-- 数据变量 :
-- 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 整除;-- 对齐的代码 : 0x50010020 可以被4整除, 此时已经进行了对齐;
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 ldr r0, [pc, #-4] ; 50008004 <_start+0x4>" 代码表明 ldr r0, =0xFFF 是使用 ldr 读取内存指令, 从 pc - 4 地址上读取该地址存储的值, "50008004: 00000fff .word 0x00000fff" 表明 系统将 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 组寄存器;
-- 文档 :
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
-- 文档截图 :
-- CP15 寄存器访问 : 如果读取 MainID 寄存器, 就取前面的哪些 CRn Op1 CRm Op2 等参数;
作者 : 韩曙亮
博客地址 : 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