非法指令(Illegal Instruction)問題定位


 關鍵詞:Illegal Instruction、SIGILL等。

 

進程在運行過程中會收到SIGILL信號,此類錯誤是由操作系統發送給進程的。

SIGILL是某個進程中的某一句不能被CPU識別指令,這些指令可能是一些形式錯誤、未知或者特權指令。

1. SIGILL原因

1.1 錯誤修改代碼段

進程代碼段中數據是作為指令運行的,如果不小心代碼段被錯誤覆蓋,那么CPU可能無法識別對應的代碼,進而造成Illegal Instruction。

同樣,如果棧被不小心覆蓋了,造成返回地址錯誤、CPU跳轉到錯誤地址,執行沒有意義的內存數據,進而造成Illegal Instruction。

進一步可以認為,任何導致代碼段錯誤的問題都可能帶來Illegal Instruction。

1.2 指令集演進

CPU的指令集在不停演進,如果將較新指令集版本的程序在老版本CPU上運行,則老版本CPU運行時會有Illegal Instruction問題。

1.3 工具鏈Bug

編譯器(匯編器或者連接器)自身的bug,有可能生成CPU無法識別的指令。

1.4 內存訪問對齊或浮點格式問題

出現錯誤的指令可能和訪存地址指令有關。 另外,浮點數的格式是否符合IEEE的標准也可能會有影響。

2. 錯誤排查指南

  • 程序中有沒有特權指令、或者訪問特權寄存器

  • 有沒有將在較新CPU上編譯得到的可執行文件拿到老CPU上運行------------這種問題是100%復現,只需要查看對應匯編程序即可知道大概。

  • 程序中有沒有嵌入式匯編,先檢查。-------------------------------------------------編譯器bug。

    • 一般編譯器很少會生成有這種問題的代碼

    • X86平台上要尤其注意64位匯編指令和32位匯編指令的混用問題

  • 程序有在進程代碼段空間寫數據的機會嗎?----------------------------------------下面的分析就是代碼段被非法修改。還可能是意見存在問題,DDR中數據正確,從DDR讀取的數據經過總線產生數據突變異常。

  • 棧操作夠安全嗎?--------------------------------------------------------------------------如果異常PC指向棧,那么即是棧被非法修改。

  • 注意程序的ABI是否正確------------------------------------------------------------------100%復現問題,只需要檢查ABI說明書即可。

    • 尤其是動態鏈和靜態鏈是否處理的正確,盡量避免動態鏈的可執行文件調用錯誤庫的問題(ARM的EABI,MIPS的N32/O32/N64都很可能出這種問題)

  • 用的工具鏈靠譜嗎? 

3. Illegal Instruction處理

CK異常向量VEC_ILLEGAL對應非法指令錯誤,出現問題的時候內核輸出“Illegal instruction Error”,然后輸出寄存去、相關代碼段、棧等信息;最后發送SIGILL信號給進程。

asmlinkage void trap_c(struct pt_regs *regs)
{
    int sig;
    unsigned long vector;
    siginfo_t info;

    vector = (mfcr("psr") >> 16) & 0xff;

    switch (vector) {
...
        case VEC_ILLEGAL:
#ifndef CONFIG_CPU_NO_USER_BKPT
        if (*(uint16_t *)instruction_pointer(regs) != 0x1464)
#endif
        {
            sig = SIGILL;
            pr_err("Illegal instruction Error\n");
            show_regs(regs);
            break;
        }
...
    }
    send_sig(sig, current, 0);---------------------------------------------發送SIGILL給當前進程。
}

