第014课 异常与中断

来自百问网嵌入式Linux wiki
Baiwen root讨论 | 贡献2018年1月18日 (四) 15:22的版本

第001节_概念引入与处理流程

取个场景解释中断 假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。 问:这个母亲怎么才能知道这个小孩醒? 1 过一会打开一次房门,看婴儿是否睡醒,让后接着看书 2 一直等到婴儿发出声音以后再过去查看,期间都在读书

第一种 叫做查询方式: 优点:简单 缺点: 累 写程序如何:

while(1)
{
	1 read book(读书)
	2 open door(开门)
	if()
		return(read book)
	else
		照顾小孩
	
}

第二种叫中断方式: 优点:不累 缺点:复杂

写程序:

 

while(1)
{
	read book
	中断服务程序()//如何被调用?
	{
	处理照顾小孩
	}
}

我们还是看看母亲被小孩哭声打断如何照顾小孩?

母亲的处理过程 1 平时看书 2 发生了各种声音,如何处理这些声音 有远处的猫叫(听而不闻,忽略) 门铃声有快递(开门收快递) 小孩哭声(打开房门,照顾小孩) 3 母亲的处理 只会处理门铃声和小孩哭声 a 现在书中放入书签,合上书(保存现场) b 去处理 (调用对应的中断服务程序) c 继续看书(恢复现场)

不同情况,不同处理 a 对于门铃:开门取快件 b 对于哭声:照顾小孩

我们将母亲的处理过程抽象化 母亲的头脑相当于CPU 耳朵听到声音会发送信号给脑袋,声音来源有很多种,有远处的猫叫,门铃声,小孩哭声。这些声音传入耳朵,再由耳朵传给大脑,除了这些可以中断母亲的看书,还有其他情况,比如身体不舒服,有只蜘蛛掉下来,对于特殊情况无法回避,必须立即处理

对比我们得arm系统

