s3c2440学习之路-012-1 Undefined未定义中断

硬件平台:jz2440
软件平台:Ubuntu16.04 arm-linux-gcc-3.4.5
源码位置: https://github.com/lian494362816/C/tree/master/2440/013_excption/001_undef

承接上篇博客 s3c2440学习之路-012-0 异常中断基础知识 接下来让我们看看第一个异常/中断相关的实例。

其实这里应该叫做未定义异常,不过网上大多都是叫做未定义中断,所以后面的属于异常的叫法我都会换成中断,免得给人感觉怪怪的

1 未定义中断的原理

1.1 ARM的指令组成

ARM的指令是由32位组成的,是有一定的组成格式的,如果不符合组成格式的话,那就这条指令就无法被识别,就是未定义指令了。指令的[31]~[28] 是条件位,当条件位为1110B时,就表明该指令一定背执行。这里特别指出[31] ~[28]是因为后面的例子种将会使用到。

在这里插入图片描述
在这里插入图片描述

1.2 执行未定中断的过程

当发现未定义指令时ARM会做什么呢,如同s3c2440学习之路-012-0 异常中断基础知识 的1.4 小节所说的, 程序会自动跳到0x4的位置去执行代码。
具体的执行过程如下:

  1. 执行某条命令,发现不符合ARM的命令格式,产生未定义异常

  2. 发生异常时硬件的处理,即进入异常
    2.1将返回地址保存在LR(R14)寄存器
    2.2将CPSR复制到SPSR(SPSR 就是专门弄来备份CPSR的)
    2.3设置CPSR的模式位(设置CPSR的bit0~bit4)
    2.4设置PC值为0x4(对应未定义异常向量的地址)

  3. 发生异常时软件的处理,即处理异常然后回到异常发生时的位置继续处理
    3.1 设置SP(因为需要通过压栈来保存寄存器和代码跳转)
    3.2 保存现场(将通用的寄存器保存起来)
    3.3 将SPSR赋值给CPSR
    3.4 清除中断标志位(未定义异常无中断标志可清除)
    3.5 恢复现场(将通用的寄存器恢复回去)
    3.6 将LR直接赋值给PC(这里不需要做差值)

我们把重点放在软件的处理上,接下来通过源码来分析,这样可以更好的体会到未定义中断的处理过程。
在这里插入图片描述
在这里插入图片描述

2 源码分析

2.1 进入未定义中断

start.s

   ldr pc, =sdram_addr

sdram_addr:

    bl uart0_init
    bl print_hello

    .word 0xdeadc0de

    bl print_hello

    ldr pc, =main /* abs jump to main */

loop:
    b loop

ldr pc, =sdram_addr 是为了让代码跳到SDRAM上去执行,如果不清楚请看s3c2440学习之路-011代码重定位
接下来就是初始化串口,然后执行print_hello函数,这个函数非常简单,就是打印"hello"。

void print_hello(void)
{
    printf("\n");
    printf("hello\n");
}

接下来就是重头戏,.word 0xdeadc0de, 是一条未定指令(0xdeadc0de,C零DE, 不是C欧DE, 有点像Dead Code 死代码),它的组成不符合ARM指令格式,因此会产生未定义中断。回顾前面所说的,最终硬件会把PC赋值成0x4, 程序会跑到0x4的地址去执行。

2.2 未定义中断的软件处理

start.s

.text
.global _start
_start:

    b _reset
    ldr pc, _undef_addr

_undef_addr: .word _undef

_undef:

    /*set bank sp, sdram end address is 0x34000000
    because sdram size is 64M,
    and sdram start address is 0x30000000 */
    ldr sp, =0x34000000

      /* store register */
    stmdb sp!, {r0-r12, lr}

    mrs r0, cpsr
    ldr r1, =undef_string
    bl printf_undef

    mov r0, #4
    bl led_off

    /* resume register */
    ldmia sp!, {r0-r12, pc}^ /*^ means copy spsr to cpsr */

undef_string:
    .string "Undefinf Excption!"
    .align 4

_reset:
    /* stop watch dog */
    ldr r0, =0x53000000
    mov r1, #0
    str r1, [r0]

程序开始会执行2条指令,第一条是b reset, 所处的地址是0x0, 也就是上电后自动执行的第一条指令。第二条指令是ldr pc, _undef_addr, 所处的地址是0x4, 也就是发生未定义中断时硬件会自动跳到这里的地址。要弄懂这条命令的意思,就需要弄懂ldr 汇编指令的2种用法。

ldr r0, =0xA
ldr r0, 0xB

上面2条ldr指令,一条是带"=",一条不带"="。 第1条指令的意思是r0=0xA, 是直接赋值的意思,而第2条指令是获取0xB地址上的值,再赋值给r0。如果把0xB当做指针的话,第2条指令就等价于r0=*0xB。一个是直接赋值,一个是取它地址里面的值,在赋值。

在回到ldr pc, _undef_addr, 先获取_undef_addr地址处的数值,在赋值给pc。通过反汇编查看可知,_undef_addr的值为0x30000008, 而0x30000008地址上的值为0x3000000C ,因此ldr pc, _undef_addr 就等价于ldr pc, =0x3000000C。
(这里起始地址为0x30000000是因为链接脚本的原因, 实际的存储地址需要减去0x30000000)
在这里插入图片描述
代码跳到0x3000000C的位置,也就是_undef: 标号的位置。接下来代码就会按照之前所说的流程来处理。

