在移植uboot时编译一切正常,但uboot启动中载入自己写的网卡驱动出现故障,一直在打印raise:Signal #8 caught
google 百度了一番,也有非常多人遇到了这个问题,大家都说出了解决这个问题的办法,
就是自己编写的驱动中有出现除以0的误操作,就会一直打印raise:Signal #8 caught
将除操作改为位移操作,或者避免除数为0,就能够解决问题。
那为什么有除以0的操作就会引发raise: Signal #8 caught ? 来分析一番!
遇到错误打印,首先要找到打印来源于哪个函数,在uboot源代码中grep一把,找到该打印的来源,在arch/arm/lib/eabi_compat.c中,例如以下:
int raise (int signum) { /* Even if printf() is available, it's large. Punt it for SPL builds */ #if !defined(CONFIG_SPL_BUILD) printf("raise: Signal # %d caught ", signum); #endif return 0; }eabi_compat.c是为符合eabi接口的工具链提供一些通用的函数。
看到raise函数的实现,感觉參数signum好像是代表信号的意思(C语言中变量名是多么重要啊!),想起在网络编程中有一个函数raise是用来自身进程发信号。
那么这里一直打印signal #8 caught,是不是一直在发8号信号,搜了一下linux下的64种信号,8号代表的是SIGFPE,当一个进程遇到一个错误的算术操作时就会发送该信号。
这不就跟大家给出的解决的方法是一致的嘛,由于出现了除以0的操作,所以会发SIGFPE信号!
可是这种解释还是有非常大漏洞,raise发出信号,谁调它发的SIGFPE,grep源代码,根本找不到调用raise的地方!
linux系统下用户空间出现除以0的非法操作,进程就发SIGFPE信号,信号是什么,事实上就是软中断,使用指令SWI使处理器陷入内核态,内核态中的异常处理捕获处理该错误。
写一个简单的測试程序,例如以下:
#include <stdio.h> void main() { int a = 100; int c= 0; c = a / 0; }静态交叉编译(将全部须要函数都拷贝进来),然后objdump反汇编,查看反汇编文件,找到main函数:
00008428 <main>: 8428: e92d4800 push {fp, lr} 842c: e28db004 add fp, sp, #4 ; 0x4 8430: e24dd008 sub sp, sp, #8 ; 0x8 8434: e3a03064 mov r3, #100 ; 0x64 8438: e50b300c str r3, [fp, #-12] 843c: e3a03000 mov r3, #0 ; 0x0 8440: e50b3008 str r3, [fp, #-8] 8444: e51b300c ldr r3, [fp, #-12] 8448: e3a02000 mov r2, #0 ; 0x0 844c: e1a00003 mov r0, r3 8450: e1a01002 mov r1, r2 8454: eb000003 bl 8468 <__aeabi_idiv> 8458: e1a03000 mov r3, r0 845c: e50b3008 str r3, [fp, #-8] 8460: e24bd004 sub sp, fp, #4 ; 0x4 8464: e8bd8800 pop {fp, pc}
大体阅读下,
除操作‘/’编译之后成了调用__aeabi_idiv,网上搜索一番,原来对于满足eabi(嵌入式arm应用程序二进制接口)的arm工具链,编译时编译器将编译对象的'/'操作替换为调用__aeabi_idiv函数,__aeabi_idiv是由libgcc.so或gcc.a库提供的。
反汇编文件里也有__aeabi_idiv的反汇编实现,大体看下,例如以下:
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"></span><pre name="code" class="cpp">00008468 <__aeabi_idiv>: 8468: e3510000 cmp r1, #0 ; 0x0 846c: 0a000081 beq 8678 <.divsi3_nodiv0+0x208> 00008470 <.divsi3_nodiv0>: 8470: e020c001 eor ip, r0, r1 8474: 42611000 rsbmi r1, r1, #0 ; 0x0 8478: e2512001 subs r2, r1, #1 ; 0x1
首先推断r1是否为0,是main传来的參数,r0是被除数,r1是除数,这里是0,所以跳到divsi3_nodiv0+0x208处,假设不为0则运行divsi3_nodiv0,到0x208处看一下:
8460: e3500000 cmp r0, #0 ; 0x0 8464: c3e00102 mvngt r0, #-2147483648 ; 0x80000000 8468: b3a00102 movlt r0, #-2147483648 ; 0x80000000 846c: ea000007 b 8490 <__aeabi_idiv0> 00008470 <__aeabi_idivmod>: 8470: e3510000 cmp r1, #0 ; 0x0 8474: 0afffff9 beq 8460 <.divsi3_nodiv0+0x208> 8478: e92d4003 push {r0, r1, lr} 847c: ebffff75 bl 8258 <.divsi3_nodiv0> 8480: e8bd4006 pop {r1, r2, lr} 8484: e0030092 mul r3, r2, r0 8488: e0411003 sub r1, r1, r3 848c: e12fff1e bx lr 00008490 <__aeabi_idiv0>: 8490: e92d4002 push {r1, lr} 8494: e3a00008 mov r0, #8 ; 0x8 8498: eb0007d8 bl a400 <raise> 849c: e8bd8002 pop {r1, pc}调用__aeabi_idiv0,而__aeabi_idv0则调用raise,參数为8
Crazy!原来是这里调用的raise函数!
这也就解释了为什么在源代码中找不到调用raise的代码,由于调用raise不是代码本身,而是eabi的arm工具链给出的数学运算函数。
arm工具链的__aeabi_div除数为0则会调用raise,发SIGFPE信号。这样整个的解释就通了!
uboot是裸机程序,是非GNU/linux的程序。
linux内核也是满足eabi的GNU/linux程序,符合eabi的arm工具链是GNU/linux工具链,eabi接口规定了内核的系统调用 信号规范,arm工具链编译linux内核,内核实现中会去实现raise函数。
也就是说arm工具链的__aeabi_idiv与linux内核的raise出现了交叉调用,有了依赖关系!
这样裸机程序如uboot可就受苦了,只是uboot中实现了如上的raise空函数,来让arm工具链的__aeabi_div来调用,仅仅是打印一下,而不做处理,
就是我们看到的raise:Signal #8 caught!
这也算是提个醒,以后做裸机程序,用eabi的arm工具链,假设代码中有除操作,记得链接libgcc.so和实现raise函数。