C ARM代码中未定义的指令异常
我正在ARM Cortex A53上进行裸机编程(我正在开发内核),SoC BCM2837(换言之,是Raspberry PI 3)。实际上,我正在编写一个负责处理mini-UART(一种helloworld,正如osdevwiki上报道的那样)的软件。因此,我编写了一组函数来处理微型UART,让我们考虑下面的问题,因为对于任何其他函数,问题仍然存在:C ARM代码中未定义的指令异常,c,exception,arm,kernel,bare-metal,C,Exception,Arm,Kernel,Bare Metal,我正在ARM Cortex A53上进行裸机编程(我正在开发内核),SoC BCM2837(换言之,是Raspberry PI 3)。实际上,我正在编写一个负责处理mini-UART(一种helloworld,正如osdevwiki上报道的那样)的软件。因此,我编写了一组函数来处理微型UART,让我们考虑下面的问题,因为对于任何其他函数,问题仍然存在: void miniUartSendByte(unsigned char byte){ // FIFO can accept at least
void miniUartSendByte(unsigned char byte){
// FIFO can accept at least one byte
while(*AUX_MU_LSR_REG & 0b100000);
// write byte to buffer
*AUX_MU_IO_REG = byte;
return;
}
其中,AUX_MU_*的类型为volatile unsigned int*。这是对上述代码的分解:
1000ac: d10043ff sub sp, sp, #0x10
1000b0: 39003fe0 strb w0, [sp, #15]
1000b4: d503201f nop
1000b8: d28a0a80 mov x0, #0x5054 // #20564
1000bc: f2afc420 movk x0, #0x7e21, lsl #16
1000c0: b9400000 ldr w0, [x0]
1000c4: 121b0000 and w0, w0, #0x20
1000c8: 7100001f cmp w0, #0x0
1000cc: 54ffff61 b.ne 1000b8 <miniUartSendByte+0xc> // b.any
1000d0: d28a0800 mov x0, #0x5040 // #20544
1000d4: f2afc420 movk x0, #0x7e21, lsl #16
1000d8: 39403fe1 ldrb w1, [sp, #15]
1000dc: b9000001 str w1, [x0]
1000e0: d503201f nop
1000e4: 910043ff add sp, sp, #0x10
1000e8: d65f03c0 ret
正如您所看到的,当机器执行跳转时,它接收到一个异常并跳转到地址0x200,该地址放置了中断处理程序(注意,没有配置中断处理程序,我还没有实现它),并在地址0x200执行无限循环(没有中断处理程序时的默认行为)。现在,通过QEMU,我能够捕获异常的类型:
Taking exception 1 [Undefined Instruction]
...from EL3 to EL3
...with ESR 0x0/0x2000000
...with ELR 0x200
...to EL3 PC 0x200 PSTATE 0x3cd
我正在使用以下命令进行编译:
aarch64-elf-gcc -Wall -O0 -ffreestanding -nostdinc -nostdlib -nostartfiles -mcpu=cortex-a53 -g -c ... -o ...
我还试图看看这是否是一个“编译器”问题,试图执行以下完全无用的代码:
void a(){
for(int j=0; j<10; j++);
return;
}
void b(char* string){
for(int i = 0; i<10; i++){
a();
}
return;
}
void kernel_main(){
a();
b("test");
while(1);
return;
}
void a(){
对于(int j=0;j而言,问题在于访问外围设备寄存器的基址设置错误。一旦将其设置为0x3F000000,将不再引发异常
我将其设置为基址0x7E000000,误导了BCM数据表中的报告:
外围设备的物理地址范围为0x3F000000到0x3FFFFFFF
外围设备的总线地址设置为映射到外围总线地址范围
从0x7E000000开始。因此,在总线地址0x7ENNNNN处播发的外围设备是
物理地址为0x3FNNN
但后来有报道说:
本文件中指定的外设地址为总线地址。软件直接
访问外围设备必须将这些地址转换为物理或虚拟地址
问题在于访问外围设备寄存器的基址设置错误。一旦将其设置为0x3F000000,将不再引发异常
我将其设置为基址0x7E000000,误导了BCM数据表中的报告:
外围设备的物理地址范围为0x3F000000到0x3FFFFFFF
外围设备的总线地址设置为映射到外围总线地址范围
从0x7E000000开始。因此,在总线地址0x7ENNNNN处播发的外围设备是
物理地址为0x3FNNN
但后来有报道说:
本文件中指定的外设地址为总线地址。软件直接
访问外围设备必须将这些地址转换为物理或虚拟地址
您使用unsigned int*AUX_____IO_REG
写入一个字节有什么原因吗?这将写入至少16位。结果还会写入什么?未签名的int*指地址寄存器AUX____IO_REG映射到,换句话说,要写入AUX______REG,我需要写入该内存位置,而且acc根据BCM2837数据表,即使寄存器是32位宽,也只有前8位是可访问的,因此我一次只能写入一个字节,其余的都被忽略。此外,我看到的任何代码片段都使用volatile unsigned int*访问寄存器(例如,看看问题中的OsDev链接,函数mmio_write()如果处理器是little-endian,那么我所做的就是(除了使用uint32_t而不是unsigned int)。好的,根据ARM文档()指令总是little-endian。你能解释一下这对执行的影响吗?我的意思是为什么miniUartSendByte()会导致跳转和像a()或b()这样的函数出现异常不要??另外,如果问题出现在寄存器中存储的数据中,我不应该遇到UART行为问题(我不知道,我想发送“c”,但发送的是完全不同的东西)而不是在代码执行上??抱歉,但我有点困惑…我尝试在gcc中使用-mlittle-endian标志进行编译,但结果没有改变。实际上gcc已经以little-endian格式编译(我认为它能够通过查看-mcpu参数来检测目标的endianness)实际上,如果我使用-mbig-endian编译,则返回“编译为大端系统,目标为小端”。我不认为这是问题所在……您使用无符号int*AUX_____IO_REG
写入一个字节有什么原因吗?这将写入至少16位。结果还会写入什么?未签名int*指的是地址寄存器AUX______IO_REG映射到的地址寄存器,换句话说,写入我需要写入的AUX________IO_REG该内存位置,而且根据BCM2837数据表,即使寄存器为32位宽,也只能访问前8位,因此我一次只能写入一个字节,其余的都被忽略。此外,我看到的任何代码片段都使用易失性无符号int*访问寄存器(以问题中的OsDev链接为例,函数mmio_write()与我所做的完全相同(除了它们使用uint32_t而不是unsigned int),根据ARM文档()指令总是小端的。你能解释一下这是如何影响执行的吗?我的意思是为什么miniUartSendByte()会导致跳转异常,而像a()或b()这样的函数不会?另外,如果问题是存储在寄存器中的数据,我应该不会遇到UART行为问题(我不知道,我想发送'c',但发送的是一个完全不同的东西)而不是在代码执行上??抱歉,但我有点困惑…我尝试在gcc中使用-mlittle-endian标志进行编译,但结果没有改变。实际上gcc已经以little-endian格式编译了(我认为可以通过查看-mcpu参数来检测目标的endianness),事实上,如果我使用-mbig endian编译,则如下所示
void a(){
for(int j=0; j<10; j++);
return;
}
void b(char* string){
for(int i = 0; i<10; i++){
a();
}
return;
}
void kernel_main(){
a();
b("test");
while(1);
return;
}