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

Get the address that caused segmentation fault from core dump using C

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

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

但我想直接检索C中的地址。非常感谢任何信息。谢谢!

注意:我知道如何像小精灵一样解析核心转储。但是我不知道如何获取导致段错误的地址。

BFD (Binary File Descriptor) library 提供了一个 ELF 解析器,它是 binutils 的一部分,并被 gdbreadelf 和其他人使用。然而,它显然相当陈旧和粗糙,因此直接从规范中编写您自己的 ELF 解析器可能更直接。

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

似乎 siginfo object is not stored in the core files. The kernel source for do_coredump() is worth a look. But saving siginfo 似乎是人们正在努力的东西。

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

进一步阅读:

My question is, how can I get the address that caused the core dump in C?

简答:

这个问题有两种解读方式。

  1. 错误指令的地址是什么?

  2. 越界的地址是什么?

Elf 核心转储将所有元信息保存在注释中,它们是 存储在注释段中。笔记有不同的类型。

要回答#1,我们需要获取寄存器。看小精灵header来 找到程序 header table。走程序headertable找 注释 table(输入 PT_NOTE)。走笔记table找到一条笔记 输入 NT_PRSTATUS。这个笔记的有效载荷是一个struct elf_prstatus,可以在linux/elfcore.h中找到。中的一个 该结构的字段是所有通用寄存器。抓 %rip,你就完成了。

对于#2,我们做了类似的事情。这次我们要找的是note 类型 NT_SIGINFO。这篇笔记的有效载荷是一个siginfo_t结构 在 signal.h 中定义。对于适用的信号(SIGILL、SIGFPE、SIGSEGV、 SIGBUS),字段 si_addr 将包含您尝试访问的地址 访问但不能。

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

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

长答案:

我认为 BFD 已经有 25 年的历史了,所以我不会仅仅使用它来将核心文件的内容转储到 linux 盒子上。也许如果您必须编写某种需要处理多种格式的通用代码,但即使那样我也不确定我今天会怎么做。

elf spec 写得很好,根据需要浏览程序 header 的 table 或部分 header 并不难。核心文件中的所有进程元信息都包含在 PT_NOTE 程序段中的一组注释中,只需几行 C 代码即可解析出来。

我写了一个小程序从 x86_68 核心文件中获取寄存器并打印一些元数据。我把它放在 github 上。获取note payload的逻辑在这个函数中:

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 文件的指针和一个注释类型,returns一个指针传递给相关注释的有效负载(如果存在)。各种可能的音符类型在 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 下的 header 中。 xsave 结构是 intel manual 的第 13 章中描述的一对 KB 的浮点信息。它具有 SSE、AVX 和 MPX 寄存器。

NT_FILE 有效负载在 header 中似乎没有关联的结构,但在内核注释 (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 结构并为可变大小的字段四舍五入而不是 8。

最近几天我一直在给这个小程序添加东西。目前它处理文件 header、段 headers、通用寄存器、程序状态、程序信息和回溯。我会在有空的时候添加对其余笔记的支持。这是当前输出:

 $ ./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