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

NASM宏传递本地标签参数至另一宏触发段错误问题排查

问题分析与修复

你的段错误根源在于错误地将字符串数据嵌入到了代码段(.text)的指令流中,这完全违背了x86-64程序的内存布局规范,具体问题拆解:

1. 数据与指令混放导致的非法指令执行

你在PRINT宏里直接用%%str db %1, 0定义字符串,而宏是被展开在.text段的_start指令序列中的。汇编器会把这些字符串字节直接插入到push指令之后,CPU执行到这里时,会把Hello World!的ASCII码当作机器指令来解析——看你的反汇编结果,开头的65 6c 6c 6f...(也就是"Hello"的ASCII)被解析成了gs ins ins outs等完全无效的指令,CPU执行到这些非法指令时直接触发段错误。

2. 符号地址解析错误

从反汇编里的movabs rsi,0x0能看出来,汇编器根本没正确计算%%str的地址——因为你在代码段的指令中间定义数据,汇编器的地址计算逻辑被打乱,最终把字符串地址解析成了0,而地址0属于内核空间,用户态程序访问直接触发段错误。


修复方案:分离数据与代码

我们需要把字符串数据放到只读数据段(.rodata),同时用宏生成唯一的标签避免重复定义(如果多次调用PRINT的话)。以下是修复后的代码:

%macro PRINT 1
; 生成唯一的本地标签上下文,避免多次调用宏时符号重复
%push print_ctx
%%str_label: db %1, 0
%%str_len equ $ - %%str_label
; Save state
push rax
push rdi
push rsi
push rdx
; Write
_syscall_write %%str_label, %%str_len
; Restore state
pop rdx
pop rsi
pop rdi
pop rax
%pop
%endmacro

%macro _syscall_write 2
mov rax, 1          ; syscall number for write
mov rdi, 1          ; stdout file descriptor
mov rsi, %1         ; pointer to string
mov rdx, %2         ; string length
syscall
%endmacro

global _start
section .rodata  ; 把字符串放到只读数据段,更安全且符合规范
section .data
SYS_EXIT equ 60
EXIT_CODE equ 0
section .text
_start:
PRINT "Hello World!"
exit:
mov rax, SYS_EXIT
mov rdi, EXIT_CODE
syscall

修复要点说明:

  • 使用%push和%pop创建宏的独立上下文,确保多次调用PRINT时,本地标签%%str_label不会出现重复定义的问题。
  • 把字符串定义移到.rodata段(只读数据段,专门存放常量字符串,比.data段更安全),彻底分离代码与数据。
  • 现在汇编器能正确计算字符串的内存地址,syscall的rsi会指向合法的用户态内存区域。

验证反汇编结果

修复后反汇编的_start部分会变成正常的指令序列:

0000000000000000 <_start>:
   0:   50                      push   rax
   1:   57                      push   rdi
   2:   56                      push   rsi
   3:   52                      push   rdx
   4:   b8 01 00 00 00          mov    eax,0x1
   9:   bf 01 00 00 00          mov    edi,0x1
   e:   48 be 00 00 00 00 00    movabs rsi,0x402000  ; 这里会是.rodata里字符串的正确地址
  15:   00 00 00 
  18:   ba 0d 00 00 00          mov    edx,0xd
  1d:   0f 05                   syscall
  1f:   5a                      pop    rdx
  20:   5e                      pop    rsi
  21:   5f                      pop    rdi
  22:   58                      pop    rax

此时syscall的rsi指向合法的字符串地址,rdx是正确的长度,程序能正常打印"Hello World!"并退出。

内容的提问来源于stack exchange,提问作者Jet Blue

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:31:03