You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

循环显示栈值触发段错误,求解栈对齐需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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:52:33