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

总结

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

相关推荐
nashane1 天前
HarmonyOS 6学习:指南针“文图反向”Bug修复——从“北偏东”变“北偏西”的坐标系纠错
学习·华为·bug·harmonyos
雨季mo浅忆1 天前
记录Vue3项目中的各类问题
前端·bug·vue3
hust_a2 天前
利用AI定位BUG的体验
bug
初圣魔门首席弟子4 天前
bug【已解决】腾讯 WorkBuddy 无法访问:校园网限制导致的网络问题排查全记录
bug
乐兮创想 小林6 天前
企业官网的运维分工模型:内容自助、Bug 终身免费修与服务器托管的边界设计
运维·服务器·bug·网站建设·企业官网·北京网站建设公司
菠萝猫yena6 天前
bug描述规范
bug
乐兮创想 小林6 天前
生物科技官网的工程化设计:产品×应用二维信息架构、多语言与国际化 SEO 实践
运维·服务器·bug·网站建设·企业官网·北京网站建设公司
调问开源问卷DWSurvey7 天前
调问更新5.16~5.30:解锁Excel图片上传,修复多项高频体验Bug
bug
胡图图不糊涂^_^7 天前
测试BUG篇
学习·bug·测试