X86-64 Assembly中printf 打印 float 和 double的bug的解决

前言

笔者最近闲的无聊,学习一些Assembly,其实笔者以前也想学习,但是没有坚持下来

笔者的电脑是Windows x64,那就学习x86-64的Assembly。

笔者学习了打印hello world,然后打印int,后面在打印float和double遇到巨大的问题。

笔者才疏学浅,搞了老半天才明白。

正文

看代码

cpp 复制代码
extern printf
extern ExitProcess
section .data
    fmt_int db "Integer: %d",10,0
    fmt_float db "Float: %f",10,0
    my_int dd 42
    my_float dd 3.141
section .text
global main
main:
    sub rsp,40
    ; ========== 1. 打印整数 int ==========
    lea rcx,[rel fmt_int]
    mov rdx,[rel my_int]
    xor eax,eax
    call printf
    ; ========== 2. 打印 float(需先转 double)  ==========
    lea rcx,[rel fmt_float] ;rcx存放fmt_float的地址
    movss xmm0,[rel my_float] ;取内存里的值到xmm0
    cvtss2sd xmm0,xmm0 ;转换成8字节
    mov eax,1;AL=1,使用1个xmm寄存器
    call printf
    
    xor ecx,ecx
    call ExitProcess

这个assembly代码意思是显然的,首先打印整数,然后打印float。

rcx是第一个Windows x64的第一个参数寄存器,存放整数 / 指针 / 地址。

rdx是第一个Windows x64的第二个参数寄存器。

还有其他参数寄存器,r8,r9。

还有存放浮点数的寄存器,分别是xmm0,xmm1,xmm2,xmm3。


对于printf这个c函数,它是一个可变参数函数。

cpp 复制代码
lea rcx,[rel fmt_int]

它的参数类型和参数的个数都是不确定的。

int

首先先来看看打印int。

cpp 复制代码
printf("%d",a);

需要传入两个参数,因此,需要两个寄存器rcx和rdx。

那么第一个

cpp 复制代码
lea rcx,[rel fmt_int]

lea 是 Load Effective Address(加载有效地址)的缩写。

说白了,将fmt_int的地址存放到rcx。

cpp 复制代码
 mov rdx,[rel my_int]

mov 是 Move(传送)的缩写,作用是把数据从源操作数复制到目标操作数

意思是rdx=42。

cpp 复制代码
xor  eax, eax

eax是rax这个64位寄存器的低32位。

上面的意思就是设置eax=0,本质上其实是设置rax的低8位al为0,即,al=0

al用来告诉变参函数printf:用了多少个 XMM 寄存器传浮点参数。

现在al设置为0,其实告诉printf没有设置浮点参数。


float

对float进行考虑

cpp 复制代码
printf("%f",a)

还是需要传入两个参数。

那个第一个参数的地址存放到rcx里面,没什么问题

cpp 复制代码
 lea  rcx, [rel fmt_float] 

因为,%f 按 64 位读取,需要把32位的float变成64位的/。

先把浮点数放到xmm0中。

cpp 复制代码
movss xmm0,[rel my_float]

然后变成64位。

cpp 复制代码
cvtss2sd xmm0,xmm0

因为使用了一个浮点寄存器,因此需要设置al=1,即

cpp 复制代码
mov eax,1

调用,打印。

代码看起来没什么问题。

笔者使用NASMClang进行汇编和链接,笔者使用的终端是nushell。

Index of /pub/nasm/releasebuilds/3.01/win64https://www.nasm.us/pub/nasm/releasebuilds/3.01/win64/llvm/llvm-project: The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.https://github.com/llvm/llvm-project

笔者创建了一个build.nu文件。如下

nasm -f win64 test.asm -o test.obj
clang test.obj -o test.exe
test

运行,结果如下

cpp 复制代码
❯ nu build.nu
Integer: 42
Float: 0.000000

可以发现打印的int没什么问题,打印的float居然是0,这就非常奇怪了。

笔者在这里搞了许久,最后终于成功了。一些参考

