0x0000000000400553 <main+59>: mov -0x4(%rbp),%eax
0x0000000000400556 <main+62>: cltq
0x0000000000400558 <main+64>: shl $0x3,%rax
0x000000000040055c <main+68>: mov %rax,%rdx
实际上我的程序很简单:
5 int main(int argc, char *argv[]) {
6 int i = 0;
7 while(environ[i]) {
8 printf("%s\n", environ[i++]);
9 }
10 return 0;
但是程序集输出相当长:
Dump of assembler code for function main:
0x0000000000400518 <main+0>: push %rbp
0x0000000000400519 <main+1>: mov %rsp,%rbp
0x000000000040051c <main+4>: sub $0x20,%rsp
0x0000000000400520 <main+8>: mov %edi,-0x14(%rbp)
0x0000000000400523 <main+11>: mov %rsi,-0x20(%rbp)
0x0000000000400527 <main+15>: movl $0x0,-0x4(%rbp)
0x000000000040052e <main+22>: jmp 0x400553 <main+59>
0x0000000000400530 <main+24>: mov -0x4(%rbp),%eax
0x0000000000400533 <main+27>: cltq
0x0000000000400535 <main+29>: shl $0x3,%rax
0x0000000000400539 <main+33>: mov %rax,%rdx
0x000000000040053c <main+36>: mov 0x2003e5(%rip),%rax # 0x600928 <environ@@GLIBC_2.2.5>
0x0000000000400543 <main+43>: lea (%rdx,%rax,1),%rax
0x0000000000400547 <main+47>: mov (%rax),%rdi
0x000000000040054a <main+50>: addl $0x1,-0x4(%rbp)
0x000000000040054e <main+54>: callq 0x400418 <puts@plt>
0x0000000000400553 <main+59>: mov -0x4(%rbp),%eax
0x0000000000400556 <main+62>: cltq
0x0000000000400558 <main+64>: shl $0x3,%rax
0x000000000040055c <main+68>: mov %rax,%rdx
0x000000000040055f <main+71>: mov 0x2003c2(%rip),%rax # 0x600928 <environ@@GLIBC_2.2.5>
0x0000000000400566 <main+78>: lea (%rdx,%rax,1),%rax
0x000000000040056a <main+82>: mov (%rax),%rax
0x000000000040056d <main+85>: test %rax,%rax
0x0000000000400570 <main+88>: jne 0x400530 <main+24>
0x0000000000400572 <main+90>: mov $0x0,%eax
0x0000000000400577 <main+95>: leaveq
0x0000000000400578 <main+96>: retq
End of assembler dump.
我不明白的是这个街区:
0x000000000040052e <main+22>: jmp 0x400553 <main+59>
0x0000000000400530 <main+24>: mov -0x4(%rbp),%eax
0x0000000000400533 <main+27>: cltq
0x0000000000400535 <main+29>: shl $0x3,%rax
0x0000000000400539 <main+33>: mov %rax,%rdx
0x000000000040053c <main+36>: mov 0x2003e5(%rip),%rax # 0x600928 <environ@@GLIBC_2.2.5>
0x0000000000400543 <main+43>: lea (%rdx,%rax,1),%rax
0x0000000000400547 <main+47>: mov (%rax),%rdi
0x000000000040054a <main+50>: addl $0x1,-0x4(%rbp)
0x000000000040054e <main+54>: callq 0x400418 <puts@plt>
0x0000000000400553 <main+59>: mov -0x4(%rbp),%eax
0x0000000000400556 <main+62>: cltq
0x0000000000400558 <main+64>: shl $0x3,%rax
0x000000000040055c <main+68>: mov %rax,%rdx
0x000000000040055f <main+71>: mov 0x2003c2(%rip),%rax # 0x600928 <environ@@GLIBC_2.2.5>
0x0000000000400566 <main+78>: lea (%rdx,%rax,1),%rax
0x000000000040056a <main+82>: mov (%rax),%rax
0x000000000040056d <main+85>: test %rax,%rax
0x0000000000400570 <main+88>: jne 0x400530 <main+24>
4条答案
按热度按时间voj3qocg1#
cltq
是英特尔cdqe
的gas
助记符,如以下网址所示:https://sourceware.org/binutils/docs/as/i386_002dMnemonics.html助记符包括:
cltq
):AT & T风格cdqe
): Intel术语:
这是GAS名称与Intel版本非常不同的少数指令之一。
as
接受两种助记符中的任何一种,但像NASM这样的Intel语法汇编器可能只接受Intel名称。它将4字节符号扩展为8字节,这在2的补码中意味着:
在C中,这通常表示从有符号
int
到long
的强制转换。示例:
此指令仅适用于64位。
还应考虑以下说明:
CWDE
(美国电话电报公司CWTL
),CBW
(美国电话电报公司CBTW
):CDQE
的较小版本,也存在于32位CQO
系列,哪个符号将RAX
扩展到RDX:RAX
MOVSX
系列,符号扩展和移动:what does movsbl instruction do?在GitHub上使用Assert的最小可运行示例:
CWDE
和CWTL
CDQE
和CLTQ
GCC 4.9.3规定:
编译和反汇编:
包含:
其行为为:
xt0899hw2#
cltq提升一个int到一个int 64. shl 3,%rax做一个偏移到一个64-bit指针(乘任何是在rax由8).什么代码正在做是循环通过一个指针列表到环境变量.当它发现一个值为零,那是这结束,并且它退出这循环.
下面是Linux如何将环境变量存储在RAM中堆栈之上的一个视图。您将看到指针从0xbffff 75 c开始;其指向0xbffff 893,“TERM=rxvt”。
显然,你的编译器足够聪明,可以将简单格式的
printf
优化为puts
。环境字符串的获取和i的后增量就在代码中。如果你自己不解决其中的一些问题,你永远不会真正理解它。只要“成为”计算机,使用我用gdb为你转储的数据,逐步完成循环。你就会明白了。v1uwarro3#
cltq
是CDQE的AT & T助记符,它将EAX符号扩展为RAX。它是movslq %eax, %rax
的缩写形式,节省代码字节。它的存在是因为x86 - 64如何从8086到386再到AMD64。它将EAX的符号位复制到更宽寄存器的所有高位,因为2的补码就是这样工作的。助记符是Convert Long to Quad的缩写。
AT & T语法(GNU
as
/objdump
使用)对某些指令使用与Intel不同的助记符(请参阅official docs)。您可以使用objdump -drwC -Mintel
或gcc -masm=intel -S
获取Intel语法,使用Intel和AMD在其指令参考手册中记录的助记符(请参阅x86标记wiki中的链接)。(有趣的事实:作为输入,gas在任一模式中接受任一助记符)。Intel insn ref manual entry for these 3 insns.
显然,
cltq
/cdqe
仅在64位模式下可用,但其它两个在所有模式下都可用。movsx
和movzx
仅在386中引入,这使得对除al
/ax
之外的寄存器进行符号/零扩展,或者在加载时动态地进行符号/零扩展变得容易/高效。可以把
cltq
/cdqe
看作movslq %eax,%rax
的一个特例,它运行起来一样快,但唯一的好处是节省了几个字节的代码,所以不值得牺牲其他东西来代替movsxd
/movzx
。eax
符号扩展到edx:eax
**在idiv
之前,或者仅仅在返回一对寄存器中的宽整数之前,是有用的。它们没有等效的单指令,但可以用两条指令来执行:
例如
mov %eax, %edx
/sar $31, %edx
英特尔用于在
rax
内扩展的助记符都以e
结尾,除了原来的8086cbw
。您可以记住这种情况,因为即使是8086也可以在单个寄存器中处理16位整数。因此不需要将dl
设置为al
的符号位。div r8
和idiv r8
从ax
读取被除数,而不是从dl:al
。所以cbw
将al
符号扩展为ax
。AT & T的助记符没有明显的提示来帮助你记住哪个是哪个。一些写
*dx
的助记符以d
结尾(对于dx?),而不是通常的l
结尾。cqto
打破了这种模式,但一个八位字是128b,因此必须是rdx:rax
的串联。英特尔的助记符更容易记住,英特尔的语法也更容易阅读。(我先学了AT & T的语法,但后来习惯了英特尔,因为阅读英特尔/AMD的手册很有用!)
注意,**表示零扩展,
mov %edi,%edi
**将%edi
零扩展为%rdi
,因为any write to a 32-bit register zeros the upper 32 bits。(In实践中,尝试将
mov
扩展到另一个寄存器(例如mov %eax, %ecx
),因为same,same
defeats mov-elimination in Intel CPUs。您经常会看到编译器为具有32位无符号参数的函数生成的asm使用mov
进行零扩展,不幸的是,它通常与src和destination使用相同的寄存器。)从8或16到32(隐式为64),
and $0xff, %eax
可以工作,但效率低于movzbl %al, %eax
。$0xff
不适合8位 * 符号扩展 * 立即数,因此需要完整的4字节0x000000ff
立即数。(或者更好,movzbl %al, %ecx
,因此移动消除可以使其在Intel CPU上的延迟为零,其中移动消除适用于movzx
8-〉32。)z9ju0rcb4#
如果你的操作系统是64位,如果你没有声明一个函数驻留在另一个文件中,但你想在这个文件中使用它。GCC会默认认为这个函数是32位的。所以cltq只会使用RAX的低32位(返回值),高32位将填充1或0。希望这个网站能帮助你http://www.mystone7.com/2012/05/23/cltq/