循环显示栈值触发段错误,求解栈对齐需120字节的原因
问题
尝试在循环中打印栈上的十六进制值,仅执行2次迭代后,最后一次printf调用触发Segmentation fault(段错误)。曾尝试用sub rsp, 8对齐栈但无效,后来发现扩展栈120字节能解决对齐问题,对此感到疑惑:循环前后只有3次CALL和3次PUSH操作,每次占8字节栈空间,为何需要120字节?
代码
global _start extern printf section .data ;format db "value at esp: %llx", 10, 0x00 format db "%012llX", 10, 0x00 section .text _start: mov rax, 0xa284ee5c7cde4bd7 push rax mov rax, 0x935add110510849a push rax mov rax, 0x10b29a9dab697500 push rax mov rax, 0x200ce3eb0d96459a push rax mov rax, 0xe64c30e305108462 push rax mov rax, 0x69cd355c7c3e0c51 push rax mov rax, 0x65659a2584a185d6 push rax mov rax, 0x69ff00506c6c5000 push rax mov rax, 0x3127e434aa505681 push rax mov rax, 0x6af2a5571e69ff48 push rax mov rax, 0x6d179aaff20709e6 push rax mov rax, 0x9ae3f152315bf1c9 push rax mov rax, 0x373ab4bb0900179a push rax mov rax, 0x69751244059aa2a3 push rax ;--- mov rbx, 0x2144d2144d2144d2 ;!! ;--- xor rcx, rcx ; initialize rcx to 0 mov rcx, 2 ; initialize rcx to 14 loops mov r15, rsp ; initialize the pointer to rsp right after pushing all of RAX call Loop call Exit ; Exit the program Loop: xor rax, rax mov r14, [r15] ;have rax as temp holder of valu at rdx not to overwrite ;xor r14, rbx ;rdx while xoring at the next line push rcx push r14 push r15 call printMessage pop r15 pop r14 pop rcx add r15, 8 ;move up the stack loop Loop printMessage: mov rdi, format ; set 1st argument (Print Format) mov rsi, r14 ; set 2nd argument (message) call printf ; printf(outFormat, message) ret Exit: mov rax, 60 mov rdi, 0 syscall
GDB调试信息
gef➤ r Starting program: /home/kali/Desktop/assembly/exercise 1/Final/final1 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 69751244059AA2A3 373AB4BB0900179A Program received signal SIGSEGV, Segmentation fault. **0x00007ffff7e1db44 in __printf (format=0x403008 "%012llX\n") at ./stdio-common/printf.c:28 28 ./stdio-common/printf.c: No such file or directory.** [ Legend: Modified register | Code | Heap | Stack | String ] ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ──── $rax : 0x11 $rbx : 0x2144d2144d2144d2 $rcx : 0x0 $rdx : 0x0 $rsp : 0x00007fffffffdd48 → 0x0000000000000000 $rbp : 0x0 $rsi : 0x373ab4bb0900179a $rdi : 0x0000000000403008 → "%012llX\n" $rip : 0x00007ffff7e1db44 → <printf+36> movaps XMMWORD PTR [rsp+0x50], xmm0 $r8 : 0x58 $r9 : 0x0 $r10 : 0x0 $r11 : 0x202 $r12 : 0x0000000000401020 → <_start+0> movabs rax, 0xa284ee5c7cde4bd7 $r13 : 0x00007fffffffdea0 → 0x0000000000000001 $r14 : 0x373ab4bb0900179a $r15 : 0x00007fffffffde40 → 0x9ae3f152315bf1c9 $eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification] $cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ──── 0x00007fffffffdd48│+0x0000: 0x0000000000000000 ← $rsp 0x00007fffffffdd50│+0x0008: 0x373ab4bb0900179a 0x00007fffffffdd58│+0x0010: 0x0000000000000000 0x00007fffffffdd60│+0x0018: 0x0000000000000001 0x00007fffffffdd68│+0x0020: 0x0000000000000400 0x00007fffffffdd70│+0x0028: 0x373ab4bb0900179a 0x00007fffffffdd78│+0x0030: 0x0000000000000000 0x00007fffffffdd80│+0x0038: 0x0000000000000000 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ──── 0x7ffff7e1db3b <printf+27> mov QWORD PTR [rsp+0x48], r9 0x7ffff7e1db40 <printf+32> test al, al 0x7ffff7e1db42 <printf+34> je 0x7ffff7e1db7b <__printf+91> → 0x7ffff7e1db44 <printf+36> movaps XMMWORD PTR [rsp+0x50], xmm0 0x7ffff7e1db49 <printf+41> movaps XMMWORD PTR [rsp+0x60], xmm1 0x7ffff7e1db4e <printf+46> movaps XMMWORD PTR [rsp+0x70], xmm2 0x7ffff7e1db53 <printf+51> movaps XMMWORD PTR [rsp+0x80], xmm3 0x7ffff7e1db5b <printf+59> movaps XMMWORD PTR [rsp+0x90], xmm4 0x7ffff7e1db63 <printf+67> movaps XMMWORD PTR [rsp+0xa0], xmm5 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "final1", stopped 0x7ffff7e1db44 in __printf (), reason: SIGSEGV ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ──── [#0] 0x7ffff7e1db44 → __printf(format=0x403008 "%012llX\n") [#1] 0x401106 → printMessage() [#2] 0x4010d4 → _start() ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── gef➤ gef➤ si Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists.
解答
1. 段错误的直接原因:栈未16字节对齐
从GDB输出能看到,段错误发生在printf内部的movaps指令——这个指令要求内存操作数必须是16字节对齐的。当调用printf时,rsp的值是0x7fffffffdd48,地址低4位是8,不是16的倍数,导致movaps访问未对齐内存,触发段错误。
2. 为什么sub rsp,8无效?
算一下调用printf前的栈对齐状态:
- 程序启动时,
_start执行前rsp是16字节对齐的(系统保证)。 _start中push了14个8字节值,总大小14*8=112字节(16的倍数),push后rsp仍对齐。- 调用
Loop时,call指令push8字节返回地址,rsp变为原rsp-8,失去对齐。 - 进入
Loop后push三个8字节寄存器(共24字节),此时rsp=原rsp-8-24=原rsp-32(32是16的倍数,暂时对齐)。 - 调用
printMessage时,call指令再push8字节返回地址,rsp=原rsp-32-8=原rsp-40,40 mod16=8,此时rsp未对齐,调用printf直接触发错误。
你尝试的sub rsp,8如果加在错误位置,反而会打乱对齐状态,无法覆盖调用printf前的关键对齐需求。
3. 为什么扩展栈120字节能解决问题?
120字节的本质是调整了栈基地址:
- 120 mod16=8,给初始对齐的
rsp减8后,后续经过call Loop(-8)、push三个寄存器(-24)、call printMessage(-8),总偏移为8+8+24+8=48(48是16的倍数),刚好让调用printf时的rsp回到16字节对齐状态。
正确的修复方式
不用盲目加120字节,直接在调用printf前确保栈对齐:
修改printMessage函数,在调用printf前调整栈到16字节边界,调用后恢复:
printMessage: sub rsp, 8 ; 对齐栈到16字节边界 mov rdi, format ; 设置第一个参数 mov rsi, r14 ; 设置第二个参数 call printf ; 调用printf add rsp, 8 ; 恢复栈 ret
更规范的做法是遵循System V AMD64 ABI,预留16字节的shadow space:
printMessage: sub rsp, 16 ; 对齐栈+预留shadow space mov rdi, format mov rsi, r14 call printf add rsp, 16 ret
内容的提问来源于stack exchange,提问作者T-series
相关产品推荐
相关产品推荐

