为啥我要添加 rax 和 rdx?
Posted
技术标签:
【中文标题】为啥我要添加 rax 和 rdx?【英文标题】:Why am i adding rax and rdx?为什么我要添加 rax 和 rdx? 【发布时间】:2018-09-26 02:14:20 【问题描述】:这是一个将字符串作为输入传递的程序。
我对下面显示的汇编代码感到困惑,特别是第 6 行。 这是我从研究中了解到的:
rbp-48
是指向存储argv
的堆栈地址的指针。 (argv
本身,就是指向argv
数组开头的地址)
现在 rax 寄存器存储 argv
数组地址。
然后我们将 8 个字节添加到 rax。这意味着 rax 现在指向argv[1]
的地址。 (我知道argv[1]
中存储了另一个地址,指向一个字符串)。
然后我们访问存储在 argv[1] 中的值并将其存储在 rdx 寄存器中。这意味着,rdx 现在指向字符串开始的地址。
然后我们将 [rbp-24] = i 计数器变量移动到 eax 寄存器。
然后我们有一个行动 cdqe,我认为它不相关。
现在我是否感到困惑:如果我想访问argv[1]
中的第一个字符并将其存储在 eax 寄存器中,我希望汇编程序执行以下操作:
mov eax, BYTE PTR [rdx]
如果我需要访问存储在 argv[1] 中的第二个字符并将其存储在 eax 寄存器中,我希望汇编器执行以下操作:
mov eax, BYTE PTR [rdx+1]
但相反,我看到编译器执行以下操作:
add rax, rdx
将内存中字符串开始的地址添加到内存中存储指向字符串开头的地址的地址,并将此结果保存在 rax 中。
我无法理解这条指令如何让 rax 指向 argv[1] 中的任何字符。
下面是循环指令对应的C代码和汇编代码:
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
int sum = 0;
for(int i = 0; i < strlen(argv[1]); i ++)
sum += (int)argv[1][i];
return 0;
组装
mov rax, QWORD PTR [rbp-48]
add rax, 8
mov rdx, QWORD PTR [rax]
mov eax, DWORD PTR [rbp-24]
cdqe
add rax, rdx
movzx eax, BYTE PTR [rax]
movsx eax, al
add DWORD PTR [rbp-20], eax
add DWORD PTR [rbp-24], 1
【问题讨论】:
assembly 也必须与架构一起使用。并且汇编器不会给你add rax, rdx
,它是发出该指令的编译器。汇编器只是将人类可读的指令组装成机器代码
你不明白如何将 i 添加到 argv[1] 得到 &argv[1][i]?
我不明白这种混淆。你知道rdx+1是argv[1][1]的地址,你知道rax包含i,所以应该清楚rdx+rax就是argv[1][i]的地址。下一条语句movszx eax, byte ptr [rax]
加载该地址处的字符。
编译时启用优化以获得更易于理解的代码。就像gcc -Og
将变量保存在寄存器中并可能进行轻微优化,或者gcc -O3 -fno-tree-vectorize
或gcc -Os
。见How to remove "noise" from GCC/clang assembly output?。此代码不将1
添加到指针,而是将其添加到双字i
,并使用i
作为索引。奇怪的是您的编译器没有使用像movzx eax, byte ptr [rax+rdx]
这样的索引寻址模式。这是来自 MSVC 的吗?看起来不像反优化的gcc -O0
会做什么。
代码不完整,应该至少有一条jmp
-family指令,大概2条。
【参考方案1】:
哦,我终于明白你的困惑了。在有问题的指令处,rax 不再包含 argv;它被重新加载了 i 的值。编译器正在使用add
指令而不是索引寻址模式。
eax 是 rax 的低 32 位。加载 eax 时,该值被零扩展为 64 位。
然后cdqe
将 EAX 符号扩展为 RAX,因为 i
是一个带符号的 32 位整数,用于索引指针。编译器可以通过加载movsx rax, dword ptr [rbp-24]
来简化。
【讨论】:
非常感谢! :)以上是关于为啥我要添加 rax 和 rdx?的主要内容,如果未能解决你的问题,请参考以下文章
关于间接寻址,如 %segreg:disp(base,index,scale),foo