void show_regs(struct pt_regs *fp)
{
    unsigned long   *sp;
    unsigned char   *tp;
    int    i;

    pr_info("\nCURRENT PROCESS:\n\n");
    pr_info("COMM=%s PID=%d\n", current->comm, current->pid);

    if (current->mm) {
        pr_info("TEXT=%08x-%08x DATA=%08x-%08x BSS=%08x-%08x\n",
               (int) current->mm->start_code,
               (int) current->mm->end_code,
               (int) current->mm->start_data,
               (int) current->mm->end_data,
               (int) current->mm->end_data,
               (int) current->mm->brk);
        pr_info("USER-STACK=%08x  KERNEL-STACK=%08x\n\n",
               (int) current->mm->start_stack,
               (int) (((unsigned long) current) + 2 * PAGE_SIZE));
    }

    pr_info("PC: 0x%08lx (%pS)\n", (long)fp->pc, (void *)fp->pc);
    pr_info("LR: 0x%08lx (%pS)\n", (long)fp->lr, (void *)fp->lr);
    pr_info("SP: 0x%08lx \n", (long)fp);
    pr_info("orig_a0: 0x%08lx\n", fp->orig_a0);
    pr_info("PSR: 0x%08lx\n", (long)fp->sr);

    pr_info(" a0: 0x%08lx   a1: 0x%08lx   a2: 0x%08lx   a3: 0x%08lx\n",
           fp->a0, fp->a1, fp->a2, fp->a3);
#if defined(__CSKYABIV2__)
    pr_info(" r4: 0x%08lx   r5: 0x%08lx   r6: 0x%08lx   r7: 0x%08lx\n",
           fp->regs[0], fp->regs[1], fp->regs[2], fp->regs[3]);
    pr_info(" r8: 0x%08lx   r9: 0x%08lx  r10: 0x%08lx  r11: 0x%08lx\n",
           fp->regs[4], fp->regs[5], fp->regs[6], fp->regs[7]);
    pr_info("r12: 0x%08lx  r13: 0x%08lx  r15: 0x%08lx\n",
           fp->regs[8], fp->regs[9], fp->lr);
    pr_info("r16: 0x%08lx  r17: 0x%08lx  r18: 0x%08lx  r19: 0x%08lx\n",
           fp->exregs[0], fp->exregs[1], fp->exregs[2], fp->exregs[3]);
    pr_info("r20: 0x%08lx  r21: 0x%08lx  r22: 0x%08lx  r23: 0x%08lx\n",
           fp->exregs[4], fp->exregs[5], fp->exregs[6], fp->exregs[7]);
    pr_info("r24: 0x%08lx  r25: 0x%08lx  r26: 0x%08lx  r27: 0x%08lx\n",
           fp->exregs[8], fp->exregs[9], fp->exregs[10], fp->exregs[11]);
    pr_info("r28: 0x%08lx  r29: 0x%08lx  r30: 0x%08lx  tls: 0x%08lx\n",
           fp->exregs[12], fp->exregs[13], fp->exregs[14], fp->tls);
    pr_info(" hi: 0x%08lx   lo: 0x%08lx \n",
           fp->rhi, fp->rlo);
#else
    pr_info(" r6: 0x%08lx   r7: 0x%08lx   r8: 0x%08lx   r9: 0x%08lx\n",
           fp->regs[0], fp->regs[1], fp->regs[2], fp->regs[3]);
    pr_info("r10: 0x%08lx  r11: 0x%08lx  r12: 0x%08lx  r13: 0x%08lx\n",
           fp->regs[4], fp->regs[5], fp->regs[6], fp->regs[7]);
    pr_info("r14: 0x%08lx   r1: 0x%08lx  r15: 0x%08lx\n",
           fp->regs[8], fp->regs[9], fp->lr);
#endif

    pr_info("\nCODE:");---------------------------------------------------------------加大dump的代碼段范圍,確認覆蓋范圍是多少?
    tp = ((unsigned char *) fp->pc) - 0x40;
    tp += ((int)tp % 4) ? 2 : 0;
    for (sp = (unsigned long *) tp, i = 0; (i < 0xc0);  i += 4) {
        if ((i % 0x10) == 0)
            pr_cont("\n%08x: ", (int) (tp + i));
        pr_cont("%08x ", (int) *sp++);
    }
    pr_cont("\n");

    pr_info("\nKERNEL STACK:");
    tp = ((unsigned char *) fp) - 0x40;
    for (sp = (unsigned long *) tp, i = 0; (i < 0xc0); i += 4) {
        if ((i % 0x10) == 0)
            pr_cont("\n%08x: ", (int) (tp + i));
        pr_cont("%08x ", (int) *sp++);
    }
    pr_cont("\n");

    show_stack(NULL, (unsigned long *)fp->regs[4]);
    return;
}

 

4. Illegal Instruction問題分析(位於Kernel)

Illegal Instruction輸出如下:

