使用C获取从核心转储导致分段错误的地址

时间:2016-07-12 13:41:43

标签: c elf coredump

我正在尝试编写一个可以解析核心转储文件的C程序。我的问题是,如何在C中获取导致核心转储的地址?我知道可以从这个答案中使用gdb获取地址:

How can I get GDB to tell me what address caused a segfault?

但我想直接在C中检索地址。任何信息都将受到高度赞赏。谢谢!

注意:我知道如何将核心转储解析为精灵。但我不知道如何获得导致段错误的地址。

2 个答案:

答案 0 :(得分:10)

  

我的问题是,如何获取导致核心转储的地址    在C?

简答:

有两种方法可以解释这个问题。

  1. 故障指令的地址是什么?

  2. 超出界限的地址是什么?

  3. Elf核心转储将所有元信息保存在备注中 存储在注释段中。这些音符有不同的类型。

    要回答#1,我们需要抓住寄存器。看看精灵头 找到程序头表。走程序头表找到 注释表(类型PT_NOTE)。走到便条桌上找一张纸条 键入NT_PRSTATUS。本注释的有效负载是struct elf_prstatus,可以在linux / elfcore.h中找到。其中一个 这个结构的字段是所有通用寄存器。抓 %rip,你就完成了。

    对于#2,我们做类似的事情。这次我们正在寻找一张便条 类型NT_SIGINFO。此注释的有效负载是siginfo_t结构 在signal.h中定义。适用信号(SIGILL,SIGFPE,SIGSEGV, SIGBUS),字段si_addr将包含您尝试过的地址 访问但不能。

    更多信息如下。在示例核心转储中,rip是0x400560,即尝试进行非法访问的指令地址。这与通用寄存器的其余部分一起显示。

    程序试图访问的内存是0x03。这与其余信号信息一起显示。

    答案很长:

    我认为BFD已经有25年了,所以我不会仅仅将它用于在linux机器上转储核心文件的内容。也许你必须编写一些需要使用一堆格式的通用代码,但即便如此,我也不确定今天是怎么回事。

    elf spec编写得非常好,并且根据需要遍历程序头或节头的表格并不难。核心文件中的所有进程元信息都包含在PT_NOTE程序段中的一组注释中,这些注释可以在几行直接C代码中解析出来。

    我写了一个小程序来从x86_68核心文件中获取寄存器并打印一些元数据。我把它放在github上。获取笔记有效负载的逻辑在此函数中:

    void *get_note(void *vp, int nt_type){
        Elf64_Ehdr *eh=vp;
        for(int i=0; i<eh->e_phnum; ++i){
            Elf64_Phdr *ph=(vp+eh->e_phoff+i*eh->e_phentsize);
            if(ph->p_type!=PT_NOTE){
                continue;
            }
            void *note_table=(vp + ph->p_offset);
            void *note_table_end=(note_table+ph->p_filesz);
            Elf64_Nhdr *current_note=note_table;
            while(current_note<(Elf64_Nhdr *)note_table_end){
                void *note_end=current_note;
                note_end += 3*sizeof(Elf64_Word);
                note_end += roundup8(current_note->n_namesz);
                if(current_note->n_type==nt_type){
                    return note_end;
                }
                note_end += roundup8(current_note->n_descsz);
                current_note=note_end;          
            }
        }
        return 0;
    }
    

    该函数被传递给指向elf文件的指针和一个音符类型,并返回一个指针,指示相关音符的有效负载(如果存在)。各种可能的笔记类型都在elf.h中。我在机器核心文件中看到的笔记类型是:

    #define NT_PRSTATUS 1       /* Contains copy of prstatus struct */
    #define NT_FPREGSET 2       /* Contains copy of fpregset struct */
    #define NT_PRPSINFO 3       /* Contains copy of prpsinfo struct */
    #define NT_AUXV     6       /* Contains copy of auxv array */
    #define NT_X86_XSTATE   0x202       /* x86 extended state using xsave */
    #define NT_SIGINFO  0x53494749  /* Contains copy of siginfo_t,
                                       size might increase */
    #define NT_FILE     0x46494c45  /* Contains information about mapped
                                       files */
    

    这些结构中的大多数位于/ usr / include / linux下的头文件中。 xsave结构是intel manual的第13章中描述的几KB浮点信息。它有SSE,AVX和MPX寄存器。

    NT_FILE有效内容似乎在标头中没有关联的结构,但它在内核注释(fs / binfmt_elf.c)中描述:

    /*
     * Format of NT_FILE note:
     *
     * long count     -- how many files are mapped
     * long page_size -- units for file_ofs
     * array of [COUNT] elements of
     *   long start
     *   long end
     *   long file_ofs
     * followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
     */
    

    解析32位系统的elf文件的变化非常简单。对于可变大小的字段,使用相应的Elf32_XXX结构并向上舍入4而不是8。

    过去几天我一直在为这个小程序添加内容。目前它执行文件头,段头,通用寄存器,程序状态,程序信息和回溯。随着时间的推移,我会添加对其余笔记的支持。这是当前的输出:

     $ ./read_pc -biprst core
    General Registers: 
    r15     0x000000000000000000  r14     0x000000000000000000  
    r13     0x0000007ffc20d36a50  r12     0x000000000000400430  
    rbp     0x0000007ffc20d36950  rbx     0x000000000000000000  
    r11     0x000000000000000246  r10     0x000000000000000000  
    r9      0x000000000000000002  r8      0x000000000000000000  
    rax     0x000000000000000003  rcx     0x00000000007ffffffe  
    rdx     0x0000007f5817523780  rsi     0x000000000000000001  
    rdi     0x000000000000000001  ss      0x00000000000000002b  
    rip     0x000000000000400560  cs      0x000000000000000033  
    eflags  0x000000000000010246  rsp     0x0000007ffc20d36950  
    fs_base 0x0000007f5817723700  gs_base 0x000000000000000000  
    ds      0x000000000000000000  es      0x000000000000000000  
    fs      0x000000000000000000  gs      0x000000000000000000  
    orig_rax 0x00ffffffffffffffff  
    
    Program status: 
    signo 11 signal code 0 errno 0
    cursig 11 sigpend 000000000000000000 sigheld 000000000000000000
    pid 27547 ppid 26600 pgrp 27547 sid 26600
    utime: 0.000000 stime 0.000000
    cutime: 0.000000 cstime 0.000000
    fpvalid: 1
    
    
    Signal Information: 
    signo: 11 errno 0 code 1
    addr 0x3 addr_lsb 0 addr_bnd ((nil), (nil))
    
    
    Process Information:
    state 0 (R) zombie 0 nice 0 flags 0x400600
    uid 1000 gid 1000 pid 27547 ppid 26600 pgrp 27547 sid 26600
    fname: foo
    args: ./foo 
    
    
    Backtrace: 
    rip = 0x000000000000400560
    rip = 0x000000000000400591
    rip = 0x0000000000004005a1
    
    
    Program Headers:
       Type      Offset             Virt Addr          PhysAddr          
                 FileSiz            MemSize              Flags  Align    
     NOTE      0x00000000000004a0 0x0000000000000000 0000000000000000
               0x0000000000000b98 0x0000000000000000         0x000000
     LOAD      0x0000000000002000 0x0000000000400000 0000000000000000
               0x0000000000001000 0x0000000000001000 R X     0x001000
     LOAD      0x0000000000003000 0x0000000000600000 0000000000000000
               0x0000000000001000 0x0000000000001000   X     0x001000
     LOAD      0x0000000000004000 0x0000000000601000 0000000000000000
               0x0000000000001000 0x0000000000001000  WX     0x001000
     LOAD      0x0000000000005000 0x00000000018bf000 0000000000000000
               0x0000000000021000 0x0000000000021000  WX     0x001000
     LOAD      0x0000000000026000 0x00007f581715e000 0000000000000000
               0x0000000000001000 0x00000000001c0000 R X     0x001000
     LOAD      0x0000000000027000 0x00007f581731e000 0000000000000000
               0x0000000000000000 0x00000000001ff000         0x001000
     LOAD      0x0000000000027000 0x00007f581751d000 0000000000000000
               0x0000000000004000 0x0000000000004000   X     0x001000
     LOAD      0x000000000002b000 0x00007f5817521000 0000000000000000
               0x0000000000002000 0x0000000000002000  WX     0x001000
     LOAD      0x000000000002d000 0x00007f5817523000 0000000000000000
               0x0000000000004000 0x0000000000004000  WX     0x001000
     LOAD      0x0000000000031000 0x00007f5817527000 0000000000000000
               0x0000000000001000 0x0000000000026000 R X     0x001000
     LOAD      0x0000000000032000 0x00007f5817722000 0000000000000000
               0x0000000000003000 0x0000000000003000  WX     0x001000
     LOAD      0x0000000000035000 0x00007f581774a000 0000000000000000
               0x0000000000002000 0x0000000000002000  WX     0x001000
     LOAD      0x0000000000037000 0x00007f581774c000 0000000000000000
               0x0000000000001000 0x0000000000001000   X     0x001000
     LOAD      0x0000000000038000 0x00007f581774d000 0000000000000000
               0x0000000000001000 0x0000000000001000  WX     0x001000
     LOAD      0x0000000000039000 0x00007f581774e000 0000000000000000
               0x0000000000001000 0x0000000000001000  WX     0x001000
     LOAD      0x000000000003a000 0x00007ffc20d16000 0000000000000000
               0x0000000000022000 0x0000000000022000  WX     0x001000
     LOAD      0x000000000005c000 0x00007ffc20d9c000 0000000000000000
               0x0000000000002000 0x0000000000002000   X     0x001000
     LOAD      0x000000000005e000 0x00007ffc20d9e000 0000000000000000
               0x0000000000002000 0x0000000000002000 R X     0x001000
     LOAD      0x0000000000060000 0xffffffffff600000 0000000000000000
               0x0000000000001000 0x0000000000001000 R X     0x001000
    All worked
    

答案 1 :(得分:5)

BFD (Binary File Descriptor) library提供了一个ELF解析器,它是binutils的一部分,由gdbreadelf和其他人使用。然而,它显然是相当古老和苛刻,所以直接从规范编写自己的ELF解析器可能更直接。

运行时库通常会安装一个信号处理程序来捕获故障(例如SIGSEVSIGBUS等)和abort。要获取故障的地址,您很可能需要展开堆栈才能进行回溯。您还需要使用符号表来查找地址以匹配函数名称。这可以作为二进制文件(在调试版本中)的一部分或单独的符号表文件。您之后的错误地址是_siginfo._sifields._sigfault.si_addr

似乎是siginfo object is not stored in the core fileskernel source for do_coredump()值得一看。但saving siginfo似乎是人们正在努力的事情。

@evaitl给出了一个很好的答案,所以我的投票就在那里。 :)

进一步阅读: