64位Linux下机器码打印字符串触发Segmentation fault问题排查
64位Linux手动编写ELF机器码程序段错误问题
机器码十六进制内容
00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000 .ELF............ 00000010: 0200 3e00 0100 0000 7800 4000 0000 0000 ..>.....x.@..... 00000020: 4000 0000 0000 0000 0000 0000 0000 0000 @............... 00000030: 0000 0000 4000 3800 0100 4000 0000 0000 ....@.8...@..... 00000040: 0100 0000 0700 0000 0000 0000 0000 0000 ................ 00000050: 0000 4000 0000 0000 0000 4000 0000 0000 ..@.......@..... 00000060: 8e00 0000 0000 0000 8e00 0000 0000 0000 ................ 00000070: 0010 0000 0000 0000 48b9 0000 0000 0000 ........H....... 00000080: 0001 bf00 0000 0000 4000 88bb 0000 0000 ........@....... 00000090: 0000 0005 c000 0000 0000 0000 010f 05c7 ................ 000000a0: c03c 0000 0048 c7c7 0000 0000 0f05 6865 .<...H........he 000000b0: 6c6c 6f00 0a llo..
指令与机器码对应关系(存在错误)
mov rax, 1对应b900 0000 0000 0000 01mov rsi, input对应bf00 0000 0000 4000 88mov rdx, 5对应bb00 0000 0000 0000 05mov rdi, 1对应c000 0000 0000 0000 01syscall对应0f05
参考汇编程序
; print.asm - written in fasm assembly language format ELF64 executable 3 ; value 3 marks the executable for Linux system ; print mov rax, 1 ; syscall #1 (write) mov rsi, input ; arg1 = msg mov rdx, 5 ; arg2 = msg length mov rdi, 1 ; arg3 = 1 (stdout) syscall ; exit mov rax, 60 ; syscall #60 (exit) mov rdi, 0 ; arg 1 = 0 (OK) syscall ; call exit ; data input db "hello", 0
执行与调试信息
$ ./print Segmentation fault (core dumped) $ gdb ./print (gdb) r Program received signal SIGSEGV, Segmentation fault. 0x0000000000400087 in ?? () (gdb) quit $ readelf -l print Elf file type is EXEC (Executable file) Entry point 0x400078 There is 1 program header, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000008e 0x000000000000008e RWE 0x1000 $ file print print: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
问题分析与排查步骤
核心问题1:机器码与汇编指令完全不匹配
x86-64的立即数mov指令有严格编码规则:
mov rax, imm64正确编码是48 c7 c0 xx xx xx xx(xx为小端存储的立即数),你用的48 b9 00 00 00 00 00 00 00 01实际是mov rcx, 0x100000000,根本没给rax赋值,导致syscall时rax不是write的系统调用号1。mov rsi, imm64正确编码是48 be xx xx xx xx xx xx xx xx,你用的bf开头编码实际是mov rdi, imm64,把input地址写到rdi(本该存stdout的文件描述符1),rsi反而没拿到缓冲区地址,参数完全错位。mov rdi, 1你写的c000 0000 0000 0000 01不是有效指令,CPU执行时会触发非法操作,直接导致段错误。
核心问题2:数据段未被加载到内存
从readelf -l输出看,LOAD段的FileSiz和MemSiz都是0x8e,意味着只有文件前0x8e字节被映射到内存。但你的input字符串在文件偏移0xa0处,不在该范围内,内存中0x4000a0位置是未映射区域,即使寄存器赋值正确,访问该地址也会触发段错误。
排查步骤
验证指令编码正确性:
- 用fasm/nasm编译参考汇编代码,再用
objdump -d反编译,对比正确机器码:fasm print.asm print.o && objdump -d print.o - 对照x86-64指令集手册,逐一确认寄存器对应的操作码(比如rax对应
c0、rsi对应be、rdi对应bf)。
- 用fasm/nasm编译参考汇编代码,再用
修正ELF程序头:
- 调整LOAD段的
p_filesz和p_memsz字段,从0x8e改为至少0xb6(覆盖到字符串末尾),让数据段也被映射到内存。
- 调整LOAD段的
用gdb逐指令分析:
- 在gdb中执行
x/20i 0x400078,查看入口点开始的实际执行指令,对比预期指令,快速定位编码错误:gdb ./print (gdb) x/20i 0x400078
- 在gdb中执行
内容的提问来源于stack exchange,提问作者Charlie_23
相关产品推荐
相关产品推荐

