linux - 无法在 GDB 中加载自定义 ELF 可执行文件

标签 linux gdb elf

我目前正在编写一个编译器(http://curly-lang.org 如果您好奇的话),并且在尝试在最新的 Linux 内核上运行生成的 ELF 二进制文件时遇到了一个奇怪的错误。相同的二进制文件在旧内核上运行良好(我试过几个 Ubuntu 机器,uname 4.4.0-1049-aws),但在我更新的 Arch 机器(uname 4.17.11-arch1)上,我什至无法打开它们在 GDB 下。

GDB 给出的错误消息是During startup program terminated with signal SIGSEGV, Segmentation fault,据我所知,这表明在第一条指令运行之前加载程序段失败。

我用 GCC/NASM 编译了一个最小的 ELF 可执行文件来尝试重现这个问题,但是 GCC 生成的可执行文件加载顺利,而我的程序肯定没有。

这是两个可执行文件的 readelf -a 的打印输出,以供引用。第一个是我的编译器生成的程序:

$ readelf -a my-program
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400040
  Start of program headers:          2400 (bytes into file)
  Start of section headers:          2568 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         6
  Section header string table index: 1

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1]                   STRTAB           0000000000000000  00000b88
       000000000000009e  0000000000000000           0     0     0
  [ 2] .init             PROGBITS         0000000000400040  00000040
       000000000000006b  0000000000000000  AX       0     0     0
  [ 3] .text             PROGBITS         00000000004000ab  000000ab
       0000000000000824  0000000000000000  AX       0     0     0
  [ 4] .data             PROGBITS         00000000008008cf  000008cf
       0000000000000091  0000000000000000  WA       0     0     0
  [ 5] .symtab           SYMTAB           0000000000000000  00000c26
       00000000000000c0  0000000000000018           1     8     0
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000040 0x0000000000400040 0x0000000000000000
                 0x000000000000006b 0x000000000000006b  R E    0x1000
  LOAD           0x00000000000000ab 0x00000000004000ab 0x0000000000000000
                 0x0000000000000824 0x0000000000000824  R E    0x1000
  LOAD           0x00000000000008cf 0x00000000008008cf 0x0000000000000000
                 0x0000000000000091 0x0000000000000091  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .init 
   01     .text 
   02     .data 

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Symbol table '.symtab' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 00000000004004e0     0 NOTYPE  LOCAL  HIDDEN     3 .text.argument
     1: 00000000004005a0     0 NOTYPE  LOCAL  HIDDEN     3 .text.constant
     2: 0000000000400270     0 NOTYPE  LOCAL  HIDDEN     3 .text.memextend-page
     3: 0000000000400210     0 NOTYPE  LOCAL  HIDDEN     3 .text.memextend-pool-32
     4: 00000000004002b0     0 NOTYPE  LOCAL  HIDDEN     3 .text.unit
     5: 00000000004005f0     0 NOTYPE  LOCAL  HIDDEN     3 .text.write
     6: 00000000008008d0     0 NOTYPE  LOCAL  HIDDEN     4 .data.brkaddr
     7: 0000000000400040     0 NOTYPE  LOCAL  HIDDEN     2 .init.brkaddr-init

No version information found in this file.

对于 GCC 生成的程序:

$ readelf -a gcc-program
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400110
  Start of program headers:          64 (bytes into file)
  Start of section headers:          336 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         5
  Section header string table index: 4

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.build-i NOTE             00000000004000e8  000000e8
       0000000000000024  0000000000000000   A       0     0     4
  [ 2] .text             PROGBITS         0000000000400110  00000110
       0000000000000010  0000000000000000  AX       0     0     16
  [ 3] .data             PROGBITS         0000000000600120  00000120
       0000000000000001  0000000000000000  WA       0     0     4
  [ 4] .shstrtab         STRTAB           0000000000000000  00000121
       000000000000002a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000120 0x0000000000000120  R E    0x200000
  LOAD           0x0000000000000120 0x0000000000600120 0x0000000000600120
                 0x0000000000000001 0x0000000000000001  RW     0x200000
  NOTE           0x00000000000000e8 0x00000000004000e8 0x00000000004000e8
                 0x0000000000000024 0x0000000000000024  R      0x4

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .text 
   01     .data 
   02     .note.gnu.build-id 

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

No version information found in this file.

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size   Description
  GNU                  0x00000014   NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 1a3e678b08996ee6a9d289c3f76c7c52cd4a30aa

如您所见,我已尝试镜像 GCC 的段放置(代码为 ~0x400000,数据为 ~0x800000),并且两个 ELF header 完全相同。我能想到的唯一有意义的区别是我的自定义二进制文件有两个 LOAD 段(一个用于初始化代码,一个用于其余部分)共享同一页,而 GCC 只生成一个代码 LOAD 段。不过,这不应该成为问题,因为它们共享相同的权限并且不重叠。

除此之外,我看不出有什么可能会阻止第一个程序正确加载。如果任何精通 Linux ELF 加载程序奥秘的人能启发我,那将不胜感激。

感谢您的关注,

最佳答案

没关系,一直以来都是页面共享段导致了问题。

考虑到问题可能出在内核加载程序中,我应该考虑更早地运行 dmesg,在那里我会注意到以下消息,一清二楚:

    [54178.211348] 12766 (my-program): Uhuuh, elf segment at 0000000000400000 requested but the memory is mapped already

显然,some benevolent mastermind 3 个月前决定,最好实际捕获双重映射错误,而不是像我们在 ELF 加载程序中一直做的那样让它们默默地消失。

并不是说我的二进制文件曾经是正确的,而是之前没有发现它们造成的错误。我不知道我应该为我的 bug 一直躲过检测而感到自豪还是感到羞耻。

无论如何,我留下这个答案是为了警告任何愚蠢到在 ELF 二进制文件的单个页面上映射多个段的人:不要。没有尝试。

PS:@rodrigo:感谢您的回答,在您指出之前我什至没有注意到 PhysAddr。手册说它们用于“与物理寻址相关的系统”,这里似乎不是这种情况,但下次我会记得留意它们。

关于linux - 无法在 GDB 中加载自定义 ELF 可执行文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51656713/

相关文章:

c++ - 是否可以以编程方式设置 gdb 观察点?

c - 简单的缓冲区溢出漏洞利用 shellcode 不起作用

linux - ELF 可执行文件 : required version information for imported symbols

macos - 可执行文件中公共(public)字符串的含义?

linux - exec 函数系列内部如何工作?

linux - 如何在 ssh 命令中转义引号

linux - 连接关闭后 Socat 终止

c++11 - 在 gdb 中调用 std::~basic_string()

linux - 在 Linux 中复制具有特定日期的文件夹及其文件

linux - 为什么最新版本的 sys-stat 在杀死后不显示平均值?