在 64 位上执行 32 位二进制时的 SIGSEGV Linux

SIGSEGV when executing 32 bit binary on 64 bit Linux

您可能听说过 kragen 的一个项目 StoneKnifeForth:https://github.com/kragen/stoneknifeforth。它是一个充当小型 Forth 解释器的 Python 程序和一个充当 Forth 编译器的 Forth 程序。因此,您可以同时使用这两个来构建 Forth 编译器二进制文件。

将 StoneKnifeForth 移植到 C++ (https://github.com/tekknolagi/stoneknifecpp) 后,我注意到 StoneKnifeForth(任一变体)生成的所有二进制文件在 64 位上都存在段错误 Linux。也就是说,如果您克隆 stoneknifecpp 和 运行:

make
./l01compiler  # produced by the Forth program

您将获得以下内容:

willow% ./l01compiler                 
[1]    31614 segmentation fault  ./l01compiler

显然,这不是一个非常有趣的错误消息,所以我想我会跟踪它:

willow% strace ./l01compiler
execve("./l01compiler", ["./l01compiler"], [/* 110 vars */]) = -1 EPERM (Operation not permitted)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} ---
+++ killed by SIGSEGV +++
[1]    31615 segmentation fault (core dumped)  strace ./l01compiler

并获得了...更多信息。看来ELFheader是错的不知何故,除了下面两个有趣的花絮:

即使在互联网上搜索 32 位和 64 位 Linux 内核之间的 ELF header 格式之间可能存在的差异等之后,我也有点不知所措。如果谁有任何信息,我会很高兴。

我附上了下面的header:

willow% readelf -h l01compiler   
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x1e39
  Start of program headers:          52 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         0
  Section header string table index: 0

非常感谢 Tom Hebb (tchebb) 证实了我的怀疑并解决了这个问题。从 this commit 中可以看出,问题是原始地址太低了。它与 32 位或 64 位无关,而是较早的内核与较新的内核。

较新的内核增加了vm.mmap_min_addr sysctl 参数,这意味着旧的内核将完全禁止程序启动。这解释了为什么 sudo 有效。正如汤姆所解释的那样,"Unless you invoke qemu with KVM support, qemu is an emulator not a hypervisor, so it simulates the entire address space and virtual memory subsystem in software and presumably doesn't impose any load address restrictions."