[ 2343.202217] Illegal instruction Error
[ 2343.205883] 
[ 2343.205883] CURRENT PROCESS:
[ 2343.205883] 
[ 2343.211728] COMM=syslogd PID=135-----------------------------------發生錯誤進程的信息,代碼數據段。
[ 2343.214963] TEXT=00008000-000c68cc DATA=000c7f1c-000c8175 BSS=000c8175-000ea000
[ 2343.222278] USER-STACK=7f89ae80  KERNEL-STACK=be826580
[ 2343.222278] 
[ 2343.228906] PC: 0x805397de (__skb_try_recv_datagram+0x4e/0x2d8)----0x80000000以上的地址表示內核空間。
[ 2343.234837] LR: 0x805ce90e (unix_dgram_recvmsg+0xa2/0x56c)
[ 2343.240327] SP: 0xbe82bd1c 
[ 2343.243124] orig_a0: 0xbf3b2400
[ 2343.246269] PSR: 0x80040340
[ 2343.249070]  a0: 0xbf3b2400   a1: 0x00000000   a2: 0xbe82bdb8   a3: 0x00000000
[ 2343.256301]  r4: 0xbe82be0c   r5: 0xbf3b2400   r6: 0xbe82be14   r7: 0xbe82be14
[ 2343.263531]  r8: 0xbe82bdbc   r9: 0xbe82be08  r10: 0xbe82be04  r11: 0x00000000
[ 2343.270761] r12: 0x80100340  r13: 0x805397c0  r15: 0x805ce90e
[ 2343.276514] r16: 0xbe82be04  r17: 0xbe82be10  r18: 0xbe8245b4  r19: 0x0000003b
[ 2343.283745] r20: 0x00000000  r21: 0x00000000  r22: 0x00000038  r23: 0x2dc2ae38
[ 2343.290974] r24: 0xbf3b2454  r25: 0x00000001  r26: 0x8004f940  r27: 0x000000ff
[ 2343.298204] r28: 0x2abf5000  r29: 0x00000000  r30: 0x00000000  tls: 0x00000001
[ 2343.305433]  hi: 0x007838aa   lo: 0x33ee4b1f 
[ 2343.309793] 
CODE:------------------------------------------------------------------PC指向的代碼段,非法指令即在此處。發生在內核的地址比較固定。如果在用戶空間,則需要代碼段映射的其實地址。
805397c0: 4820c400 4831c402 e4486dcf d8681003 
805397d0: b260200b 2040d860 da086d43 e560200c 
805397e0: e923fe53 e4610403 e4480040 e5210107 
805397f0: b2602002 20001047 20002070 6026d900 
[ 2343.328840] 
KERNEL STACK:
be82bcdc: be82bcdc 808fec04 00000004 00000001 
be82bcec: 000c8175 000ea000 be82bd10 8004b002 
be82bcfc: be82bd1c bf3b2400 be82be14 be82be14 
be82bd0c: be82be08 be82bdbc 8004a418 be82be0c 
be82bd1c: 00000001 805ce90e 805397de 80040340 
be82bd2c: 2df4eaf0 bf3b2400 bf3b2400 00000000 
be82bd3c: be82bdb8 00000000 be82be0c bf3b2400 
be82bd4c: be82be14 be82be14 be82bdbc be82be08 
be82bd5c: be82be04 00000000 80100340 805397c0 
be82bd6c: be82be04 be82be10 be8245b4 0000003b 
be82bd7c: 00000000 00000000 00000038 2dc2ae38 
be82bd8c: bf3b2454 00000001 8004f940 000000ff 
[ 2343.382450] 
Call Trace:
[<805ce90e>] unix_dgram_recvmsg+0xa2/0x56c
[<8052b6f0>] sock_recvmsg+0x40/0x50
[<8052b786>] sock_read_iter+0x86/0xac
[<80134de8>] __vfs_read+0xc0/0x108
[<80135c14>] vfs_read+0x94/0x128
[<80136d12>] SyS_read+0x52/0xd4
[<8004a246>] csky_systemcall+0x96/0xe0

地址0x805397c0通過csky-abiv2-linux-objudmp -D -S vmlinux之后,如下:

80539790 <__skb_try_recv_datagram>:
...
805397c0:       c4004820        lsli            r0, r0, 0
805397c4:       c4024831        lsli            r17, r2, 0
805397c8:       6dcf            mov             r7, r3
805397ca:       e4481003        subi            r2, r8, 4
805397ce:       d868200b        ld.w            r3, (r8, 0x2c)
805397d2:       b260            st.w            r3, (r2, 0)
805397d4:       d8602040        ld.w            r3, (r0, 0x100)
805397d8:       6d43            mov             r5, r0
805397da:       da08200c        ld.w            r16, (r8, 0x30)
805397de: e5600053 addi r11, r0, 84-----------------------------非法指令異常現場。
805397e2:       e9230103        bnez            r3, 0x805399e8  // 805399e8 <_end+0xffb857e8>
805397e6:       e4612040        andi            r3, r1, 64
805397ea:       e4481007        subi            r2, r8, 8
805397ee:       e5212002        andi            r9, r1, 2
805397f2:       b260            st.w            r3, (r2, 0)
805397f4:       d9472000        ld.w            r10, (r7, 0)
805397f8:       dd702000        st.w            r11, (r16, 0)
805397fc:       c0006026        mfcr            r6, cr<0, 0>
...

