zoukankan      html  css  js  c++  java
  • s3c2440裸机-异常中断(二. und未定义指令异常)

    1._und(未定义指令异常)介绍

    我们之前分析过5种异常,那么如何进入未定义指令异常,当然是cpu读取指令发生异常,出现了指令解析异常。
    我们先来看下当cpu解析到什么样的指令才会触发未定义指令异常呢?

    从上面的arm指令格式中可知,只要指令码属于划线的格式,就属于未定义指令异常。

    2.汇编向c函数传参

    我们知道汇编给C语言函数传参是通过r0,r1,...通过堆栈的方式去传递的参数,比如r0=1, r1=2;那么在被调用的c函数中argv0就是r0, argv1就是r1...,那么我们如果通过汇编给C函数传递字符串呢?

    我们可以通过这样声明und_string为一个字符串:

    und_string:
    	.string "undefined instruction exception"
    

    然后用ldr r1, =und_string,这样r1中就保存了und_string的地址。
    这样调用我们的c函数就可以把und_string传入进去。

    3._und异常程序示例

    我们现在定义一条未定义指令伪代码:

    .text
    .global _start
    
    _start:
    	b reset  /* vector 0 : reset */ 
    	b do_und /* vector 4 : und (看中断向量表)*/
    	
    reset:
    	/*看门狗
    	时钟
    	sdram
    	设置SP
    	重定位*/
    	...
    	bl print1
    
    und_code:
    	.word 0xdeadc0de; /*定义一条未定义指令*/
    	/*故意以一个数据的方式引入一条未定义指令,当cpu执行到这里,读取0xdeadc0de指令码的时候,
    	发现无法识别这条指令,就发生未定义指令异常,就跳转到0x4的中断向量去执行*/
    	
    	bl print2
    	...
    

    我们现在为了方便调试理解:我们在未定义指令异常前后加上打印print1, print2,如果出现未定义指令异常后,就会跳到0x4的地方去读取指令,print2也就没法执行。

    当跳转到0x4的中断向量后,发现此处是一条跳转指令"bl do_und", 我们再到未定义指令异常的服务程序do_und中打印出und_string这个字符串的内容。

    现在开始写指令异常的服务程序do_und,实现如下:

    do_und:
    	/* sp_und未设置, 先设置它 (由于之前一直处于管理模式,现在处在und状态)*/
    	ldr sp, =0x34000000
    
    	/* 保存现场 */
    	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    	/* lr是异常处理完后的返回地址, 也要保存 */
    	stmdb sp!, {r0-r12, lr}  /*先减后存*/ /* 把栈中的值备份到r0-r12*/
    	
    	/* 处理und异常 */
    	mrs r0, cpsr
    	ldr r1, =und_string /*保存und_string地址*/
    	bl printException
    	
    	/* 恢复现场 */
    	ldmia sp!, {r0-r12, pc}^  /*(ldmia先读后加),把备份的值恢复到栈中,让pc=lr就可以恢复到异常前的指令地址。^会把spsr的值恢复到cpsr里 */
    

    下面来分析一下这个未定义指令异常服务程序:(其实代码的注释已经讲的很详细了)

    1.进入未定义指令异常服务do_und之前硬件自动完成的事情如下:

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

    2.进入指令异常服务程序do_und后,我们需要保存现场,处理und异常,恢复现场,注意:由于发生了cpu模式切换,如果要用到栈,那么先要设置对应模式的栈。由于栈的地址是向下生长的,这里我就用sdram的末位地址作为栈指针,把sp_und=0x34000000。

    3.在und异常服务程序中有可能会用到栈, 所以先保存现场,通过stmdb sp!, {r0-r12, lr}语句把栈中的值备份到r0-r12和lr,然后恢复现场的时候通过ldmia sp!, {r0-r12, pc}^,详见上面的注释。

    4.我们看到保存现场后,我们把cpsr的值放到r0, 把und_string放到r1, 然后用bl printException调用c函数,这样我们的c函数printException就能收到汇编传过来的参数,一个是cpsr模式(r0),一个是und_string汇编传过来的字符串(r1)。我们用C函数实现printException:

    void printException(unsigned int cpsr, char *str)
    {
    	puts("Exception! cpsr = ");
    	printHex(cpsr);
    	puts(" ");
    	puts(str);
    	puts("
    
    ");
    }
    

    完整的代码如下:

    点击展开代码
    
            .text
    	.global _start
    
    _start:
    	b reset  /* vector 0 : reset */ 
    	
    	b do_und /* vector 4 : und (看中断向量表)*/
    
    do_und:
    	/* 执行到这里之前:
    	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    	 * 2. SPSR_und保存有被中断模式的CPSR
    	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    	 * 4. 跳到0x4的地方执行程序 (bl do_und)
    	 */
    
    
    	/* sp_und未设置, 先设置它 (由于之前一直处于管理模式,现在处在und状态)*/
    	ldr sp, =0x34000000
    
    	/* 保存现场 */
    	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    	/* lr是异常处理完后的返回地址, 也要保存 */
    	stmdb sp!, {r0-r12, lr}  /*先减后存*/ /* 把栈中的值备份到r0-r12*/
    	
    	/* 处理und异常 */
    	mrs r0, cpsr
    	ldr r1, =und_string /*保存und_string地址*/
    	bl printException
    	
    	/* 恢复现场 */
    	ldmia sp!, {r0-r12, pc}^  /*(ldmia先读后加),把备份的值恢复到栈中,让pc=lr就可以恢复到异常前的指令地址。^会把spsr的值恢复到cpsr里 */
    	
    und_string:
    	.string "undefined instruction exception"
    
    
    reset:
    	/* 关闭看门狗 */
    	/* 时钟 */
    	/* sdram */	
    	bl copy2sdram
    	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
    

    测试结果如下:

    打印出print1中的字符串‘abc’后,紧接着打印printException函数中的结果,cpsr=0x600000db,那么对应的M[4:0]=11011, 对应下图为und模式。然后从und异常返回,恢复原来的模式继续执行。

    4.上述代码改进:

    1.保证指令4字节对齐

    我们将上面的代码的und_string字符串修改一下:

    ...
    und_string:
    	.string "undef instruction"
    
    reset:
    	/* 关闭看门狗 */
    	/* 时钟 */
    ...
    

    编译烧录再次运行,发现没有任何打印输出,这是为什么呢?我明明只是把und_string字符串改了一下呀。

    查看反汇编:

    我们发现reset的地址是0x30000032,竟然不是4字节对齐的,我们知道arm指令集是以4字节为基本单位的,那么这里没有对齐,肯定无法解析指令。那么我们手工改进代码如下:

    ...
    und_string:
    	.string "undef instruction"
    
    .align 4
    reset:
    	/* 关闭看门狗 */
    	/* 时钟 */
    ...
    

    我们再来看看反汇编,发现reset的地址是30000040,是以4字节对齐的,再次烧录运行,发现能够正常输出print1, 能够进入未定义指令异常。

    2.中断向量表进入异常向量用绝对跳转

    如果我们程序非常大,中断向量入口代码的地址可能会大于sram的容量4k,比如do_und和do_swi,那么这个时候就需要用绝对跳转。

        .text
    .global _start
    _start:
        b reset  /* vector 0 : reset */ 
        b do_und /* vector 4 : und (看中断向量表)*/
    

    将上面的相对跳转换成如下代码:

     .text
    .global _start
    
    _start:
    	b reset  
    	ldr pc, und_addr 
        ldr pc, swi_addr
        ...
        ... 
    und_addr:
    	.word do_und
    swi_addr:
        .word do_swi
    

    这样我们的do_und, do_swi就可放在4k之外的地方。

    3.重定位完程序后马上跳转到sdram上执行

    我们现在不断增加的程序代码量,那么有可能在 'ldr pc, =main' 这条指令执行之前程序就已经超过4k。那么我们当从nand启动的时候,还没执行到ldr pc, =main这句来,就无法取指令执行了。 所以我们干脆重定位完代码后就直接跳转到sdram上去执行,代码裁剪如下:

    	...
    	reset:
    		/*
    		看门狗
    		时钟
    		set SP
    		sdram_init
    		重定位
                        */
    
    ldr pc, =sdram sdram:
                ...
    	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    	halt:
    		b halt
    

    我们再来分析下整个程序执行过程

    1.一上电,cpu从0地址执行,执行b reset(进行初始化硬件)
    2.重定位程序
    3.跳转到sdram去继续执行
    4.执行到 deadc0de,发生未定义指令异常
    5.跳转到异常向量表的0x4地址去执行
    6.跳转到sdram上执行异常处理函数(do_und)
    7.异常返回,继续执行
  • 相关阅读:
    如何去除ecshop标题和网站底部的Powered by ECShop
    ecshop标签
    安装Wamp后 Apache无法启动的解决方法
    wamp5中的apache不能启动,80端口被占用
    iOS UI-AlertView(警示框)和ActionSheet(选择框、操作表单)
    iOS UI-三种简单的动画设置
    iOS UI-IOS开发中Xcode的一些使用技巧
    iOS UI-九宫格
    iOS开发-开发文档安装
    iOS UI-创建空项目
  • 原文地址:https://www.cnblogs.com/fuzidage/p/12114215.html
Copyright © 2011-2022 走看看