我有以下内容:
foo:
movl $0, %eax //result = 0
cmpq %rsi, %rdi // rdi = x, rsi = y?
jle .L2
.L3:
addq %rdi, %rax //result = result + i?
subq $1, %rdi //decrement?
cmp %rdi, rsi
jl .L3
.L2
rep
ret
我正在尝试将其翻译为:
long foo(long x, long y)
{
long i, result = 0;
for (i= ; ; ){
//??
}
return result;
}
我不知道cmpq%rsi,%rdi是什么意思。 为什么我长时间没有另一个&eax了?
在解决这个问题上,我很乐意提供帮助。我不知道自己想念的是什么-我一直在浏览笔记,教科书和互联网的其余部分,但我陷入了困境。这是一个评论问题,我已经讨论了几个小时。
答案 0 :(得分:2)
假设这是一个带有2个参数的函数。假设这使用的是gcc amd64调用约定,它将在rdi和rsi中传递两个参数。在C函数中,您将它们称为x和y。
long foo(long x /*rdi*/, long y /*rsi*/)
{
//movl $0, %eax
long result = 0; /* rax */
//cmpq %rsi, %rdi
//jle .L2
if (x > y) {
do {
//addq %rdi, %rax
result += x;
//subq $1, %rdi
--x;
//cmp %rdi, rsi
//jl .L3
} while (x > y);
}
return result;
}
答案 1 :(得分:1)
我不知道
cmpq %rsi, %rdi
是什么意思
这是cmp rdi, rsi
的AT&T语法。 https://www.felixcloutier.com/x86/CMP.html
您可以在ISA手册中查找单个指令的详细信息。
更重要的是,像cmp
/ jcc
这样的cmp %rsi,%rdi
/ jl
就像jump if rdi<rsi
。
Assembly - JG/JNLE/JL/JNGE after CMP。如果您详细了解cmp
设置标志的方式以及每个jcc
条件检查使用的标志的详细信息,则可以验证它是正确的,但是使用起来很多 JL的语义含义=跳过小于(假设标志由cmp
设置)以记住它们的作用。
(由于AT&T语法而相反; jcc
谓词对Intel语法具有正确的语义含义。这是我通常偏爱Intel语法的主要原因之一,但是您可以习惯于AT&T语法。)< / p>
通过使用rdi
和rsi
作为输入(在写入前不带/读取它们),它们就是arg传递寄存器。这就是x86-64 System V调用约定,其中整数args在RDI,RSI,RDX,RCX,R8,R9中传递,然后在堆栈中传递。 (What are the calling conventions for UNIX & Linux system calls on i386 and x86-64涵盖了函数调用以及系统调用)。另一个主要的x86-64调用约定是Windows x64,它将传递RCX和RDX中的前两个参数(如果它们都是整数类型)。
是的,x = RDI,y = RSI。是的,结果= RAX。 (写入EAX会零扩展到RAX)。
从代码结构(不将每个C变量存储/重新加载到语句之间的内存中)中,它在启用了一定程度的优化的情况下进行了编译,因此 for()
循环变为带条件的普通asm循环底部的分支。 Why are loops always compiled into "do...while" style (tail jump)? (@ BrianWalker的回答显示,asm循环已音译回C,没有尝试将其转换回惯用的for
循环。)
从循环之前的cmp / jcc中可以看出,编译器无法证明循环运行的迭代次数为非零。因此,无论for()
循环条件是什么,第一次都可能是错误的。 (对于带符号的整数,这并不奇怪。)
由于我们没有看到i
使用单独的寄存器,所以可以得出结论,优化已将i
的另一个var寄存器重新使用 。像for(i=x;
一样,然后在函数的其余部分未使用x
的原始值的情况下,这是“死的”,编译器可以将RDI用作i
,破坏原始值值x
。
我猜想是i=x
而不是y
,因为RDI是在循环内修改的arg寄存器。我们希望C源代码在循环内修改i
和result
,并且大概不会修改它的输入变量x
和y
。先做i=y
然后再做x--
之类的事情是没有道理的,尽管那是另一种有效的反编译方式。
cmp %rdi, %rsi
/ jl .L3
表示要(重新)进入循环的循环条件是rsi-rdi < 0
(有符号)或i<y
。 < / p>
循环之前的cmp / jcc 正在检查相反的情况;请注意,操作数是相反的,并且正在检查jle
,即jng
。因此,这确实是相同的循环条件从循环中剥离并以不同的方式实现。因此,它与C源代码(具有一个条件的普通for()
循环)兼容。
sub $1, %rdi
显然是i--
或--i
。我们可以在for()
内部或循环主体的底部进行操作。最简单,最惯用的说法是在for(;;)
语句的第三部分。
addq %rdi, %rax
显然将i
添加到result
中。我们已经知道此功能中的RDI和RAX。
将各个部分放在一起,我们得出:
long foo(long x, long y)
{
long i, result = 0;
for (i= x ; i>y ; i-- ){
result += i;
}
return result;
}
在.L3:
标签名称中,这看起来像是gcc
的输出。 (以某种方式损坏了,请从:
中删除.L2
,更重要的是,在一个cmp中从%
中删除%rsi
。请确保将代码复制/粘贴到SO问题中为了避免这种情况。)
因此,使用正确的gcc版本/选项可能可以为某些C输入完全获取该asm。可能是gcc -O1
,因为movl $0, %eax
排除了-O2
及更高版本(GCC将寻找xor %eax,%eax
窥孔优化以有效地将寄存器归零)。但这不是-O0
,因为那样会将循环计数器存储/重新加载到内存中。 -Og
(为调试进行了优化)喜欢对循环条件使用jmp
而不是单独的cmp/jcc
来跳过循环。对于只反编译为相同功能的C,这种详细程度基本上无关紧要。
rep ret
是gcc的另一个标志;由于AMD K8 / K10分支预测,gcc7和更早的版本在tune=generic
的默认ret
输出中使用了此参数,该输出作为分支目标或从jcc
跌落而到达。 What does `rep ret` mean?
gcc8及更高版本仍将与-mtune=k8
或-mtune=barcelona
一起使用。但是我们可以排除掉,因为该调整选项将使用dec %rdi
而不是subq $1, %rdi
。 (对于寄存器操作数,只有少数现代CPU的inc/dec
保留CF不变,而没有任何问题。INC instruction vs ADD 1: Does it matter?)
gcc4.8,随后将rep ret
放在同一行上。 gcc4.7和更早版本按照您所显示的进行打印,并在前一行加上rep
前缀。
gcc4.7和更高版本喜欢将初始分支放在之前mov $0, %eax
,这看起来像是缺少的优化。这意味着他们需要函数之外的单独return 0
路径,其中包含另一个mov $0, %eax
。
gcc4.6.4 -O1
精确地再现了您的输出 ,on the Godbolt compiler explorer
# compiled with gcc4.6.4 -O1 -fverbose-asm
foo:
movl $0, %eax #, result
cmpq %rsi, %rdi # y, x
jle .L2 #,
.L3:
addq %rdi, %rax # i, result
subq $1, %rdi #, i
cmpq %rdi, %rsi # i, y
jl .L3 #,
.L2:
rep
ret
使用i=y
的其他版本也是如此。当然,我们可以添加许多可以优化的东西,例如i=y+1
,然后具有x>--i
这样的循环条件。 (签名溢出是C语言中未定义的行为,因此编译器可以假定它没有发生。)
// also the same asm output, using i=y but modifying x in the loop.
long foo2(long x, long y) {
long i, result = 0;
for (i= y ; x>i ; x-- ){
result += x;
}
return result;
}
mov $0
而不是xor-零,以及从标签名开始),看起来像是gcc -O1
的输出,因此我放入了该命令行选项并选择了一个老式的像gcc6一样的gcc版本。 (事实证明,这个asm实际上是来自更老的gcc)。我尝试了基于cmp / jcc和x<y
的初步猜测,例如i++
和i++
(在我实际上认真阅读过其余所有asm之前 ),因为for循环经常使用result += x
。琐碎的死循环asm输出向我表明,显然是错误的:P
我猜想i = x,但是在选择i--
但使用i
的版本转错了方向之后,我意识到i
令人分心,起初通过根本不使用x--
来简化。我刚使用result += x
时就将其反转,因为显然RDI = x。 (我非常了解x86-64 System V的调用约定,可以立即看到它。)
在查看了循环主体之后,从x--
和add
指令中可以清楚地看到sub
和cmp/jl
。
something < something
显然是一个x<y
循环条件,涉及2个输入变量。
我不确定是y<x
还是jne
,并且较新的gcc版本使用x > y
作为循环条件。我认为在这一点上,我作弊并查看了Brian的答案以确认它确实是x--
,而不是花一分钟时间来研究实际的逻辑。 但是一旦我发现它是x>y
,只有x
才有意义。如果完全进入循环,则另一个直到绕回才成立,但是有符号溢出是C中未定义的行为。
然后我查看了一些较旧的gcc版本,看是否有使asm更像问题中的内容的东西。
然后我返回并在循环内将i
替换为{{1}}。
如果这看起来像是偶然的事情,那是因为这个循环是如此之小,以至于我不希望发现它会遇到任何麻烦,而我对寻找准确复制它的source + gcc版本更感兴趣,而不是完全将其反转的原始问题。
(我并不是说初学者应该这么容易,我只是在记录我的思考过程,以防万一有人好奇。)