英文:
Why is loading ELF64 headers causing a segmentation fault?
问题
I am trying to create an ELF64 file from scratch; the program I'm using to test it right now just calls the exit() syscall using assembly; there's no linking of libraries etc. Here's the output of "dumpelf":
#include <elf.h>;
/*
* ELF dump of 'elftest'
* 4120 (0x1018) bytes
*/
Elf64_Dyn dumpedelf_dyn_0[];
struct {
Elf64_Ehdr ehdr;
Elf64_Phdr phdrs[3];
Elf64_Shdr shdrs[3];
Elf64_Dyn *dyns;
} dumpedelf_0 = {
.ehdr = {
.e_ident = { /* (EI_NIDENT bytes) */
/* [0] EI_MAG: */ 0x7F,'E','L','F',
/* [4] EI_CLASS: */ 2 , /* (ELFCLASS64) */
/* [5] EI_DATA: */ 1 , /* (ELFDATA2LSB) */
/* [6] EI_VERSION: */ 1 , /* (EV_CURRENT) */
/* [7] EI_OSABI: */ 0 , /* (ELFOSABI_NONE) */
/* [8] EI_ABIVERSION: */ 0 ,
/* [9-15] EI_PAD: */ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
},
.e_type = 2 , /* (ET_EXEC) */
.e_machine = 62 , /* (EM_X86_64) */
.e_version = 1 , /* (EV_CURRENT) */
.e_entry = 0x400000 , /* (start address at runtime) */
.e_phoff = 64 , /* (bytes into file) */
.e_shoff = 232 , /* (bytes into file) */
.e_flags = 0x0 ,
.e_ehsize = 64 , /* (bytes) */
.e_phentsize = 56 , /* (bytes) */
.e_phnum = 3 , /* (program headers) */
.e_shentsize = 64 , /* (bytes) */
.e_shnum = 3 , /* (section headers) */
.e_shstrndx = 1
},
.phdrs = {
/* Program Header #0 0x40 */
{
.p_type = 6 , /* [PT_PHDR] */
.p_offset = 64 , /* (bytes into file) */
.p_vaddr = 0x40 , /* (virtual addr at runtime) */
.p_paddr = 0x40 , /* (physical addr at runtime) */
.p_filesz = 168 , /* (bytes in file) */
.p_memsz = 168 , /* (bytes in mem at runtime) */
.p_flags = 0x4 , /* PF_R */
.p_align = 8 , /* (min mem alignment in bytes) */
},
/* Program Header #1 0x78 */ <-- PT_NULL=OK, PT_LOAD=SEGFAULT
{
.p_type = 1 , /* [PT_LOAD] */
.p_offset = 0 , /* (bytes into file) */
.p_vaddr = 0x0 , /* (virtual addr at runtime) */
.p_paddr = 0x0 , /* (physical addr at runtime) */
.p_filesz = 441 , /* (bytes in file) */
.p_memsz = 441 , /* (bytes in mem at runtime) */
.p_flags = 0x4 , /* PF_R */
.p_align = 4096 , /* (min mem alignment in bytes) */
},
/* Program Header #2 0xB0 */ <-- WORKS AS EXPECTED
{
.p_type = 1 , /* [PT_LOAD] */
.p_offset = 4096 , /* (bytes into file) */
.p_vaddr = 0x400000 , /* (virtual addr at runtime) */
.p_paddr = 0x400000 , /* (physical addr at runtime) */
.p_filesz = 24 , /* (bytes in file) */
.p_memsz = 24 , /* (bytes in mem at runtime) */
.p_flags = 0x5 , /* PF_R | PF_X */
.p_align = 4096 , /* (min mem alignment in bytes) */
},
},
.shdrs = {
/* Section Header #0 '' 0xE8 */
{
.sh_name = 0 ,
.sh_type = 0 , /* [SHT_NULL] */
.sh_flags = 0 ,
.sh_addr = 0x0 ,
.sh_offset = 0 , /* (bytes) */
.sh_size = 0 , /* (bytes) */
.sh_link = 0 ,
.sh_info = 0 ,
.sh_addralign = 0 ,
.sh_entsize = 0
},
/* Section Header #1 '.shstrtab' 0x128 */
{
.sh_name = 1 ,
.sh_type = 3 , /* [SHT_STRTAB] */
.sh_flags = 32 ,
.sh_addr = 0x1A8 ,
.sh_offset = 424 , /* (bytes) */
.sh_size = 17 , /* (bytes) */
.sh_link = 0 ,
.sh_info = 0 ,
.sh_addralign = 1 ,
.sh_entsize = 1
},
/* Section Header #2 '.text' 0x168 */
{
.sh_name = 11 ,
.sh_type = 1 , /* [SHT_PROGBITS] */
.sh_flags = 6 ,
.sh_addr = 0x400000 ,
.sh_offset = 4096 , /* (bytes) */
.sh_size = 24 , /* (bytes) */
.sh_link = 0 ,
.sh_info = 0 ,
.sh_addralign = 16 ,
.sh_entsize = 0
},
},
.dyns = dumpedelf_dyn_0,
};
Elf64_Dyn dumpedelf_dyn_0[] = {
/* no dynamic tags ! */ };
If I try to execute the program in x86_64 Linux, the program segfaults. Attempting to debug it in gdb is literally a non-starter
英文:
I am trying to create an ELF64 file from scratch; the program I'm using to test it right now just calls the exit() syscall using assembly; there's no linking of libraries etc. Here's the output of "dumpelf":
#include <elf.h>
/*
* ELF dump of 'elftest'
* 4120 (0x1018) bytes
*/
Elf64_Dyn dumpedelf_dyn_0[];
struct {
Elf64_Ehdr ehdr;
Elf64_Phdr phdrs[3];
Elf64_Shdr shdrs[3];
Elf64_Dyn *dyns;
} dumpedelf_0 = {
.ehdr = {
.e_ident = { /* (EI_NIDENT bytes) */
/* [0] EI_MAG: */ 0x7F,'E','L','F',
/* [4] EI_CLASS: */ 2 , /* (ELFCLASS64) */
/* [5] EI_DATA: */ 1 , /* (ELFDATA2LSB) */
/* [6] EI_VERSION: */ 1 , /* (EV_CURRENT) */
/* [7] EI_OSABI: */ 0 , /* (ELFOSABI_NONE) */
/* [8] EI_ABIVERSION: */ 0 ,
/* [9-15] EI_PAD: */ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
},
.e_type = 2 , /* (ET_EXEC) */
.e_machine = 62 , /* (EM_X86_64) */
.e_version = 1 , /* (EV_CURRENT) */
.e_entry = 0x400000 , /* (start address at runtime) */
.e_phoff = 64 , /* (bytes into file) */
.e_shoff = 232 , /* (bytes into file) */
.e_flags = 0x0 ,
.e_ehsize = 64 , /* (bytes) */
.e_phentsize = 56 , /* (bytes) */
.e_phnum = 3 , /* (program headers) */
.e_shentsize = 64 , /* (bytes) */
.e_shnum = 3 , /* (section headers) */
.e_shstrndx = 1
},
.phdrs = {
/* Program Header #0 0x40 */
{
.p_type = 6 , /* [PT_PHDR] */
.p_offset = 64 , /* (bytes into file) */
.p_vaddr = 0x40 , /* (virtual addr at runtime) */
.p_paddr = 0x40 , /* (physical addr at runtime) */
.p_filesz = 168 , /* (bytes in file) */
.p_memsz = 168 , /* (bytes in mem at runtime) */
.p_flags = 0x4 , /* PF_R */
.p_align = 8 , /* (min mem alignment in bytes) */
},
/* Program Header #1 0x78 */ <-- PT_NULL=OK, PT_LOAD=SEGFAULT
{
.p_type = 1 , /* [PT_LOAD] */
.p_offset = 0 , /* (bytes into file) */
.p_vaddr = 0x0 , /* (virtual addr at runtime) */
.p_paddr = 0x0 , /* (physical addr at runtime) */
.p_filesz = 441 , /* (bytes in file) */
.p_memsz = 441 , /* (bytes in mem at runtime) */
.p_flags = 0x4 , /* PF_R */
.p_align = 4096 , /* (min mem alignment in bytes) */
},
/* Program Header #2 0xB0 */ <-- WORKS AS EXPECTED
{
.p_type = 1 , /* [PT_LOAD] */
.p_offset = 4096 , /* (bytes into file) */
.p_vaddr = 0x400000 , /* (virtual addr at runtime) */
.p_paddr = 0x400000 , /* (physical addr at runtime) */
.p_filesz = 24 , /* (bytes in file) */
.p_memsz = 24 , /* (bytes in mem at runtime) */
.p_flags = 0x5 , /* PF_R | PF_X */
.p_align = 4096 , /* (min mem alignment in bytes) */
},
},
.shdrs = {
/* Section Header #0 '' 0xE8 */
{
.sh_name = 0 ,
.sh_type = 0 , /* [SHT_NULL] */
.sh_flags = 0 ,
.sh_addr = 0x0 ,
.sh_offset = 0 , /* (bytes) */
.sh_size = 0 , /* (bytes) */
.sh_link = 0 ,
.sh_info = 0 ,
.sh_addralign = 0 ,
.sh_entsize = 0
},
/* Section Header #1 '.shstrtab' 0x128 */
{
.sh_name = 1 ,
.sh_type = 3 , /* [SHT_STRTAB] */
.sh_flags = 32 ,
.sh_addr = 0x1A8 ,
.sh_offset = 424 , /* (bytes) */
.sh_size = 17 , /* (bytes) */
.sh_link = 0 ,
.sh_info = 0 ,
.sh_addralign = 1 ,
.sh_entsize = 1
},
/* Section Header #2 '.text' 0x168 */
{
.sh_name = 11 ,
.sh_type = 1 , /* [SHT_PROGBITS] */
.sh_flags = 6 ,
.sh_addr = 0x400000 ,
.sh_offset = 4096 , /* (bytes) */
.sh_size = 24 , /* (bytes) */
.sh_link = 0 ,
.sh_info = 0 ,
.sh_addralign = 16 ,
.sh_entsize = 0
},
},
.dyns = dumpedelf_dyn_0,
};
Elf64_Dyn dumpedelf_dyn_0[] = {
/* no dynamic tags ! */ };
If I try to execute the program in x86_64 Linux, the program segfaults. Attempting to debug it in gdb is literally a non-starter, GDB tells me the program segfaults "during startup" so no help there.
After several days of beating my head against this problem, I can not for the life of me figure out why this is causing a segfault; I have looked at numerous other executable files including system binaries and files compiled with various different programs, they all load the ELF headers at vaddr=0x0 without problems so why does it fail in this case?
If I change Program Header #1 0x78
from PT_LOAD
to PT_NULL
to not load the ELF64 headers into memory, the program runs as expected and exits without error so I know the executable part is OK. This narrows the problem down to that header but it isn't a workable solution.
Edit: Here's the same file as viewed with readelf -a
:
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: 0x400000
Start of program headers: 64 (bytes into file)
Start of section headers: 232 (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: 3
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] .shstrtab STRTAB 00000000000001a8 000001a8
0000000000000011 0000000000000001 S 0 0 1
[ 2] .text PROGBITS 0000000000400000 00001000
0000000000000018 0000000000000000 AX 0 0 16
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),
D (mbind), l (large), p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000000a8 0x00000000000000a8 R 0x8
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000001b9 0x00000000000001b9 R 0x1000
LOAD 0x0000000000001000 0x0000000000400000 0x0000000000400000
0x0000000000000018 0x0000000000000018 R E 0x1000
Section to Segment mapping:
Segment Sections...
00
01
02 .text
There is no dynamic section in this file.
There are no relocations in this file.
No processor specific unwind information to decode
No version information found in this file.
Edit #2: Added output of strace as both ordinary user, and as root
which surprisingly works:
$ strace ./filename
execve("./filename", ["./filename"], 0x7ffd660b1910 /* 28 vars */) = -1 EPERM (Operation not permitted)
+++ killed by SIGSEGV +++
Segmentation fault
(as root)
# strace ./filename
execve("./filename", ["./filename"], 0x7ffd06138b90 /* 27 vars */) = 0
exit(0) = ?
+++ exited with 0 +++
$ stat filename
File: filename
Size: 4120 Blocks: 16 IO Block: 4096 regular file
Device: 820h/2080d Inode: 1920 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 1000/ username) Gid: ( 1000/ username)
Access: 2023-06-15 13:24:24.007538545 +0200
Modify: 2023-06-15 13:24:16.787538566 +0200
Change: 2023-06-15 13:24:16.787538566 +0200
Birth: 2023-06-15 13:24:16.787538566 +0200
答案1
得分: 4
在经过数天的苦苦探索后,我简直想不明白为什么这会导致段错误。
实际上,这并不会导致SIGSEGV
。相反,内核会检查你的二进制文件并表示“无法执行”,然后发送一个“用火烧毁”的信号。
原因与这个答案相同:你有一个ET_EXEC
文件(必须加载到链接的地址,你设置为0
)。但是内核不会加载任何地址低于0x10000
的东西(有一个内核常数定义了最低允许的地址,我没有追踪到它)。
你可以将二进制文件更改为ET_DYN
(使其成为PIE
二进制文件;可以加载到任意位置),或者将ET_EXEC
链接到地址0x10000
或更高的位置,然后它就能正常工作了。
英文:
> After several days of beating my head against this problem, I can not for the life of me figure out why this is causing a segfault
It's not actually causing a SIGSEGV
. Rather, the kernel looks at your binary and says "no can't do", and sends it a "kill with fire" signal.
The reason is the same as this answer: you have an ET_EXEC
file (which must be loaded at the linked-at address, which you have as 0
). But the kernel will not load anything below 0x10000
(there is some kernel constant which defines the lowest allowed address, I haven't tracked it down).
You can change the binary to ET_DYN
(making it a PIE
binary; which can be loaded at arbitrary location), or you can link that ET_EXEC
at address 0x10000
or above, and it would start working.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论