如何在 Cortex M3 上产生异常?

How to Generate Exceptions on Cortex M3?

我正尝试在 ARM Cortex-M3 上生成总线故障、使用故障等异常。我的启用异常代码:

void EnableExceptions(void)
{
    UINT32 uReg = SCB->SHCSR;

    uReg |= 0x00070000;

    SCB->SHCSR = uReg;

    //Set Configurable Fault Status Register
    SCB->CFSR = 0x0367E7C3;
    //Set to 1 DIV_0_TRP register
    SCB->CCR |= 0x00000010;

    //Set priorities of fault handlers

    NVIC_SetPriority(MemoryManagement_IRQn, 0x01);

    NVIC_SetPriority(BusFault_IRQn, 0x01);

    NVIC_SetPriority(UsageFault_IRQn, 0x01);
}
    
void UsageFault_Handler(void){
    //handle
    //I've set a breakpoint but system does not hit 
}

void BusFault_Handler(void){
    //handle
    //I've set a breakpoint but system does not hit 
}

我试图生成被零除异常,并看到变量值为“Infinity”。但是系统不会在 keeps 运行 上生成任何异常。还尝试生成总线故障异常并发生同样的事情。

另外,当我注释掉 EnableExceptions 函数时,系统工作正常。我的代码有什么问题? ARM 是否在微处理器内部处理这些类型的错误?

来自您的评论问题:如何生成任何异常。

这是文档中的一个:

Encoding T1 All versions of the Thumb instruction set.
SVC<c> #<imm8>

...

Exceptions 
SVCall.

我可以通过搜索 SVCall 找到它。

ARM 对异常进行了详细记录,有多种方法可以在不中断总线的情况下导致您列出的异常(需要 sim 和 fpga 或创建您自己的芯片),您已经知道文档的搜索词查找 busfault 和 usagefault。

记录了 ARM 如何处理这些(内部或外部)。在这种情况下,内部意味着如果你看的话是否锁定,否则它们会执行故障处理程序(当然除非在获取故障处理程序时出现故障)。

您可以在 C 中创建大部分内容而无需诉诸汇编语言指令,但您必须小心它生成的是您认为生成的内容:

void fun ( void )
{
  int x = 3;
  int y = 0;
  int z = x / y;
}

Disassembly of section .text:

00000000 <fun>:
   0:   4770        bx  lr

相反,您想要的是实际生成可能导致错误的指令的东西:

int fun0 ( int x, int y )
{
    return(x/y);
}
void fun1 ( void )
{
    fun0(3,0);
}

00000000 <fun0>:
   0:   fb90 f0f1   sdiv    r0, r0, r1
   4:   4770        bx  lr
   6:   bf00        nop

00000008 <fun1>:
   8:   4770        bx  lr

但是如图所示,您必须小心调用它的位置和方式。在这种情况下,调用是在同一个文件中完成的,因此优化器可以看到这现在是死代码并将其优化掉,因此像这样的测试将由于多种原因而无法生成错误。

这就是为什么 OP 需要提供一个完整的最小示例的原因,因为没有看到故障的原因不是处理器。但是软件and/or测试代码.

编辑