2.2.1 设置SP

ldr sp, =0x34000000, 重新设置栈,原因有2个:

  1. 在发生未定义中断前,ARM就进入了未定义模式,此时sp(r13)寄存器是此模式下私有的寄存器, 不理解请看 s3c2440学习之路-012-0 异常中断基础知识
  2. 后续需要跳转到C函数和压栈操作,这些都依赖sp寄存器

2.2.2 保存现场

stmdb sp!, {r0-r12, lr}, stmdb是用来存储多个寄存器的指令。意思是以sp的数值为基础(0x340000000),依次把r0-r12, lr寄存器入栈保存起来。如果不懂麻烦上网查查,这里不做过深的解释。

2.2.3 打印数值和点亮led灯

    mrs r0, cpsr
    ldr r1, =undef_string
    bl printf_undef

    mov r0, #4
    bl led_off
    /* resume register */
    ldmia sp!, {r0-r12, pc}^ /*^ means copy spsr to cpsr */

undef_string:
    .string "Undefinf Excption!"
    .align 4

通过mrs命令把cpsr 的数值读给r0, 把字符串"Undefinf Excption!" 传递给r1, 然后调用printf_undef 函数。
printf_undef 函数就是先打印传进来的字符串,然后输出cpsr的数值。
如果不清楚为什么要把值传给r0, r1 请看s3c2440学习之路-003 汇编给C传参数 点亮不同led灯


void printf_undef(unsigned int cpsr_status, char *string)
{
    printf("%s\n", string);
    printf("cpsr:0x%x\n", cpsr_status);
}

最终的实验结果就是, 先打印hello, 然后输出Undefinf Excption! ,cpsr的数值为0x600000db
0xdb=1101 1011b, 最低5位为11011b 也就是Undefinded模式。从这里可以看出,ARM确实进入了未定义模式。
在这里插入图片描述

在这里插入图片描述

2.2.4 恢复现场

ldmia sp!, {r0-r12, pc}^, 一条指令就搞定了。ldmia 对应前面的stmdb 命令,一个是把寄存器入栈存起来,一个是把寄存器出栈取出来。这里有2小点要注意

  1. 保存的时候是stmdb sp!, {r0-r12, lr}, 而取出时是ldmia sp!, {r0-r12, pc}^, 最后面一个是保存lr, 一个是取出pc, 就等价于把lr 赋值给pc了
  2. ldmia sp!, {r0-r12, pc}^ 最后还有一个"^" 符号, 意思是把spsr赋值给cpsr

这么简单的一条指令就完成了3个动作:

  1. 把之前保存的寄存器r0-r12恢复
  2. spsr赋值给cpsr
  3. lr 赋值给pc ,代码就回到了发生未定义中断前的位置了

2.3 总结

通过2.2 节 可以看出,对于未定义中断,整个软件部分的处理流程就如同1.2 小节说所的。
这里还有一个小疑问,问什么程序的一开始就是2条跳转指令呢?
2条跳转指令的地址分别位于0x0, 0x4, 这是ARM在发生复位操作和未定义中断时会去访问的地址。这里拿未定义中断来说,软件的处理有很多步骤需要完成,因此一条指令是无法完成的,所以在0x4的位置直接执行跳转ldr pc, _undef_addr, 跳转到其地方来完成,避免占用到0x0~0x1C的位置。(0x0 ~ 0x1C是ARM不同异常时的向量地址,不过目前我的程序只需要0x0 和0x4 不被占用)
因此,2440 uboot 的开头就是一堆的跳转指令,而且是按照下面的异常向量表来写的。
在这里插入图片描述

2440 uboot的start.S 开头部分代码
在这里插入图片描述

3 遗留问题

3.1 bl print_hello

sdram_addr:

    bl uart0_init
    bl print_hello

    .word 0xdeadc0de

    bl print_hello

    ldr pc, =main /* abs jump to main */

loop:
    b loop

在执行 .word 0xdeadc0de 前先执行了bl print_hello。但是发现如果把bl print_hello 去掉后,就不会出现未定异常了,换句话说.word 0xdeadc0de 指令被忽视了。
由此做了一个小测试,在word 0xdeadc0de 后面打印了cpsr的值,分2种情况

  1. 去掉106 行的bl print_hello,bl printf_cpsr的值为:0x200000d3
  2. 保留106 行的bl print_hello,bl printf_cpsr的值为:0x600000d3

唯一的不同就是最高的拿一个字节,一个是0x2, 一个是0x6。查看手册可以得到, 0x2表示进为,0x6 表示溢出位,为何0x2不会被执行,而0x6会被执行,这里暂时不清楚,不过区别点就在这里。我们这里的原因就是word 0xdeadc0de 指令被忽视了,没有执行。

为了保证指令被执行,需要将0xdeadc0de 修改为0xeeadc0de , 这样最高位就是1110b, 一定会被执行。这样即使去掉106行的去掉106 行的bl print_hello, .word 0xeeadc0de 也一定会被执行,一定会产生未定义中断。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值