历史上的今天
返回首页

历史上的今天

今天是:2024年09月09日(星期一)

正在发生

2021年09月09日 | s3c2440学习之路-012-1 Undefined未定义中断

2021-09-09 来源:eefocus

1 未定义中断的原理

1.1 ARM的指令组成

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

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

1.2 执行未定中断的过程

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

具体的执行过程如下:


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


发生异常时硬件的处理,即进入异常

2.1将返回地址保存在LR(R14)寄存器

2.2将CPSR复制到SPSR(SPSR 就是专门弄来备份CPSR的)

2.3设置CPSR的模式位(设置CPSR的bit0~bit4)

2.4设置PC值为0x4(对应未定义异常向量的地址)


发生异常时软件的处理,即处理异常然后回到异常发生时的位置继续处理

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("hellon");

}


接下来就是重头戏,.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个:


在发生未定义中断前,ARM就进入了未定义模式,此时sp(r13)寄存器是此模式下私有的寄存器, 不理解请看 s3c2440学习之路-012-0 异常中断基础知识

后续需要跳转到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("%sn", string);

    printf("cpsr:0x%xn", 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小点要注意


保存的时候是stmdb sp!, {r0-r12, lr}, 而取出时是ldmia sp!, {r0-r12, pc}^, 最后面一个是保存lr, 一个是取出pc, 就等价于把lr 赋值给pc了

ldmia sp!, {r0-r12, pc}^ 最后还有一个"^" 符号, 意思是把spsr赋值给cpsr

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


把之前保存的寄存器r0-r12恢复

spsr赋值给cpsr

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种情况


去掉106 行的bl print_hello,bl printf_cpsr的值为:0x200000d3

保留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 也一定会被执行,一定会产生未定义中断。

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

推荐阅读

史海拾趣

AC Interface Inc公司的发展小趣事

ABC Electronics Inc. 起初是一家小型电子元件供应商,专注于为本地市场提供基本的电子零部件。然而,随着技术的快速发展,公司创始人意识到,只有不断创新才能在竞争激烈的市场中立足。于是,ABC Electronics 开始投入大量资源研发具有竞争力的新产品。经过数年的努力,公司成功推出了一款具有高效能、低能耗特点的电源管理芯片,迅速获得了市场的认可。这一创新产品不仅为公司带来了可观的利润,也奠定了其在电子行业的技术领先地位。

Advanced Thermal Solutions公司的发展小趣事

为了进一步扩大市场份额,ATS开始实施全球化战略。公司先后在北美、欧洲和亚洲等地设立了分公司或办事处,以便更好地服务全球客户。同时,ATS还积极参与国际展览和交流活动,与全球各地的合作伙伴建立紧密的合作关系。这些举措使ATS的产品和服务得以覆盖更广泛的地域,公司的知名度和影响力也不断提升。

芯海科技(CHIPSEA)公司的发展小趣事

进入21世纪第二个十年,物联网技术迅猛发展,为芯海科技带来了新的机遇。公司紧跟市场趋势,发布了高精度SoC(系统级芯片)/MCU芯片,广泛应用于智能家居、智能穿戴等领域。这一创新使得芯海科技能够为客户提供一站式的智能硬件解决方案,进一步提升了其市场竞争力。

Hilscher Gesellschaft für Systemautomation mbH公司的发展小趣事

随着在高压电源领域的深入发展,HVPSI逐渐参与到行业标准的制定工作中。公司凭借其在技术上的深厚积累和市场经验,为行业标准的制定提供了宝贵的建议和数据支持。这些标准的出台不仅规范了市场秩序,也进一步巩固了HVPSI在行业内的领先地位。

GREEGOO公司的发展小趣事
时钟信号线上尽量少打过孔,以避免信号衰减和反射。
Cadeka公司的发展小趣事

在电子行业的初期,Cadeka公司凭借一项革命性的半导体技术突破,迅速崭露头角。公司研发团队经过数年的努力,成功开发出一种新型的集成电路,大大提高了电子设备的性能和效率。这一技术突破迅速吸引了市场的关注,Cadeka公司的订单量激增,公司规模迅速扩大。

问答坊 | AI 解惑

DM12232A(C框LED).pdf

本帖最后由 paulhyde 于 2014-9-15 09:22 编辑 DM12232A(C框LED).pdf  …

查看全部问答>

为什么GPIO中有的Pin要设为Pull_Up/Pull_Down?

如题,其中有的pin为输入pin,有的pin为输出pin,但是为什么要设置一些pin的属性为pull up或者为pull down,pull up/pull down到底是干吗用的?根据什么来设的呢,聆听各位大虾的教诲!!…

查看全部问答>

帮忙看看这段sql哪里错了

SELECT id, content, recivetime, readtime, statflag FROM goodsmessage ORDER BY recivetime DESC LIMIT 2 我在wince 5.0 下执行总是提示说 LIMIT 2 附近有错误,那位高手帮忙看一下是怎么回事啊?…

查看全部问答>

关于CreateControl问题

        RECT rectClient;         GetClientRect(&rectClient);          CLSID  clsid = { 0xca8a9780, 0x280d, 0x11cf, { 0xa2, 0x4d, 0x44, 0x45, 0x53, 0 ...…

查看全部问答>

用.net c#做winCE应用开发的问题

各位大哥,我问一下,在.net环境下开发winCE应用是用VC好呢,还是C#好呢,有经验的介绍一下他们的区别吧, 我要作的东西要组件化,VC++下智能设备下的ATL工程能直接在c#下调用吗?c#的组件编程怎么创建工程呀? …

查看全部问答>

波特率的问题请教

我对硬件不怎么了解,想问一下如果发送的硬件的波特率是19200,接收的波特率是9600,这之间可以进行准确通信么,就是19200的向9600的发送数据,后者可以识别发送的具体是什么么? 谢谢…

查看全部问答>

温湿度采集问题

怎样才能把采集的数据显示到,edit中呢? spcomm控件作的数据传输可以用的,但是不知道怎样把数据转化呢,怎样才可以显示在edti中呢,是不是一定要现有硬件才可以实现呢 >? 各位老师有没有好一点的代码给看看呢,最好整个代码,学生不胜感激了.…

查看全部问答>

俺的C习题(4)--给3来一个重写

这些天上班,写程序,自己没有养成良好的习惯 造成的 弊端 越来越暴露出致命的缺陷。甚至让自己都怀疑自己是不是不会写程序了。晚上不想加班了,累累的,回到家,静静想着调整思路。想到这个 一天一个C代码。在哪里跌倒哪里爬起,哪里狗屎哪里完善 ...…

查看全部问答>

DSP5509结构体

typedef int             int16; typedef long            int32; typedef unsigned int    Uint16; typedef unsigned long   Uint3 ...…

查看全部问答>

【TI C2000的使用经验】反面教材之半途而废

其实挺早就有兴趣学习c2000,因为在数字电源领域基本都会接触到c2000系列的产品,在新能源这边应用也不少。但是工作上就不会用得上,负责纯硬件的。所以从2812开始学,然后又鼓捣28335,现在又开始折腾2808了,曾经差点被28377给套住,不过确实太新 ...…

查看全部问答>