一个完整的最小示例,除了 gnu 工具链(不。这是在 stm32 blue pill 和 STM32F103 上...

flash.s

.cpu cortex-m3
.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset /* 1 Reset */
.word hang  /* 2 NMI */
.word hang  /* 3 HardFault */
.word hang  /* 4 MemManage */
.word hang  /* 5 BusFault */
.word usagefault  /* 6 UsageFault */
.word hang  /* 7 Reserved */
.word hang  /* 8 Reserved */
.word hang  /* 9 Reserved */
.word hang  /*10 Reserved */
.word hang  /*11 SVCall */
.word hang  /*12 DebugMonitor */
.word hang  /*13 Reserved */
.word hang  /*14 PendSV */
.word hang  /*15 SysTick */
.word hang  /* External interrupt 1 */
.word hang  /* External interrupt 2 */

.thumb_func
reset:
    bl notmain
    b hang
.thumb_func
hang:   b .

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.thumb_func
.globl dummy
dummy:
    bx lr

.thumb_func
.globl dosvc
dosvc:
    svc 1

.thumb_func
.globl hop
hop:
    bx r0

flash.ld

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

fun.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
void hop ( unsigned int );

#define GPIOCBASE   0x40011000
#define RCCBASE     0x40021000
#define SHCSR       0xE000ED24

void usagefault ( void )
{
    unsigned int ra;

    while(1)
    {
        PUT32(GPIOCBASE+0x10,1<<(13+0));
        for(ra=0;ra<100000;ra++) dummy(ra);
        PUT32(GPIOCBASE+0x10,1<<(13+16));
        for(ra=0;ra<100000;ra++) dummy(ra);
    }
}

int notmain ( void )
{
    unsigned int ra;

    ra=GET32(SHCSR);
    ra|=1<<18; //usagefault
    PUT32(SHCSR,ra);

    ra=GET32(RCCBASE+0x18);
    ra|=1<<4; //enable port c
    PUT32(RCCBASE+0x18,ra);
    ra=GET32(GPIOCBASE+0x04);
    ra&=(~(3<<20));   //PC13
    ra|=  (1<<20) ;   //PC13
    ra&=(~(3<<22));   //PC13
    ra|=  (0<<22) ;   //PC13
    PUT32(GPIOCBASE+0x04,ra);

    PUT32(GPIOCBASE+0x10,1<<(13+0));
    for(ra=0;ra<200000;ra++) dummy(ra);
    PUT32(GPIOCBASE+0x10,1<<(13+16));
    for(ra=0;ra<200000;ra++) dummy(ra);

    ra=GET32(0x08000004);
    ra&=(~1);
    hop(ra);

    return(0);
}

建设

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-none-eabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m3 -mthumb -c so.c -o so.o
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld flash.o so.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy -O binary so.elf so.bin

所有这些命令行选项都不是必需的 arm-linux-gnueabi- 和其他风格的 gnu 工具链从几个版本回到现在都工作得很好,因为我将它们用作编译器、汇编器和链接器,不要混淆库或其他因一种风格而异的东西。

UsageFault The UsageFault fault handles non-memory related faults
caused by instruction execution. 

A number of different situations  cause usage faults, including: 

 • Undefined Instruction. 
 • Invalid state on instruction execution. 
 • Error on exception return. 
 • Attempting to access a disabled or unavailable coprocessor. 

 The following can cause usage faults when the processor is configured to 
 report them: 
  • A word or halfword memory accesses to an unaligned address. 
  • Division by zero. 

Software can disable this fault. If it does, a UsageFault escalates to HardFault. UsageFault has a configurable priority.

...

Instruction execution with EPSR.T set to 0 causes the invalid state UsageFault

所以这里的测试分支到 arm 地址与 thumb 地址,这会导致使用错误。 (也可以在文档中阅读有关 BX 指令的 psr.t 位如何以及何时更改等信息)

备份这是stm32蓝色药丸。 PC13 上有一个 led,代码启用 usagefault,将 PC13 配置为输出,闪烁一次所以我们看到程序启动,然后如果它遇到 usagefault 处理程序然后它永远闪烁。

ra&=(~1);

如果您将其注释掉,那么它会一直分支到重置,这会再次缓慢地闪烁一次,您会看到它永远重复。

在 运行ning 之前,您自然会检查构建以查看它是否有可能工作:

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000
 8000004:   08000049
 8000008:   0800004f
 800000c:   0800004f
 8000010:   0800004f
 8000014:   0800004f
 8000018:   08000061
 800001c:   0800004f
 8000020:   0800004f
 8000024:   0800004f
 8000028:   0800004f
 800002c:   0800004f
 8000030:   0800004f
 8000034:   0800004f
 8000038:   0800004f
 800003c:   0800004f
 8000040:   0800004f
 8000044:   0800004f

08000048 <reset>:
 8000048:   f000 f82a   bl  80000a0 <notmain>
 800004c:   e7ff        b.n 800004e <hang>

0800004e <hang>:
 800004e:   e7fe        b.n 800004e <hang>

...

08000060 <usagefault>:
 8000060:   b570        push    {r4, r5, r6, lr}

矢量 table 是正确的,正确的矢量指向正确的位置。

0xE000ED28 CFSR RW 0x00000000

HFSR 是 CFSR 的高位

> halt
target halted due to debug-request, current mode: Handler UsageFault
xPSR: 0x81000006 pc: 0x0800008a msp: 0x20000fc0
> mdw 0xE000ED28
0xe000ed28: 00020000

那位是

INVSTATE, bit[1] 
0 EPSR.T bit and EPSR.IT bits are valid for instruction execution.
1 Instruction executed with invalid EPSR.T or EPSR.IT field.

现在

Using the CCR, see Configuration and Control Register, CCR on page B3-604, software can enable or disable:
• Divide by zero faults, alignment faults and some features of processor operation.
• BusFaults at priority -1 and higher.

CCR 的重置值是实现定义的,因此它可能只是为您启用或未启用,可能需要查看 Cortex-m3 TRM 或直接阅读它:

> mdw 0xE000ED14          
0xe000ed14: 00000000

所以我的是零。

所以添加fun.c:

unsigned int fun ( unsigned int x, unsigned int y)
{
    return(x/y);
}

更改so.c:

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
unsigned int fun ( unsigned int, unsigned int);

#define GPIOCBASE   0x40011000
#define RCCBASE     0x40021000
#define SHCSR       0xE000ED24

#define CCR         0xE000ED14

void usagefault ( void )
{
    unsigned int ra;

    while(1)
    {
        PUT32(GPIOCBASE+0x10,1<<(13+0));
        for(ra=0;ra<100000;ra++) dummy(ra);
        PUT32(GPIOCBASE+0x10,1<<(13+16));
        for(ra=0;ra<100000;ra++) dummy(ra);
    }
}

int notmain ( void )
{
    unsigned int ra;

    ra=GET32(SHCSR);
    ra|=1<<18; //usagefault
    PUT32(SHCSR,ra);

    ra=GET32(CCR);
    ra|=1<<4; //div by zero
    PUT32(CCR,ra);

    ra=GET32(RCCBASE+0x18);
    ra|=1<<4; //enable port c
    PUT32(RCCBASE+0x18,ra);
    ra=GET32(GPIOCBASE+0x04);
    ra&=(~(3<<20));   //PC13
    ra|=  (1<<20) ;   //PC13
    ra&=(~(3<<22));   //PC13
    ra|=  (0<<22) ;   //PC13
    PUT32(GPIOCBASE+0x04,ra);

    PUT32(GPIOCBASE+0x10,1<<(13+0));
    for(ra=0;ra<200000;ra++) dummy(ra);
    PUT32(GPIOCBASE+0x10,1<<(13+16));
    for(ra=0;ra<200000;ra++) dummy(ra);

    fun(3,0);

    return(0);
}

建设

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-none-eabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m3 -mthumb -c so.c -o so.o
arm-none-eabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m3 -mthumb -c fun.c -o fun.o
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld flash.o so.o fun.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy -O binary so.elf so.bin

确认确实有一个我们要命中的除法指令

 800011e:   2100        movs    r1, #0
 8000120:   2003        movs    r0, #3
 8000122:   f000 f80f   bl  8000144 <fun>
...
08000144 <fun>:
 8000144:   fbb0 f0f1   udiv    r0, r0, r1
 8000148:   4770        bx  lr

加载并 运行 并调用处理程序。

target halted due to debug-request, current mode: Handler UsageFault
xPSR: 0x81000006 pc: 0x08000086 msp: 0x20000fc0
> mdw 0xE000ED28                
0xe000ed28: 02000000 

这表明它被零除。

所以您 know/do 所需的一切都在文档中,一个文档。

99.999% 的裸机编程是阅读或做实验来验证所读内容,几乎 none 的工作是编写最终应用程序,这只是工作的一小部分。

在开始裸机编程之前,您必须掌握工具链,否则什么都做不了。掌握工具链可以在没有任何目标硬件的情况下完成,使用免费工具,所以这只是坐下来做的问题。

就您尝试在没有硬件除以零的内核上进行浮点除以零而言,您需要查看软浮点数,例如 libgcc:

ARM_FUNC_START divsf3
ARM_FUNC_ALIAS aeabi_fdiv divsf3
    CFI_START_FUNCTION

    @ Mask out exponents, trap any zero/denormal/INF/NAN.
    mov ip, #0xff
    ands    r2, ip, r0, lsr #23
    do_it   ne, tt
    COND(and,s,ne)  r3, ip, r1, lsr #23
    teqne   r2, ip
    teqne   r3, ip
    beq LSYM(Ldv_s)
LSYM(Ldv_x):

...


    @ Division by 0x1p*: let''s shortcut a lot of code.
LSYM(Ldv_1):
    and ip, ip, #0x80000000
    orr r0, ip, r0, lsr #9
    adds    r2, r2, #127
    do_it   gt, tt
    COND(rsb,s,gt)  r3, r2, #255

and so on

这应该在反汇编中可见,我没有立即看到强制异常(故意未定义的指令,swi/svc 或类似的东西)。这只是一个可能的库,现在我想起来这看起来像手臂而不是拇指,所以必须去寻找它,更容易只看反汇编。

根据您的评论,如果我再次阅读另一个问题,我假设因为它没有引发异常,所以除以零的正确结果是正确签名的无穷大。但是如果你切换到 cortex-m4 或 m7 那么你可能会触发硬件异常,但是......阅读文档以找出答案。

编辑 2

void fun ( void )
{
    int a = 3;
    int b = 0;
    volatile int c = a/b;
}

fun.c:6:18: warning: unused variable ‘c’ [-Wunused-variable]
    6 |     volatile int c = a/b;
      |                  ^

08000140 <fun>:
 8000140:   deff        udf #255    ; 0xff
 8000142:   bf00        nop

> halt                          
target halted due to debug-request, current mode: Handler UsageFault
xPSR: 0x01000006 pc: 0x08000076 msp: 0x20000fc0
> mdw 0xE000ED28                
0xe000ed28: 00010000 

那个位表示

The processor has attempted to execute an undefined instruction

所以 volatile 无法使用 gcc 产生预期的结果

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 10.1.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


arm-linux-gnueabi-gcc --version
arm-linux-gnueabi-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

生产

Disassembly of section .text:

00000000 <fun>:
   0:   deff        udf #255    ; 0xff
   2:   bf00        nop

也一样(而且你可以通过其他人来开辟自己的道路)。

是的,出现了一个错误,它是使用错误,但这将是另一个 Stack Overflow 问题,关于为什么我没有被零除。一味的用volatile来强制除法是行不通的。

使这三个都变得不稳定

void fun ( void )
{
    volatile int a = 3;
    volatile int b = 0;
    volatile int c = a/b;
}

Disassembly of section .text:

00000000 <fun>:
   0:   2203        movs    r2, #3
   2:   2300        movs    r3, #0
   4:   b084        sub sp, #16
   6:   9201        str r2, [sp, #4]
   8:   9302        str r3, [sp, #8]
   a:   9b01        ldr r3, [sp, #4]
   c:   9a02        ldr r2, [sp, #8]
   e:   fb93 f3f2   sdiv    r3, r3, r2
  12:   9303        str r3, [sp, #12]
  14:   b004        add sp, #16
  16:   4770        bx  lr

将生成所需的错误。

并且没有优化

00000000 <fun>:
   0:   b480        push    {r7}
   2:   b085        sub sp, #20
   4:   af00        add r7, sp, #0
   6:   2303        movs    r3, #3
   8:   60fb        str r3, [r7, #12]
   a:   2300        movs    r3, #0
   c:   60bb        str r3, [r7, #8]
   e:   68fa        ldr r2, [r7, #12]
  10:   68bb        ldr r3, [r7, #8]
  12:   fb92 f3f3   sdiv    r3, r2, r3
  16:   607b        str r3, [r7, #4]
  18:   bf00        nop
  1a:   3714        adds    r7, #20
  1c:   46bd        mov sp, r7
  1e:   bc80        pop {r7}
  20:   4770        bx  lr

也会生成所需的故障

所以首先掌握语言(读读读),其次掌握工具链(读读读),然后是裸机编程(读读读)。这完全是关于阅读,而不是关于编码。如上所示,即使在这个级别拥有数十年的经验,您也无法完全预测这些工具将生成什么;您必须尝试一下,但最重要的是因为您一天一次在一台机器上为一种工具弄清楚了,没有理由在您的假设中过于宽泛。必须尝试并检查编译器生成的内容,重复该过程,直到获得所需的效果。推到推只写几行asm就搞定了。

你没有看到故障,因为你没有生成任何故障 and/or 没有捕获它们或两者兼而有之。可能的原因列表很长基于提供的代码,但是这些例子,你应该没有问题移植到你的平台,应该证明你的硬件也可以工作,然后你可以通过连接来解决你的软件没有工作的原因执行的代码和不执行的代码之间的点。我所做的就是按照文档进行操作,并检查编译器的输出,一旦我启用了最少数量的东西,就会调用故障处理程序。如果没有启用这些,则不会触发使用错误。

Cortex-M 设备专门使用 Thumb-2 指令集,ARM 使用 branch/jump/call 地址的最低有效位来确定目标是 Thumb 还是 ARM 代码,因为 Cortex-M 不能运行 ARM 代码,您可以通过创建到 even 地址的跳转来生成 BusFault 异常。

int dummy(){ volatile x = 0 ; return x ; }

int main()
{
    typedef void (*fn_t)();
    fn_t foo = (fn_t)(((char*)dummy) - 1) ;

    foo() ;
}

下面的也可以,因为在执行任何指令之前调用会失败,所以它不需要指向任何有效代码。

int main()
{
    typedef void (*fn_t)();
    fn_t foo = (fn_t)(0x8004000) ;    
    foo() ;
}

您可以通过强制 整数 除以零来生成使用错误:

int main()
{
    volatile int x = 0 ;
    volatile int y = 1 / x ;
}

BusFault、HardFault、MemmanageFault、UsageFault、SVC Call、NMI 这些是 arm cortex-M 微处理器的内部异常。 这实际上取决于您使用的是哪个处理器,但假设您使用的是 cortex-m3:

  • 默认情况下,所有故障都映射到硬故障处理程序,除非您 使它们明确地映射到它们自己的处理程序
  • 设置位:系统处理程序中的 USGFAULTENA、BUSFAULTENA、MEMFAULTENA 控制和状态寄存器 => 每个故障都将映射到 它的正确处理程序

要生成错误,您可以尝试:

  • 访问未映射的内存区域=>这会产生总线故障
  • 执行无法识别的指令 => UsageFault
  • 通过设置这些位之一明确触发这些故障之一:
    系统处理程序控制中的 USGFAULTACT、BUSFAULTACT、MEMFAULTACT 和 状态寄存器 => 这肯定会产生异常

详情请参考:https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-control-block/system-handler-control-and-state-register?lang=en