首页 > 代码库 > uboot中raise:Signal #8 caught的根本原因

uboot中raise:Signal #8 caught的根本原因

在移植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\n", 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函数。