第十四课(4)und(未定义)异常程序示例

目标:写一个程序故意让其发生未定义异常,然后处理这个异常

1、异常向量表——地址

查看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

手册P82:
在这里插入图片描述

2、编写程序

1、保存现场
2、处理异常(分辨中断源,调用处理函数)
3、恢复现场

如何处理这个异常呢?
直接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");//回车,换行
}

start.S:

.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里 */
/*
*如何定义字符串,可以百度搜索 arm-linux-gcc 汇编 定义字符串
*
*官方的说明文档
*http://web.mit.edu/gnu/doc/html/as_7.html
.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

修改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()初始化函数,在start.S加上uart0_init

    bl uart0_init

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

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

halt:
    b halt

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

打开2440芯片手册,找到ARM指令集P86在这里插入图片描述
发现竟然是SWI指令,CPU可以识别出来,他不是一条未定义指令

我们得找到一条CPU不能识别的指令,定义为0x03000000
或者word 0xdeadcode /* 也是一条未定义指令 只要指令地址对不上上表就是未定义指令*/

编译运行正确打印,我们查看下cpsr是否处于未定义模式
0x600000DB
bit[4:0]表示CPU模式 11011,果然处于und模式

3、改进程序

1)ldr pc,=do_und

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

/* 这里又使用bl指令跳转,如果是nand启动,这个函数在4k之外,这个函数必定出错 为了保险。因为中断后,我们程序已经复制到sdram上,那么我们跳转到sdram中执行这个中断处理程序 */
bl printException

所以把b do_und改为ldr pc,=do_und //绝对跳转
这样printException这个函数肯定在SDRAM上

2) ldr pc, und_addr

查看下反汇编文件:

Disassembly of section .text:

30000000 <_start>:
30000000: ea00000e b 30000040
30000004: e59ff0b4 ldr pc, [pc, #180] ; 300000c0 <halt+0x4>

30000008 <do_und>:
30000008: e3a0d30d mov sp, #872415232 ; 0x34000000

30000020 <und_string>:
30000020: 65646e75 strbvs r6, [r4, #-3701]!

30000040 :
30000040: e3a00453 mov r0, #1392508928 ; 0x53000000

3000009c: eb000111 bl 300004e8 <sdram_init>
300000a0: eb00018e bl 300006e0
300000a4: eb0001aa bl 30000754 <clean_bss>
300000a8: eb000050 bl 300001f0 <uart0_init>
300000ac: eb0000fd bl 300004a8

300000b0 <und_code>:
300000b0: deadc0de .word 0xdeadc0de
300000b4: eb000103 bl 300004c8
300000b8: e59ff018 ldr pc, [pc, #24] ; 300000d8 <halt+0x1c>

300000bc :
300000bc: eafffffe b 300000bc
300000c0: 30000008 .word 0x30000008
300000c4: 30000020 .word 0x30000020
300000c8: 4c000014 .word 0x4c000014
300000cc: 4c000004 .word 0x4c000004
300000d0: 0005c011 .word 0x0005c011
300000d4: 40001000 .word 0x40001000
300000d8: 300007b0 .word 0x300007b0
]
因为ldr是伪指令所以是要去某个地方,也就是 300000c0上取do_und的地址,因为我们的_start的程序很小,但是如果超过4Knand就没法去读这个文件

所以修改成:
这么定义的话do_und的地址就一定存放在前面

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

/*增加如下 查看反汇编,在08的地址读让后跳到3c*/    
und_addr:
    .word do_und

疑惑:写成ldr pc, =und_addr,为什么不对?
https://blog.youkuaiyun.com/linweig/article/details/5411655
回答:
ldr pc, =und_addr的意思 去某个地方取und_addr的地址,查看反汇编:
30000000 <_start>:
30000000: ea000012 b 30000050
30000004: e59ff0c8 ldr pc, [pc, #200] ; 300000d4 <halt+0x4>

30000008 <und_addr>:
30000008: 3000000c .word 0x3000000c

<und_addr>的地址存放在300000d4, 那是不是又是与1)一样,万一程序太大,超过4K,那么就找不到。何必多此一举,我们的目的是去取und_addr上的值,所以ldr pc, und_addr就可以了:
30000000 <_start>:
30000000: ea000012 b 30000050
30000004: e51ff004 ldr pc, [pc, #-4] ; 30000008 <und_addr>

30000008 <und_addr>:
30000008: 3000000c .word 0x3000000c
相当于ldr pc, =do_und,去某个地址取do_und,把这个“某个地址”,给限制在und_addr上。这样就不会超过4K

3)

修改跳转uart初始化函数的跳转:

    ldr pc, =sdram
sdram:
    bl uart0_init

4).align 4

在这里插入图片描述
reset不是4字节对齐的指令
因为的字符串不能保证是4字节

需要添加4字节对齐 .align 4,运行结果:
在这里插入图片描述

und_string:
    .string "undefined instruction exception"
/**如果你的字符串长度稍有变化,就不能保证运行
加上 .align 4才能保证后面的程序以4字节对齐,保证程序运行
**/
.align 4

reset:

5)完整代码:

.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

4、用图解释整个程序的跳转过程

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值