---》图2 ![](需要替换://i.imgur.com/Yg9tRdq.png)

有CPU 有中断控制器,我们的中断源发送 中断控制器可以发信号给CPU告诉它发生了那些紧急情况 中断源有 按键 定时器 有其它的(比如网络数据) 这些信号都可以发送信号给中断控制器,再由中断控制器发送信号给CPU表明有这些中断产生了,这些成为中断(属于一种异常)

还有什么可以中断CPU运行 指令不对,数据访问有问题 reset信号,这些都可以中断CPU 这些成为异常中断

重点在于 保存现场以及恢复现场

处理过程 a 保存现场(各种寄存器) b 处理异常(中断属于一种异常) c 恢复现场


arm对异常(中断)处理过程 1 初始化: a 设置中断源,让它可以产生中断 b 设置中断控制器(可以屏蔽某个中断,优先级) c 设置CPU总开关,(使能中断)

2 执行其他程序:正常程序


3 产生中断:按下按键--->中断控制器--->CPU

4 cpu每执行完一条指令都会检查有无中断/异常产生 5 发现有中断/异常产生,开始处理 对于不同的异常,跳去不同的地址执行程序 这地址上,只是一条跳转指令,跳去执行某个函数 (地址) 指的是异常向量 如下就是异常向量表 对于不同的异常都有一条跳转指令


.globl _start
_start: b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**
	ldr	pc, _fiq
//我们先在0x18这里放 ldr pc ,__irq,于是cpu最终会跳去执行__irq代码
//保护现场,调用处理函数,恢复现场

3-5都是硬件强制做的

6 这些函数做什么事情()软件做的 a 保存现场(各种寄存器) b 处理异常(中断): 分辨中断源 再调用不同的处理函数 c 恢复现场

对比母亲的处理过程来比较arm中断的处理过程

中断处理程序怎么被调用? CPU--->0x18 --跳转到其他函数->

做保护现场
调用函数 

分辨中断源 调用对应函数

恢复现场

cpu到0x18是由硬件决定的,跳去执行更加复杂函数(由软件决定)

第002节_CPU模式(Mode)_状态(State)与寄存器

这节课我们来讲CPU的工作模式(Mode) 状态(State)寄存器 7种Mode:

usr/sys
undefined(und)
Supervisor(svc)
Abort(abt)
IRQ(irq)
FIQ(fiq)

2种State:

ARM state
Thumb state

寄存器:

通用寄存器
备份寄存器(banked register)
当前程序状态寄存器(Current Program Status Register);CPSR
CPSR的备份寄存器:SPSR(Save Program Status Register)

我们仍然以这个母亲为例讲解这个CPU模式 这个母亲无压力看书 -->(正常模式) 要考试,看书--->(兴奋模式) 生病---->(异常模式)

可以参考书籍 《ARM体系结构与编程》作者:杜春雷

对于ARM CPU有7种模式 1 usr :类比 正常模式 2 sys :类比的话兴奋模式 3 5种异常模式:(2440用户手册72页) 3.1 und :未定义模式 3.2 svc :管理模式 3.3 abt :终止模式 a 指令预取终止(读写某条错误的指令导致终止运行) b 数据访问终止 (读写某个地址,这个过程出错) 都会进入终止模式 3.4 IRQ: 中断模式 3.5 FIQ: 快中断模式

我们可以称以下6种为特权模式

und :未定义模式
svc :管理模式
abt :终止模式
IRQ :中断模式
FIQ :快中断模式
sys :系统模式

usr用户模式(不可直接进入其他模式) 可以编程操作CPSR直接进入其他模式 ![](需要替换://i.imgur.com/cgghj9L.png) 这个图是有关各个模式下能访问寄存器的,再讲这个图之前我们先引入 2种state

CPU有两种state 1 ARM state 使用ARM指令集,每个指令4byte 2 Thumb state 使用的是Thumb指令集,每个指令2byte

比如同样是 mov R0, R1 编译后 对于ARM指令集要占据4个字节:机器码 对于Thumb指令集占据2个字节:机器码 引入Thumb减少存储空间

百度搜索ARM指令集和Thumb指令的区别


现在先区分下ARM指令集与Thumb指令集

Thumb 指令可以看作是 ARM 指令压缩形式的子集,是针对代码密度的问题而提出的,它具有 16 位的代码密度但是它不如ARM指令的效率高 .Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集.因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集,比如,所有异常自动进入 ARM 状态.在编写 Thumb 指令时,先要使用伪指令 CODE16 声明,而且在 ARM 指令中要使用 BX指令跳转到 Thumb 指令,以切换处理器状态.编写 ARM 指令时,则可使用伪指令 CODE32声明.


下节课会演示使用Thumb指令集编译,看是否生成的bin文件会变小很多

![](需要替换://i.imgur.com/cgghj9L.png) 在每种模式下都有R0 ~ R15 在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器 比如

mov R0, R8
mov R0, R8

在System 模式下访问的是R0 ~ R8,在所有模式下访问R0都是同一个寄存器

mov R0,R8_fiq

但是在FIQ模式下,访问R8是访问的FIQ模式专属的R8寄存器,不是同一个物理上的寄存器

在这五种异常模式中每个模式都有自己专属的R13 R14寄存器,R13用作SP(栈) R14用作LR(返回地址) LR是用来保存发生异常时的指令地址

为什么快中断(FIQ)有那么多专属寄存器,这些寄存器称为备份寄存器


回顾一下中断的处理过程 1 保存现场(保存被中断模式的寄存器) 就比如说我们的程序正在系统模式/用户模式下运行,当你发生中断时,需要把R0 ~ R14这些寄存器全部保存下来,让后处理异常,最后恢复这些寄存器 但如果是快中断,那么我就不需要保存 系统/用户模式下的R8 ~ R12这几个寄存器,在FIQ模式下有自己专属的R8 ~ R12寄存器,省略保存寄存器的时间,加快处理速度 但是在Linux中并不会使用FIQ模式 2 处理 3 恢复现场

CRSR当前程序状态寄存器,这是一个特别重要的寄存器 SPSR保存的程序状态寄存器,他们格式如下 ![](需要替换://i.imgur.com/P5Tkmq5.png)

首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode) 我们可以读取这5位来判断CPU处于哪一种模式,也可以修改这一种模式位,让其修改这种模式 假如你当前处于用户模式下,是没有权限修改这些位的 M4 ~ M0对应什么值,会有说明 ![](需要替换://i.imgur.com/YShQWjP.png)

查看其他位 Bit5 State bits表示CPU工作与Thumb State还是ARM State用的指令集是什么 Bit6 FIQ disable当bit6等于1时,FIQ是不工作的 Bit7 IRQ disable当bit5等于1时,禁止所有的IRQ中断,这个位是IRQ的总开关 Bit8 ~ Bit27是保留位 Bite28 ~ Bit31是状态位, 什么是状态位,比如说执行一条指令 cmp R0, R1 如果R0 等于 R1 那么zero位等于1,这条指令影响 Z 位,如果R0 == R1,则Z = 1

beq跳转到xxx这条指令会判断Bit30是否为1,是1的话则跳转,不是1的话则不会跳转 使用 Z 位,如果 Z 位等于1 则跳转,这些指令是借助状态位实现的


SPSR保存的程序状态寄存器 表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR 就比如我我的程序在系统模式下运行 CPSR是某个值,当发生中断时会进入irq模式,这个CPSR_irq就保存系统模式下的CPSR 我们来看看发生异常时CPU是如何协同工作的 进入异常的处理流程(硬件) ![](需要替换://i.imgur.com/rI6ZYwF.png) 我们来翻译一下: 发生异常时,我们的CPU会做什么事情 1把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址) 它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况 2 把CPSR保存在SPSR里面(某一种异常模式下SPSR里面的值等于CPSR) 3 修改CPSR的模式为进入异常模式(修改CPSR的M4 ~ M0进入异常模式) 4 跳到向量表


退出异常怎么做? ![](需要替换://i.imgur.com/pJzcOIV.png) 1 让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset) 减去什么值呢? 也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值 ![](需要替换://i.imgur.com/9lPkInF.png) 如果发生的是SWI可以把 R14_svc复制给PC 如果发生的是IRQ可以把R14_irq的值减去4赋值给PC 2 把CPSR的值恢复(CPSR 值等于 某一个一场模式下的SPSR) 3 清中断(如果是中断的话,对于其他异常不用设置)

第003节_不重要_Thumb指令集程序示例

在上节视频里说ARMCPU有两种状态 ARM State 每条指令会占据4byte Thumb State 每条指令占据2byte

我们说过Thumb指令集并不重要,本节演示把一个程序使用Thumb指令集来编译它 使用上一章节的重定位代码 打开Makefile和Start.S

Makefile文件

   
all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o init.o init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o start.o start.S
	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis

对于使用Thumb指令集

   

all:
	arm-linux-gcc -mthumb -c -o led.o led.c//只需要在arm-linux-gcc加上 mthumb命令即可
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o init.o init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o start.o start.S
	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis

改进

 
all: led.o uart.o init.o main.o start.o //all依赖led.o uart.o init.o main.o start.o
	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis
	
%.o : %.c
	arm-linux-gcc -mthumb -c -o $@ $< //对于所有的.c文件使用规则就可以使用thumb指令集编译 $@表示目标 $<表示第一个依赖

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


对start.S需要修改代码

原重定位章节Start.S文件


.text
.global _start

_start:

	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt

使用thumb指令集的Start.S文件

 

.text
.global _start
.code 32 //表示后续的指令使用ARM指令集
_start:

	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	/* 怎么从ARM State切换到Thumb State? */
	adr r0, thumb_func //定义此标号的地址
	add r0, r0, #1  /* bit0=1时, bx就会切换CPU State到thumb state */
	bx r0
	
.code 16 //下面都使用thumb指令集	
thumb_func:	//需要得到这个标号的地址
	/*下面就是使用thumb指令来执行程序*/
	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr r0, =main  /* 绝对跳转, 跳到SDRAM ,先把main的地址赋值给R0 */
	mov pc, r0  /*让后再移动到PC*/

halt:
	b halt


上传代码编译测试 出现错误,如下 init.o(.text+0x6c):In function 'sdram_init2'; undefined reference to 'memcpy' 发现是init,o里sdram——init2使用的了memcpy函数

查看init.c

 

#include "s3c2440_soc.h"

void sdram_init(void)
{
	BWSCON = 0x22000000;

	BANKCON6 = 0x18001;
	BANKCON7 = 0x18001;

	REFRESH  = 0x8404f5;

	BANKSIZE = 0xb1;

	MRSRB6   = 0x20;
	MRSRB7   = 0x20;
}

#if 0


/**************************************************************************   
* 设置控制SDRAM的13个寄存器
* 使用位置无关代码
**************************************************************************/   
void memsetup(void)
{
	unsigned long *p = (unsigned long *)MEM_CTL_BASE;	
	p[0] = 0x22111110;		//BWSCON
	p[1] = 0x00000700;		//BANKCON0
	p[2] = 0x00000700;		//BANKCON1
	p[3] = 0x00000700;		//BANKCON2
	p[4] = 0x00000700;		//BANKCON3	
	p[5] = 0x00000700;		//BANKCON4
	p[6] = 0x00000700;		//BANKCON5
	p[7] = 0x00018005;		//BANKCON6
	p[8] = 0x00018005;		//BANKCON7
	p[9] = 0x008e07a3;		//REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
	p[10] = 0x000000b2;		//BANKSIZE
	p[11] = 0x00000030;		//MRSRB6
	p[12] = 0x00000030;		//MRSRB7
}


#endif

/*下面函数使用了memcpy函数,显然是编译器的操作,使用了memcpy把数组里的值从代码段拷贝到了arr局部变量里 是否可以禁用掉memcpy*/

void sdram_init2(void)
{
	unsigned int arr[] = {
		0x22000000, 	//BWSCON
		0x00000700, 	//BANKCON0
		0x00000700, 	//BANKCON1
		0x00000700, 	//BANKCON2
		0x00000700, 	//BANKCON3	
		0x00000700, 	//BANKCON4
		0x00000700, 	//BANKCON5
		0x18001, 	//BANKCON6
		0x18001, 	//BANKCON7
		0x8404f5, 	//REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
		 0xb1,	//BANKSIZE
		 0x20,	//MRSRB6
		 0x20,	//MRSRB7

		};
	volatile unsigned int * p = (volatile unsigned int *)0x48000000;
	int i;

	for (i = 0; i < 13; i++)
	{
		*p = arr[i];
		p++;
	}
	
}

文章说没有什么方法禁用memecpy但是可以修改这些变量

比如说将其修改为静态变量,这些数据就会放在数据段中,最终重定位时会把数据类拷贝到对应的arr地址里面去

 
void sdram_init2(void)
{
	const static unsigned int arr[] = {  //加上const 和static
		0x22000000, 	//BWSCON
		0x00000700, 	//BANKCON0
		0x00000700, 	//BANKCON1
		0x00000700, 	//BANKCON2
		0x00000700, 	//BANKCON3	
		0x00000700, 	//BANKCON4
		0x00000700, 	//BANKCON5
		0x18001, 	//BANKCON6
		0x18001, 	//BANKCON7
		0x8404f5, 	//REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
		 0xb1,	//BANKSIZE
		 0x20,	//MRSRB6
		 0x20,	//MRSRB7

		};
	volatile unsigned int * p = (volatile unsigned int *)0x48000000;
	int i;

	for (i = 0; i < 13; i++)
	{
		*p = arr[i];
		p++;
	}
	
}

拷贝进行实验

得出bin文件有1.4k左右 ![](https://i.imgur.com/0RY4sBN.png)

查看之前的文件使用ARM指令集是2K左右

![](https://i.imgur.com/yei76DQ.png)

查看反汇编代码

 
sdram.elf:     file format elf32-littlearm

Disassembly of section .text:

/*前面这些ARM指令还是占用4个字节*/
30000000 <_start>:
30000000:	e3a00453 	mov	r0, #1392508928	; 0x53000000
30000004:	e3a01000 	mov	r1, #0	; 0x0
30000008:	e5801000 	str	r1, [r0]
3000000c:	e3a00313 	mov	r0, #1275068416	; 0x4c000000
30000010:	e3e01000 	mvn	r1, #0	; 0x0
30000014:	e5801000 	str	r1, [r0]
30000018:	e59f005c 	ldr	r0, [pc, #92]	; 3000007c <.text+0x7c>
3000001c:	e3a01005 	mov	r1, #5	; 0x5
30000020:	e5801000 	str	r1, [r0]
30000024:	ee110f10 	mrc	15, 0, r0, cr1, cr0, {0}
30000028:	e3800103 	orr	r0, r0, #-1073741824	; 0xc0000000
3000002c:	ee010f10 	mcr	15, 0, r0, cr1, cr0, {0}
30000030:	e59f0048 	ldr	r0, [pc, #72]	; 30000080 <.text+0x80>
30000034:	e59f1048 	ldr	r1, [pc, #72]	; 30000084 <.text+0x84>
30000038:	e5801000 	str	r1, [r0]
3000003c:	e3a01000 	mov	r1, #0	; 0x0
30000040:	e5910000 	ldr	r0, [r1]
30000044:	e5811000 	str	r1, [r1]
30000048:	e5912000 	ldr	r2, [r1]
3000004c:	e1510002 	cmp	r1, r2
30000050:	e59fd030 	ldr	sp, [pc, #48]	; 30000088 <.text+0x88>
30000054:	03a0da01 	moveq	sp, #4096	; 0x1000
30000058:	05810000 	streq	r0, [r1]
3000005c:	e28f0004 	add	r0, pc, #4	; 0x4
30000060:	e2800001 	add	r0, r0, #1	; 0x1
30000064:	e12fff10 	bx	r0

30000068 <thumb_func>:
30000068:	f94ef000 	bl	30000308 <sdram_init>
3000006c:	f9fef000 	bl	3000046c <copy2sdram>
30000070:	fa24f000 	bl	300004bc <clean_bss>
/**下面的thumb指令占据2个字节**/
30000074:	4805      	ldr	r0, [pc, #20]	(3000008c <.text+0x8c>)
30000076:	4687      	mov	pc, r0

30000078 <halt>:
30000078:	e7fe      	b	30000078 <halt>
3000007a:	0000      	lsl	r0, r0, #0
3000007c:	0014      	lsl	r4, r2, #0
3000007e:	4c00      	ldr	r4, [pc, #0]	(30000080 <.text+0x80>)
30000080:	0004      	lsl	r4, r0, #0
30000082:	4c00      	ldr	r4, [pc, #0]	(30000084 <.text+0x84>)
30000084:	c011      	stmia	r0!,{r0, r4}
30000086:	0005      	lsl	r5, r0, #0
30000088:	1000      	asr	r0, r0, #0
3000008a:	4000      	and	r0, r0
3000008c:	04fd      	lsl	r5, r7, #19
3000008e:	3000      	add	r0, #0

如果你的flash很小的话可以考虑使用Thumb指令集

烧写进去看是否可以运行 测试结果没有任何问题 Thumb指令集后面没有任何作用,只是简单作为介绍

第004节_und异常模示程序示例

写一个程序故意让其发生未定义异常,让后处理这个异常 查看uboot中源码 uboot\u-boot-1.1.6\cpu\arm920t 打开start.S

    /*code: 28 -- 72*/
    #include <config.h>
    #include <version.h>
    
    
    /*
     *************************************************************************
     *
     * Jump vector table as in table 3.1 in [1]
     *
     *************************************************************************
     */
    #define GSTATUS2   (0x560000B4)
    #define GSTATUS3   (0x560000B8)
    #define GSTATUS4   (0x560000BC)
    
    #define REFRESH(0x48000024)
    #define MISCCR (0x56000080)
    
    #define LOCKTIME	0x4C000000	/* R/W, PLL lock time count register */
    #define MPLLCON		0x4C000004	/* R/W, MPLL configuration register */
    #define UPLLCON		0x4C000008	/* R/W, UPLL configuration register */
    #define CLKCON		0x4C00000C	/* R/W, Clock generator control reg. */
    #define CLKSLOW		0x4C000010	/* R/W, Slow clock control register */
    #define CLKDIVN		0x4C000014	/* R/W, Clock divider control */
    
    /******下面这些就是异常向量表*****/
    .globl _start
    _start:	b   reset
    	ldr	pc, _undefined_instruction
    	ldr	pc, _software_interrupt
    	ldr	pc, _prefetch_abort
    	ldr	pc, _data_abort
    	ldr	pc, _not_used
    	ldr	pc, _irq
    	ldr	pc, _fiq
    
    _undefined_instruction:	.word 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
    
    	.balignl 16,0xdeadbeef

手册异常向量表定义

![](https://i.imgur.com/4iuxNLR.png)

接下来我们写程序

    .text
    .global _start
    
    _start:
    	b reset  /* vector 0 : reset */  //一上电复位,是从0地址开始执行,跳到reset处
    	b do_und /* vector 4 : und */ //如果发生未定义指令异常,就会跳到0x04地址未定义指令异常处,执行do_und程序
    
    /*假设一上电从0地址开始执行,reset,做一系列初始化之后
    *故意加入一条未定义指令
    	
    und_code:
    	.word 0xdeadc0de  /* 未定义指令 */
    CPU发现无法执行此条指令时,就会发生未定义指令异常,就会执行do_und
    	bl print2
    */
    do_und:
    	/* 执行到这里之前:
    	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    	 * 2. SPSR_und保存有被中断模式的CPSR
    	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    	 * 4. 跳到0x4的地方执行程序 
    	 */
//需要从新设置sp栈,指向某一块没有使用的地址    
    	/* sp_und未设置, 先设置它 */
    	ldr sp, =0x34000000
    
    	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
		/* 发生异常时,当前被中断的地址会保存在lr寄存器中 先减后存*/
    	/* lr是异常处理完后的返回地址, 也要保存 */
    	stmdb sp!, {r0-r12, lr}
    	
    	/* 保存现场 */
    	/* 处理und异常 */
    	mrs r0, cpsr//把cpsr的值读入r0
    	ldr r1, =und_string//把下面的字符串地址赋值给r1

    	bl printException
    	
		/* 这些寄存器保存在栈中,把他读取出来就可以了*/
    	/* 恢复现场 */
		/* 先读后加*/
		/* 把r0 ~ r12的值从栈中都取出来,并且把原来保存的lr值,赋值到pc中去*/
    	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */

/*

.string "str"

Copy the characters in str to the object file. You may specify more than one string to copy, separated by commas. Unless otherwise specified for a particular machine, the assembler marks the end of each string with a 0 byte. You can use any of the escape sequences described in section Strings. 我们使用.str会自动加上结束符

  • /
    	
    und_string:
    	.string "undefined instruction exception"
    
    
    reset:
    	/* 关闭看门狗 */
    	ldr r0, =0x53000000
    	ldr r1, =0
    	str r1, [r0]
    
    	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    	ldr r0, =0x4C000000
    	ldr r1, =0xFFFFFFFF
    	str r1, [r0]
    
    	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    	ldr r0, =0x4C000014
    	ldr r1, =0x5
    	str r1, [r0]
    
    	/* 设置CPU工作于异步模式 */
    	mrc p15,0,r0,c1,c0,0
    	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
    	mcr p15,0,r0,c1,c0,0
    
    	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
    	 *  m = MDIV+8 = 92+8=100
    	 *  p = PDIV+2 = 1+2 = 3
    	 *  s = SDIV = 1
    	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
    	 */
    	ldr r0, =0x4C000004
    	ldr r1, =(92<<12)|(1<<4)|(1<<0)
    	str r1, [r0]
    
    	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
    	 * 然后CPU工作于新的频率FCLK
    	 */
    	
    	
    
    	/* 设置内存: sp 栈 */
    	/* 分辨是nor/nand启动
    	 * 写0到0地址, 再读出来
    	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
    	 * 否则就是nor启动
    	 */
    	mov r1, #0
    	ldr r0, [r1] /* 读出原来的值备份 */
    	str r1, [r1] /* 0->[0] */ 
    	ldr r2, [r1] /* r2=[0] */
    	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
    	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
    	moveq sp, #4096  /* nand启动 */
    	streq r0, [r1]   /* 恢复原来的值 */
    
    	bl sdram_init
    	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */
    
    	/* 重定位text, rodata, data段整个程序 */
    	bl copy2sdram
    
    	/* 清除BSS段 */
    	bl clean_bss
    
    	bl uart0_init
    
    	bl print1
    	/* 故意加入一条未定义指令 */
    und_code:
    	.word 0xff123456  /* 未定义指令 */
    	bl print2
    
    	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
    	b halt

如何处理这个异常呢? 直接print打印一句话,新建一个 exception.c文件

    #include "uart.h"
    
    void printException(unsigned int cpsr, char *str) //cpsr打印相应的寄存器,str打印一个字符串
    {
    	puts("Exception! cpsr = ");\\打印cpsr
    	printHex(cpsr);//输出cpsr的值
    	puts(" ");//输出空格
    	puts(str);//输出str值
    	puts("\n\r");//回车,换行
    }

我们打开之前编译过的程序的反汇编文件 里面一定包含了保存恢复

    30000084 <delay>:
    30000084:	e1a0c00d 	mov	ip, sp
    30000088:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc} //保存 d是减 b是存
    3000008c:	e24cb004 	sub	fp, ip, #4	; 0x4
    30000090:	e24dd004 	sub	sp, sp, #4	; 0x4
    30000094:	e50b0010 	str	r0, [fp, #-16]
    30000098:	e51b3010 	ldr	r3, [fp, #-16]
    3000009c:	e2433001 	sub	r3, r3, #1	; 0x1
    300000a0:	e50b3010 	str	r3, [fp, #-16]
    300000a4:	e51b3010 	ldr	r3, [fp, #-16]
    300000a8:	e3730001 	cmn	r3, #1	; 0x1
    300000ac:	0a000000 	beq	300000b4 <delay+0x30>
    300000b0:	eafffff8 	b	30000098 <delay+0x14>
    300000b4:	e89da808 	ldmia	sp, {r3, fp, sp, pc}//恢复,先读后加

上传编译

修改makefile添加文件

    all: start.o led.o uart.o init.o main.o exception.o
    	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
    	arm-linux-ld -T sdram.lds $^ -o sdram.elf 
		#用$ ^来包含所有的依赖
    	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
    	arm-linux-objdump -D sdram.elf > sdram.dis
    clean:
    	rm *.bin *.o *.elf *.dis
    	
    %.o : %.c
    	arm-linux-gcc -c -o $@ $<
    
    %.o : %.S
    	arm-linux-gcc -c -o $@ $<
    	*.dis

编译成功烧写 没有输出我们想要的字符串 很多同学想学会如何调试程序 这里我们演示

    sdram:
 
    
    	bl print1 //添加print1
    	/* 故意加入一条未定义指令 */
    und_code:
    	.word 0xdeadc0de  /* 未定义指令 */
    	bl print2 //添加print2,实现这两个函数,来打印
    
    	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
    	b halt
	
实现print1 print2这两个打印函数,在uart.c这个文件里

    void print1(void)
    {
    	puts("abc\n\r");
    }
    
    void print2(void)
    {
    	puts("123\n\r");
    }
    
上传代码烧写,发现print1 print2并未执行成功

发现在start.S并未初始化 uart0_init(),删除main.c中的uart0_init()初始化函数

    	ldr pc, =sdram
    sdram:
    	bl uart0_init
    
    	bl print1
    	/* 故意加入一条未定义指令 */
    und_code:
    	.word 0xff123456  /* 未定义指令 */
    	bl print2
    
    	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
    	b halt

加上uart0_init,再次编译烧写 程序正常运行,print1 print2全部打印,表明未定义指令并未运行,难道这个地址是一个已经定义的地址

打开2440芯片手册 找到ARM指令集

![](https://i.imgur.com/ZSBOZZU.png)

发现竟然是SWI指令,CPU可以识别出来,他不是一条未定义指令 我们得找到一条CPU不能识别的指令,定义为0x03000000

    	ldr pc, =sdram
    sdram:
    	bl uart0_init
    
    	bl print1
    	/* 故意加入一条未定义指令 */
    und_code:
    	.word 0x03000000  /* 未定义指令 */
    	bl print2
    
    	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
    	b halt

编译烧写执行

打印了未定义指令异常CPSR地址,打印了字符串,最后执行main函数 .word 0xdeadcode /* 也是一条未定义指令 只要指令地址对不上上表就是未定义指令*/

我们查看下cpsr是否处于未定义模式

bit[4:0]表示CPU模式 11011 果然处于und模式


我们看看这个程序做了什么事情

    .text
    .global _start
/*一上电复位,从0地址开始执行
跳到   reset:
做了一系列初始化
当执行到0xdeadc0de这条指令时候,CPU根本就不知道这条指令什么意思
    und_code:
    	.word 0xdeadc0de  /* 未定义指令 */
    	bl print2
让后就发生未定义指令异常,他会把下一条指令的地址保存到异常模式的LR寄存器

    	/* 执行到这里之前已经发生了很多事情
    	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    	 * 2. SPSR_und保存有被中断模式的CPSR
    	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    	 * 4. 跳到0x4的地方执行程序 
    	 * 
    	 * 设置栈 sp是指und的地址
    	 * sp_und未设置, 先设置它
    	 * 	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    	 * lr是异常处理完后的返回地址, 也要保存 */
    	 * 保存现场 */
    	 * 处理und异常 */
    	 * 恢复sp
    	 * cpu就会切换到之前的模式
    	 */
*/    
.text
.global _start

_start:
	b reset  /* vector 0 : reset */
	b do_und /* vector 4 : und */
    
    und_addr:
    	.word do_und
    
    do_und:
    	/* 执行到这里之前:
    	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    	 * 2. SPSR_und保存有被中断模式的CPSR
    	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    	 * 4. 跳到0x4的地方执行程序 
    	 */
    
    	/* sp_und未设置, 先设置它 */
    	ldr sp, =0x34000000
    
    	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    	/* lr是异常处理完后的返回地址, 也要保存 */
    	stmdb sp!, {r0-r12, lr}  
    	
    	/* 保存现场 */
    	/* 处理und异常 */
    	mrs r0, cpsr
    	ldr r1, =und_string
    	bl printException
    	
    	/* 恢复现场 */
    	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    	
    und_string:
    	.string "undefined instruction exception"


程序改进

源程序

.text
.global _start

_start:
	b reset  /* vector 0 : reset */
/*使用b命令跳转  相对跳转*/
	b do_und /* vector 4 : und */

do_und:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* sp_und未设置, 先设置它 */
	ldr sp, =0x34000000

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string

	/*这里又使用bl指令跳转,如果是nand启动,这个函数在4k之外,这个函数必定出错 为了保险,跳转到sdram中执行程序*/
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
    und_string:
    	.string "undefined instruction exception"
    
    
reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	bl uart0_init

	bl print1
	/* 故意加入一条未定义指令 */
und_code:
	.word 0xdeadc0de  /* 未定义指令 */
	bl print2

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt

改进后代码

    .text
    .global _start
    
    _start:
    	b reset  /* vector 0 : reset */
/*跳转到sdram执行这个函数,那么这个函数一定在sdram中
我们需要指定让他去前面这块内存去读这个值,担心如果这个文件很大,超过4Knand就没法去读这个文件*/
    	ldr pc, und_addr /* vector 4 : und */

/*增加如下 查看反汇编,在08的地址读让后跳到3c*/    
    und_addr:
    	.word do_und
    
    do_und:
    	/* 执行到这里之前:
    	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    	 * 2. SPSR_und保存有被中断模式的CPSR
    	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    	 * 4. 跳到0x4的地方执行程序 
    	 */
    
    	/* sp_und未设置, 先设置它 */
    	ldr sp, =0x34000000
    
    	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    	/* lr是异常处理完后的返回地址, 也要保存 */
    	stmdb sp!, {r0-r12, lr}  
    	
    	/* 保存现场 */
    	/* 处理und异常 */
    	mrs r0, cpsr
    	ldr r1, =und_string
    	bl printException
    	
    	/* 恢复现场 */
    	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    	
    und_string:
    	.string "undefined instruction exception"
    /**如果你的程序长度稍有变化,就不能保证运行
	加上 .align 4才能保证后面的程序以4字节对齐,保证程序运行
	**/
    .align 4
    
    reset:
    	/* 关闭看门狗 */
    	ldr r0, =0x53000000
    	ldr r1, =0
    	str r1, [r0]
    
    	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    	ldr r0, =0x4C000000
    	ldr r1, =0xFFFFFFFF
    	str r1, [r0]
    
    	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    	ldr r0, =0x4C000014
    	ldr r1, =0x5
    	str r1, [r0]
    
    	/* 设置CPU工作于异步模式 */
    	mrc p15,0,r0,c1,c0,0
    	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
    	mcr p15,0,r0,c1,c0,0
    
    	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
    	 *  m = MDIV+8 = 92+8=100
    	 *  p = PDIV+2 = 1+2 = 3
    	 *  s = SDIV = 1
    	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
    	 */
    	ldr r0, =0x4C000004
    	ldr r1, =(92<<12)|(1<<4)|(1<<0)
    	str r1, [r0]
    
    	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
    	 * 然后CPU工作于新的频率FCLK
    	 */
    	
    	
    
    	/* 设置内存: sp 栈 */
    	/* 分辨是nor/nand启动
    	 * 写0到0地址, 再读出来
    	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
    	 * 否则就是nor启动
    	 */
    	mov r1, #0
    	ldr r0, [r1] /* 读出原来的值备份 */
    	str r1, [r1] /* 0->[0] */ 
    	ldr r2, [r1] /* r2=[0] */
    	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
    	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
    	moveq sp, #4096  /* nand启动 */
    	streq r0, [r1]   /* 恢复原来的值 */
    
    	bl sdram_init
    	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */
    
    	/* 重定位text, rodata, data段整个程序 */
    	bl copy2sdram
    
    	/* 清除BSS段 */
    	bl clean_bss
    
 /*把链接地址赋值给pc 直接就跳转到sdram中*/   
    	ldr pc, =sdram
    sdram:
    	bl uart0_init
    
    	bl print1
    	/* 故意加入一条未定义指令 */
    und_code:
    	.word 0xdeadc0de  /* 未定义指令 */
    	bl print2
    
    	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
    	b halt

看一下整个程序的执行过程

![](https://i.imgur.com/SWYt6w0.png)