將log dump出來的代碼段按照objdump順序排列如下,可以看出紅色加粗部分的不一致:

c4004820
c4024831
6dcf
e4481003
d868200b
b260
d8602040
6d43
da08200c
e560fe53-------------------------------------------------------------------------------PC指向的異常代碼點。
e9230403
e4610040
e4480107
e5212002
b260
10472000
20702000
d9006026

然后在對log dump數據按照地址排列,可以發現紅色加粗部分存在一定規律:突變的8bit位置固定。

805397c0: 4820c400 4831c402 e4486dcf d8681003 
805397d0: b260200b 2040d860 da086d43 e560200c 
805397e0: e923fe53 e4610403 e4480040 e5210107 
805397f0: b2602002 20001047 20002070 6026d900 

可以看出從DDR到CPU的數據明顯的出錯規律。

這種類型的錯誤不像是上面錯誤排查里面的任一種,而類似硬件異常導致的。

4. Illegal Instruction問題定位(位於userspace)

4.1 用戶空間代碼段定位

需要對SIGILL進行處理,打印bin代碼段以及庫代碼段。

signal(SIGILL,sigillDeal);----------------------------------注冊SIGILL對應的處理函數

void sigillDeal(int sig)
{
  if(sig == SIGILL)
  {
    printf("\nGot SIGILL(Illegal Instruction)\n");
    system("cat /proc/`pidof AiApp`/maps");-----------------獲取進程的maps信息。
    raise(SIGSEGV);-----------------------------------------將當前進程的內存存入coredump中,便於后續通過gdb分析導出內存內容。
  }
}

如果內核產生SIGILL信號,用戶空間收到信號就會記錄當前進程maps,並且將進程內存保存到core中。

后面再core中可以使用dump memory到處代碼段和bin文件進行對比。

dump memory  app.bin 0x8000 0x590000

可以通過hexdump將app.bin和對應代碼段對比。

4.2 hexdump對比進程內存和代碼段

bin文件和庫文件都通過mmap到進程的地址空間,在進程的/proc/xxx/map_files中存在一地址范圍為名稱的文件。

通過hexdump讀取這些文件,可以知道文件在內存中的值。

然后和對應的bin、庫文件對比即可。

lr--------    1 root     root          64 Jan  1 08:17 2aaa8000-2aac5000 -> /lib/ld-2.28.9000.so
lr--------    1 root     root          64 Jan  1 08:17 2aac5000-2aac6000 -> /lib/ld-2.28.9000.so
lr--------    1 root     root          64 Jan  1 08:17 2aac6000-2aac7000 -> /lib/ld-2.28.9000.so
lr--------    1 root     root          64 Jan  1 08:17 2aaca000-2aacb000 -> /usr/lib/libtestdevice.so
lr--------    1 root     root          64 Jan  1 08:17 2aacb000-2aacc000 -> /usr/lib/libtestdevice.so
lr--------    1 root     root          64 Jan  1 08:17 2aacc000-2aacd000 -> /usr/lib/libtestdevice.so

具體地址范圍對應的是代碼段還是數據段可以通過/proc/xxx/maps獲取:

2aaa8000-2aac5000 r-xp 00000000 b3:01 524478     /lib/ld-2.28.9000.so
2aac5000-2aac6000 r--p 0001c000 b3:01 524478     /lib/ld-2.28.9000.so
2aac6000-2aac7000 rw-p 0001d000 b3:01 524478     /lib/ld-2.28.9000.so
2aac7000-2aac8000 r-xp 00000000 00:00 0          [vdso]
2aac8000-2aaca000 rw-p 00000000 00:00 0 
2aaca000-2aacb000 r-xp 00000000 b3:01 1180187    /usr/lib/libtestdevice.so
2aacb000-2aacc000 r--p 00000000 b3:01 1180187    /usr/lib/libtestdevice.so
2aacc000-2aacd000 rw-p 00001000 b3:01 1180187    /usr/lib/libtestdevice.so

hexdump 2aaa8000-2aac5000 -n 128 -s 4096和hexdump ld-2.28.9000.so  -n 128 -s 4096結果對比如下:

 

參考文檔:《Illegal Instruction錯誤小談

打賞

免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號  © 2018-2021 CODEPRJ.COM