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字符串规范。
修复方法
将格式字符串的定义改为以下两种方式之一:
- 手动添加单字节终止符:
sprintf_Int_str db "%lld", 0
- 使用NASM的
asciz伪指令(自动添加\0):
sprintf_Int_str asciz "%lld"
额外注意事项
- 栈对齐要求:x86-64 System V调用约定规定,调用外部函数时
rsp必须保持16字节对齐。你的代码在call malloc后执行push rax,可能破坏栈对齐状态。建议在调用sprintf前检查并修正栈对齐,例如在push rax后添加sub rsp, 8(若此时rsp未对齐),调用完成后再add rsp, 8恢复。 - 参数传递验证:确认
[rbp + 16]确实是传入的整数参数的正确位置。如果Int.str的调用符合x86-64 C调用约定,参数应通过rdi寄存器传递,而非从栈上的rbp+16读取,需根据实际调用逻辑调整参数获取方式。
内容的提问来源于stack exchange,提问作者Joseph Coppin
相关产品推荐
相关产品推荐

