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

Ubuntu环境下汇编调用sprintf偶发段错误问题排查

汇编调用sprintf时的偶发段错误问题

我尝试用NASM汇编调用sprintf将整数转为字符串,本地Ubuntu 22.10环境运行正常,但在GitHub Actions的Ubuntu虚拟机、本地Ubuntu Docker容器中触发段错误,Alpine、Debian、Arch容器则运行正常。本地GLIBC版本为2.36,故障环境为2.35,暂无法确认是否为版本差异导致。

我的汇编实现

sprintf_Int_str db "%lld",
                   dq 0
...

Int.str:
    push rbp
    mov rbp, rsp  ; creates stack frame

    ; allocate string
    mov rdi, 64   ; much larger than needed
    call malloc

    push rax ; save char*


    ; write string to allocated memory using `sprintf(buf, "%lld", n)`
    mov rdi, rax
    mov rsi, sprintf_Int_str
    mov rdx, qword [rbp + 16]     ; the location of the number on the stack 
                                  ; (trust me - it's pushed before Int.str is called,
                                  ; meaning it's 2 (64-bit) places before the start 
                                  ; of the stack frame)
    mov rcx, rsi

    mov r8, 0
    mov r9, 0    ; clearing other registers for sake of it
    mov rax, 0   ; (doesn't make a difference if I do this or not)

    call sprintf

    pop rax      ; pop return value which is saved after call to malloc


    mov rsp, rbp
    pop rbp
    ret

对比GCC生成的C++汇编实现

C++源码

char* Int_str(int i)
{
    char* str = malloc(64);
    sprintf(str, "%d", i);
    return str;
}

GCC生成的汇编

.LC0:
   .string    "%d"
   .text
   .globl Int_str
   .type  Int_str, @function
Int_str:
   pushq  %rbp
   movq   %rsp, %rbp

   subq   $32, %rsp
   movl   %edi, -20(%rbp)
   movl   $64, %edi
   call   malloc@PLT

   movq   %rax, %rdi
   movl   -20(%rbp), %edx
   leaq   .LC0(%rip), %rcx
   movq   %rcx, %rsi
   movl   $0, %eax
   call   sprintf@PLT

   movq   -8(%rbp), %rax

   leave
   ret

编译链接命令

汇编命令

nasm -f elf64 <file.asm> -o out.o

链接命令

gcc -Wall -no-pie out.o -e main -o a.out

Ubuntu Dockerfile

FROM ubuntu:latest

WORKDIR /app

COPY . .

SHELL ["/bin/bash", "-c"]

RUN apt-get update && apt-get install -y nasm curl build-essential bc

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

CMD ["/bin/bash", "bin/test"]

问题原因与解决方法

核心问题:格式字符串未正确终止

你定义的sprintf_Int_str使用db "%lld", dq 0,这里存在两个关键问题:

  • db "%lld"仅存储字符串"%lld"的ASCII字节,未自动添加C风格字符串必需的单字节终止符\0
  • 后续的dq 0是在字符串后写入8字节的0,而非紧跟在"%lld"末尾的单字节\0。sprintf会从sprintf_Int_str开始读取内存,直到找到第一个\0,这种未定义行为会因不同环境的内存布局差异,导致偶发段错误。

而GCC生成的.LC0: .string "%d"中,.string伪指令会自动在字符串末尾添加单字节\0,完全符合C字符串规范。

修复方法

将格式字符串的定义改为以下两种方式之一:

  1. 手动添加单字节终止符:
sprintf_Int_str db "%lld", 0
  1. 使用NASM的asciz伪指令(自动添加\0):
sprintf_Int_str asciz "%lld"

额外注意事项

  1. 栈对齐要求:x86-64 System V调用约定规定,调用外部函数时rsp必须保持16字节对齐。你的代码在call malloc后执行push rax,可能破坏栈对齐状态。建议在调用sprintf前检查并修正栈对齐,例如在push rax后添加sub rsp, 8(若此时rsp未对齐),调用完成后再add rsp, 8恢复。
  2. 参数传递验证:确认[rbp + 16]确实是传入的整数参数的正确位置。如果Int.str的调用符合x86-64 C调用约定,参数应通过rdi寄存器传递,而非从栈上的rbp+16读取,需根据实际调用逻辑调整参数获取方式。

内容的提问来源于stack exchange,提问作者Joseph Coppin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 15:40:48