【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )
【摘要】
一. 异常向量表
1. 异常相关概念
(1) 异常(2) 异常类型简介 2. 异常处理
(1) 异常处理 二. 异常向量表代码编写
1. 初始化异常向量表模块代码...
本博客的参考文章及相关资料下载 :
- 1.ARM 架构参考手册 ( ARM Architecture Reference Manual ) : https://download.csdn.net/download/han1202012/8324641
- 2.汇编参考手册 : https://download.csdn.net/download/han1202012/8328375
- 3.本博客代码下载 : https://download.csdn.net/download/han1202012/10392901
一. 异常向量表
参考手册 :
- 1.异常向量表手册位置 : ARM Architecture Reference Manual A2.6 ;
- 2.ARM Architecture Reference Manual 文档下载地址 : http://download.csdn.net/detail/han1202012/8324641;
1. 异常相关概念
(1) 异常
异常定义 :
- 1.异常简介 : 由于 内部或者外部的一些事件 , 导致 处理器停下正在处理的工作, 转而去处理这些发生的事;
- 2.处理器状态 : 当遇到异常的时候, 先将处理器状态保存起来, 以便执行完异常处理程序后, 可以恢复处理器状态, 继续执行异常出现点下面的代码;
- 3.异常同时出现 : 在一个时间点 可以出现 多个异常;
- 4.异常向量概念 : 当异常发生的时候, 程序被强行从一个固定的内存地址执行, 每个种类的异常都有对应的一固定内存地址, 这个内存地址就是异常向量 ;
(2) 异常类型简介
异常类型 : ARM 架构 支持 七种类型的异常,
- 1.Reset : 处理器在工作时, 突然 按下重启键, 就会触发该异常;
- 2.Undefined instructions : 处理器无法识别指令的异常, 处理器执行的指令是有规范的, 如果 尝试执行 不符合要求的指令, 就会进入到该异常指令对应的地址中;
- 3.Software interrupt (SWI) : 软中断, 软件中需要去打断处理器工作, 可以使用软中断来执行 ;
- 4.Prefetch Abort (instruction fetch memory abort) : 预取指令失败, ARM 在执行指令的过程中, 要先去预取指令准备执行, 如果预取指令失败, 就会产生该异常;
- 5.Data Abort (data access memory abort) : 读取数据失败;
- 6.IRQ (interrupt) : 普通中断;
- 7.FIQ (fast interrupt) : 快速中断, 快速中断要比普通中断响应速度要快一些;
2. 异常处理
(1) 异常处理
异常处理简介 :
- 1.异常向量工作机制 : 异常发生时, ARM 处理器会跳转到对应该异常的 固定地址 去执行异常处理程序, 这个 固定的地址 就是异常向量;
- 2.默认地址 和 高位地址 : 每个中断类型对应两个异常向量, 默认是 Normal address, 如果经过配置, 配置使用高位的异常向量, 就会使用 High vector address 异常向量; 使用 普通 向量 还是 高位向量, 可以使用 CP15 协处理器进行配置;
- 3.异常 与 地址 一一对应 : 每个异常都对应着一个地址, 出现指定类型的异常时, 就会跳转到该异常对应的地址执行异常处理程序;
- 4.注意异常向量断点 ( 保留位 ) : 普通向量 ( Normal Vector ) 地址 0x00000014 和 高位向量 ( High Vector ) 地址 0xFFFF0014 暂时没有使用, 为今后的扩展保留;
二. 异常向量表代码编写
1. 初始化异常向量表模块代码
Start.S 汇编程序解析 :
- 1.汇编参考文章 : https://blog.csdn.net/shulianghan/article/details/42408137 ;
- 2.汇编参考手册下载地址 : https://download.csdn.net/download/han1202012/8328375
- 3.指明汇编代码段 : 使用
.text
宏 指明汇编代码段; - 4.标明程序入口标号 : 先使用
.global _start
将 _start 声明成全局符号; 使用_start:
标明程序的入口标号是 _start; 5.定义标号( 类似于函数名 ) : 定义自定义标号, 格式
标号:
, 例如irq:
;- ( 1 ) 定义标号执行的指令 : 标号下面定义要执行的指令, 如果想要执行标号下面的指令, 直接跳转到对应标号即可;
- ( 2 ) 异常执行的代码内容 : 在下面代码的 27 ~ 49 行就是定义了 7 个异常执行操作的 标号 以及要执行的指令 nop; 这些都是异常发生的时候要处理的代码;
- ( 3 ) 代码示例 : 下面代码定义了一个 irq 标号, 跳转到该标号即开始执行标号下的代码 nop,
irq : nop
;
6.空操作 : 如果在某个位置执行指令, 不想做任何操作, 可以使用
nop
表示 什么操作都不执行;7.定义标号 ( 类似于变量 ) : 定义一个标号, 在标号中存放 32 位的值, 定义格式
标号: .word 存储值的内容
;- ( 1 ) 示例 :
_irq: .word irq
, 定义 _irq 标号,.word
表示该标号存储的是 32 位值, 这个值的大小就是irq
地址;
- ( 1 ) 示例 :
8.分支指令 : 当异常发生的时候, 需要跳转到对应的异常处理指令中;
- ( 1 ) 分支指令语法格式 :
b{条件} 地址
, 如果①满足条件, 就跳转到 地址 位置, 如果②不满足条件, 就执行下面的语句, ③如果没有条件, 就是 100% 执行; - ( 2 ) 代码示例 :
b reset
, 异常发生时, 直接跳转到reset
标号处执行代码;
- ( 1 ) 分支指令语法格式 :
9.装载指令 :
- ( 1 ) 装载指令语法格式 :
ldr 寄存器, 地址
, 将 地址 中存放的数据 装载 到 寄存器中; - ( 2 ) 代码示例 :
- a.定义标号 ( 函数 ) : 定义要执行的指令的标号
irq
, 即跳转到该标号处, 就开始执行标号下面的指令,irq : nop
; - b.定义标号 ( 变量 ) : 定义一个标号
_irq
, 用于存放一个 32 位的值, 这里用于存放 上面 定义的标号 地址,_irq .word irq
- c.装载地址到 pc 寄存器 :
ldr pc, _irq
, 将_irq
标号中存放的值, 这个值是irq
标号的地址, 就是跳转到该地址去执行指令;
- a.定义标号 ( 函数 ) : 定义要执行的指令的标号
- ( 1 ) 装载指令语法格式 :
- 10.完整汇编代码示例 :
@****************************
@File:start.S
@
@异常处理框架
@****************************
.text @ 宏 指明代码段
.global _start @ 伪指令声明全局开始符号
_start: @ 程序入口标志
b reset @ reset 复位异常
ldr pc, _undefined_instruction @ 未定义异常, 将 _undefined_instruction 值装载到 pc 指针中
ldr pc, _software_interrupt @ 软中断异常
ldr pc, _prefetch_abort @ 预取指令异常
ldr pc, _data_abort @ 数据读取异常
ldr pc, _not_used @ 占用 0x00000014 地址
ldr pc, _irq @ 普通中断异常
ldr pc, _fiq @ 软中断异常
_undefined_instruction: .word undefined_instruction @ _undefined_instruction 标号存放了一个值, 该值是 32 位地址 undefined_instruction, undefined_instruction 是一个地址
_software_interrupt: .word software_interrupt @ 软中断异常
_prefetch_abort: .word prefetch_abort @ 预取指令异常 处理
_data_abort: .word data_abort @ 数据读取异常
_not_used: .word not_used @ 空位处理
_irq: .word irq @ 普通中断处理
_fiq: .word fiq @ 快速中断处理
undefined_instruction: @ undefined_instruction 地址存放要执行的内容
nop
software_interrupt: @ software_interrupt 地址存放要执行的内容
nop
prefetch_abort: @ prefetch_abort 地址存放要执行的内容
nop
data_abort: @ data_abort 地址存放要执行的内容
nop
not_used: @ not_used 地址存放要执行的内容
nop
irq: @ irq 地址存放要执行的内容
nop
fiq: @ fiq 地址存放要执行的内容
nop
reset: @ reset 地址存放要执行的内容
nop
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
2. 链接器脚本
gboot.lds 链接器脚本 代码解析 :
- 1.指明输出格式 ( 处理器架构 ) : 使用
OUTPUT_ARCH(架构名称)
指明输出格式, 即处理器的架构, 这里是 arm 架构的,OUTPUT_ARCH(arm)
; - 2.指明输出程序的入口 : 设置编译输出的程序入口位置, 语法为
ENTRY(入口位置)
, 在上面的 Start.S 中设置的程序入口是_start
, 代码为ENTRY(_start)
; - 3.设置代码段 : 使用
.text :
设置代码段; - 4.设置数据段 : 使用
.data :
设置数据段; - 5.设置 BSS 段 : 使用
.bss :
设置 BSS 段;
- ( 1 ) 记录 BSS 段的起始地址 :
bss_start = .;
; - ( 2 ) 记录 BSS 段的结束地址 :
bss_end = .;
;
- ( 1 ) 记录 BSS 段的起始地址 :
- 6.对齐 : 每个段都需要设置内存的对齐格式, 使用
. = ALIGN(4);
设置四字节对齐即可; - 7.代码示例 :
OUTPUT_ARCH(arm) /*指明处理器结构*/
ENTRY(_start) /*指明程序入口 在 _start 标号处*/
SECTIONS {
. = 0x50008000; /*整个程序链接的起始位置, 根据开发板确定, 不同开发板地址不一致*/
. = ALIGN(4); /*对齐处理, 每段开始之前进行 4 字节对齐*/
.text : /*代码段*/
{
start.o (.text) /*start.S 转化来的代码段*/
*(.text) /*其它代码段*/
}
. = ALIGN(4); /*对齐处理, 每段开始之前进行 4 字节对齐*/
.data : /*数据段*/
{
*(.data)
}
. = ALIGN(4); /*对齐处理, 每段开始之前进行 4 字节对齐*/
bss_start = .; /*记录 bss 段起始位置*/
.bss : /*bss 段*/
{
*(.bss)
}
bss_end = .; /*记录 bss 段结束位置*/
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
3. Makefile 编译脚本
makefile 文件编写 :
- 1.通用规则 ( 汇编文件编译规则 ) : 汇编文件 编译 成同名的 .o 文件, 文件名称相同, 后缀不同,
%.o : %.S
, 产生过程是arm-linux-gcc -g -c $^
, 其中^
标识是所有的依赖文件, 在该规则下 start.S 会被变异成 start.o ; - 2.通用规则 ( C 文件编译规则 ) : C 代码编译成同名的 .o 文件,
%.o : %.c
, 产生过程是arm-linux-gcc -g -c $^
; - 3.设置最终目标 : 使用
all:
设置最终编译目标;
- ( 1 ) 依赖文件 : 产生最终目标需要依赖 start.o 文件, 使用
all: start.o
表示最终目标需要依赖该文件; - ( 2 ) 链接过程 :
arm-linux-ld -Tgboot.lds -o gboot.elf $^
, 需要使用链接器脚本进行连接, ①链接工具是 arm-linux-ld 工具, ②使用-Tgboot.lds
设置链接器脚本 是刚写的 gboot.lds 链接器脚本, ③输出文件是 gboot.elf 这是个中间文件, ④ 依赖文件是$^
代表所有的依赖; - ( 3 ) 转换成可执行二进制文件 :
arm-linux-objcopy -O binary gboot.elf gboot.bin
, 使用-O binary
设置输出二进制文件, 依赖文件是gboot.elf
, 输出的可执行二进制文件 即 结果是gboot.bin
;
- ( 1 ) 依赖文件 : 产生最终目标需要依赖 start.o 文件, 使用
- 4.makefile 文件内容 :
all: start.o #依赖于 start.o
arm-linux-ld -Tgboot.lds -o gboot.elf $^ #使用链接器脚本, 将 start.o 转为 gboot.elf
arm-linux-objcopy -O binary gboot.elf gboot.bin #将 gboot.elf 转化为可以直接在板子上执行的 gboot.bin 文件
%.o : %.S #通用规则, 如 start.o 是由 start.S 编译来的, -c 是只编译不链接
arm-linux-gcc -g -c $^
%.o : %.c #通用规则, 如 start.o 是由 start.c 编译来的, -c 是只编译不链接
arm-linux-gcc -g -c $^
.PHONY: clean
clean: #清除编译信息
rm *.o *.elf *.bin
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
4. 编译输出可执行文件
编译过程 :
- 1.文件准备 : 将 汇编代码 ( start.S ) 链接器脚本 ( gboot.lds ) makefile 文件 拷贝到编译目录 ;
- 2.执行编译命令 :
make
; - 3.编译结果 : 可以看到 生成了 编译目标文件 start.o, 链接文件 gboot.elf, 可执行的二进制文件 gboot.bin ;
本博客的参考文章及相关资料下载 :
- 1.ARM 架构参考手册 ( ARM Architecture Reference Manual ) : https://download.csdn.net/download/han1202012/8324641
- 2.汇编参考手册 : https://download.csdn.net/download/han1202012/8328375
- 3.本博客代码下载 : https://download.csdn.net/download/han1202012/10392901
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/80163777
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)