Windows x64 NASM汇编:基于RBP寄存器访问函数8字节参数的问题排查
嘿,我来帮你排查这个问题!你的代码里有几个关键错误,都是因为没吃透Windows x64的调用约定和栈帧结构导致的,咱们一步步来梳理:
首先得明确**Windows x64调用约定(Microsoft x64 Calling Convention)**的核心规则:前4个整数/指针参数是通过寄存器传递的,顺序是RCX、RDX、R8、R9,只有第5个及以后的参数才会被压到栈上。你的函数只接受一个参数,所以它根本不在栈上,而是直接存在RCX寄存器里!
接下来看看你代码里的具体问题:
参数位置完全错了
你写的mov r8, [rbp + 8]是在读取函数的返回地址,不是参数!栈帧的布局是这样的:当C代码调用_mypopcnt时,call指令会先把返回地址压入栈,然后进入汇编函数;你执行push rbp又把旧的RBP值压栈,之后mov rbp, rsp让RBP指向这个旧RBP值的位置。此时[rbp + 8]指向的就是call指令压入的返回地址,完全不是你要的函数参数。寄存器保存/恢复不匹配
你代码里push r8但后面用pop rbx,这会直接破坏RBX寄存器的值——RBX是非易失性寄存器(调用者期望它的值在函数返回后保持不变),而R8是易失性寄存器(不需要被调用者保存)。这里要么完全不需要保存R8,要么要保存的话也应该是push r8对应pop r8,而不是乱弹到RBX里。冗余的栈操作
其实你的函数逻辑很简单,甚至可以省略建立栈帧的步骤(push rbp/mov rbp, rsp),不过保留也没问题,只要保证栈平衡就行。
修正后的汇编代码
下面是修复后的版本,直接利用RCX寄存器获取参数,并且修正了寄存器操作:
global _mypopcnt section .text _mypopcnt: ; 可选:建立栈帧(如果需要访问栈上内容才需要,这里其实可以省略) push rbp mov rbp, rsp ; 第一个参数在RCX里,直接执行popcnt,结果存入RAX(Windows x64规定整数返回值存在RAX) popcnt rax, rcx ; 恢复栈帧 mov rsp, rbp pop rbp ret
如果想要更精简的版本(不需要栈帧),可以写成这样:
global _mypopcnt section .text _mypopcnt: popcnt rax, rcx ret
为什么之前的结果不对?
因为你读取的[rbp + 8]是随机的返回地址,popcnt计算的是这个地址的二进制位数量,自然不是你期望的32。修正后直接用RCX里的参数值,就能得到正确的结果了。
另外你的C代码和Makefile都是没问题的,函数声明里返回值用int也OK,因为popcnt的结果32在int范围内,编译器会自动把RAX的低32位截断到EAX作为返回值。
备注:内容来源于stack exchange,提问作者coderodde

