手写最小x86_64 Hello World ELF二进制段错误排查
核心错误排查点
手写x86_64最小ELF在启动阶段触发SIGSEGV,90%是以下配置错误导致:
- ELF头基础字段错误:64位ELF必须保证
e_ident[EI_CLASS]值为2、e_machine值为0x3E(对应x86_64架构),程序头偏移e_phoff必须指向实际程序头的文件偏移,e_ehsize固定为64、64位程序头表项大小e_phentsize固定为56,任何一个字段填错都会导致内核无法正确解析ELF结构,加载阶段直接崩溃。 - 程序头配置不符合加载规则
- 可加载段的
p_type必须设为PT_LOAD=1,如果误填其他值内核不会创建对应内存映射 - 段权限位必须匹配使用场景:代码段需要
PF_X=1执行权限,字符串所在区域需要PF_R=4读权限,单段合并代码、数据、头结构的最小ELF必须把权限设为PF_R|PF_W|PF_X=7 - 对齐规则必须满足:
p_align为页大小(通常0x1000)的整数倍,且p_vaddr % p_align == p_offset % p_align,不满足该条件内核会直接拒绝映射 - 段长度
p_filesz、p_memsz必须覆盖所有需要访问的内容(包括ELF头、程序头、机器码、字符串),长度不足会导致部分内容不在映射范围内,访问时触发段错误
- 可加载段的
- 入口点地址错误:
e_entry必须填写代码对应的实际虚拟地址,而非文件偏移;且入口点必须落在标记为可执行的LOAD段地址范围内,否则跳转到不可执行/未映射地址直接触发段错。 - 机器码寻址错误:如果字符串紧跟机器码存放,要注意指令中填写的字符串地址是虚拟地址,不是相对文件偏移;如果用RIP相对寻址,要算准指令和字符串的相对偏移,否则sys_write调用时访问非法地址触发崩溃。
手写ELF的标准调试流程
按以下顺序排查可以快速定位问题,不要同时修改ELF结构和机器码:
- 静态校验优先
先执行readelf -a 你的二进制,不要忽略任何输出警告,重点核对:- 程序头的Type列是否为
LOAD,Flg列权限是否匹配 - Align值是否为0x1000,
VirtAddr % Align是否等于Offset % Align - Entry point address是否落在可执行LOAD段的地址区间内
- 段的大小是否覆盖整个文件长度
- 程序头的Type列是否为
- 用strace确认崩溃阶段
执行strace ./你的二进制,如果输出中没有出现write系统调用的记录,说明错误发生在内核加载ELF阶段、还没执行到你写的机器码,问题出在ELF头/程序头配置;如果出现了write调用但返回-1 EFAULT,说明机器码里的字符串地址写错了。 - gdb动态核对映射和指令
启动gdb后不要直接run,执行starti让程序停在第一条用户态指令位置:- 执行
info proc mappings,核对ELF段是否被映射到你设置的虚拟地址,权限是否正确 - 执行
x/20i $pc反汇编当前PC位置的指令,和你手写的机器码对比,如果指令不匹配,说明入口点地址错误或者段偏移配置错误 - 执行
x/s 你写的字符串地址,看能不能正确读到"Hello, World!\n"字符串,读不到就是地址计算错误或者段长度不足
- 执行
- 机器码单独验证
先把你写的syscall逻辑和字符串放到正常汇编器生成的最小ELF中运行,确认机器码本身逻辑正确,再调整ELF头字段做体积压缩,排除机器码本身的错误。
可正常运行的116字节单段最小ELF参考配置:
- ELF头:e_type=2(可执行文件),e_entry=0x400078,e_phoff=0x40,e_phnum=1
- 程序头:p_type=1,p_flags=7,p_offset=0,p_vaddr=0x400000,p_filesz=116,p_memsz=116,p_align=0x1000
- 偏移0x78位置放置syscall指令:
mov rax,1; mov rdi,1; mov rsi,0x40008b; mov rdx,14; syscall; mov rax,60; mov rdi,0; syscall,后紧跟14字节的"Hello, World!\n"字符串
内容的提问来源于stack exchange,提问作者DieDummheitInPerson
相关产品推荐
相关产品推荐