printf打印float错误引起的思考-5kdYeW0-ChinaUnix博客http://blog.chinaunix.net/uid-20773046-id-578320.htmlhttps://stackoverflow.com/questions/78806627/nasm-x64-floating-point-part-incorrectly-printed-as-0-000000https://stackoverflow.com/questions/78806627/nasm-x64-floating-point-part-incorrectly-printed-as-0-000000这其实是windows的调用约定。

当调用 printf 这种可变参数函数时,浮点数必须同时出现在 XMM 寄存器和对应的通用寄存器中

说白了,意思就是说,xmm0中的浮点数需要复制到rdx中,即rdx=xmm0

关键的代码如下

cpp 复制代码
movq rdx,xmm0

全部的代码如下

cpp 复制代码
extern printf
extern ExitProcess
section .data
    fmt_int db "Integer: %d",10,0
    fmt_float db "Float: %f",10,0
    my_int dd 42
    my_float dd 3.141
section .text
global main
main:
    sub rsp,40
    ; ========== 1. 打印整数 int ==========
    lea rcx,[rel fmt_int]
    mov rdx,[rel my_int]
    xor eax,eax
    call printf
    ; ========== 2. 打印 float(需先转 double)  ==========
    lea rcx,[rel fmt_float] ;rcx存放fmt_float的地址
    movss xmm0,[rel my_float] ;取内存里的值到xmm0
    cvtss2sd xmm0,xmm0 ;转换成8字节
    movq rdx,xmm0
    mov eax,1;AL=1,使用1个xmm寄存器

    call printf
    
    xor ecx,ecx
    call ExitProcess

再次运行。

❯ nu build.nu
Integer: 42
Float: 3.141000

没问题。

double

那么对于double还是类似的。如下代码

cpp 复制代码
extern printf
extern ExitProcess
section .data
    fmt_double db "Double: %f",10,0
    my_double dq 2.71828
section .text
global main
main:
    sub rsp,40
    lea rcx,[rel fmt_double]
    movsd xmm0,[rel my_double]
    mov eax,1
    call printf
    
    xor ecx,ecx
    call ExitProcess

设置了两个参数,但是没有进行关键的一步,设置rdx=xmm0。

结果打印显然为0,测试一下。

cpp 复制代码
❯ nu build.nu
Double: 0.000000

没问题,添加之后。

cpp 复制代码
movq rdx,xmm0

结果如下

cpp 复制代码
❯ nu build.nu
Double: 2.718280

总结

至于为什么是这样的,笔者也不知道。

相关推荐
吨吨ai18 小时前
# ChatGPT Plus / Pro + Codex Debug 实战:如何让 AI 从日志、Trace、异常堆栈自动定位线上 Bug
人工智能·chatgpt·bug
灰暗世界%2 天前
飞牛bug修复
bug
凌云拓界4 天前
NodeVerdict | 性能门禁:把追踪数据变成 CI 规则
ci/cd·信息可视化·开源·node.js·bug·数据可视化·安全架构
qingmiaozhuan5 天前
软件测试Bug复现高清录屏标准流程:新手入门与规范指南(2026实战版)
bug·电脑录屏·电脑录屏软件·测试录屏规范·音画同步录制
凌云拓界6 天前
NodeVerdict:Node.js 原生诊断数据可视化工具
信息可视化·架构·typescript·开源·node.js·github·bug
蓝天下的守望者10 天前
验证环境中使用队列的pop_front()思维惯性引发的bug
bug
一只小菜鸡..10 天前
南京大学 操作系统 (JYY) 学习笔记:并发 Bug 的地狱——数据竞争、死锁与原子性违反
笔记·学习·bug
名字还没想好☜11 天前
React 受控输入框光标跳到末尾:格式化输入时的 selection 丢失 bug 与修复
前端·javascript·react.js·bug·react·next.js
陈 洪 伟12 天前
大模型推理引擎vLLM(30):由一个GLM5 bug,整理MLP中的SwiGLU、算子融合、量化相关问题
bug·mlp·vllm
endeavor1213 天前
BUG(记录)
bug