2017-04-01 92 views
1
  1. 0x00000000004005c7 <+28>: movw $0x0,0x8(%rsp)是否在字符串的末尾添加空字符?空字符汇编代码

  2. 有人也可以解释前4行吗?

    0x00000000004005ab <+0>:  sub $0x28,%rsp 
    0x00000000004005af <+4>:  mov %fs:0x28,%rax 
    0x00000000004005b8 <+13>: mov %rax,0x18(%rsp) 
    0x00000000004005bd <+18>: xor %eax,%eax 
    0x00000000004005bf <+20>: movq $0x64636261,(%rsp) 
    0x00000000004005c7 <+28>: movw $0x0,0x8(%rsp) 
    => 0x00000000004005ce <+35>: mov %rsp,%rdi 
    0x00000000004005d1 <+38>: callq 0x40059d <func> 
    0x00000000004005d6 <+43>: mov $0x0,%eax 
    0x00000000004005db <+48>: mov 0x18(%rsp),%rdx 
    0x00000000004005e0 <+53>: xor %fs:0x28,%rdx 
    0x00000000004005e9 <+62>: je  0x4005f0 <main+69> 
    0x00000000004005eb <+64>: callq 0x400480   <[email protected]> 
    0x00000000004005f0 <+69>: add $0x28,%rsp 
    0x00000000004005f4 <+73>: retq 
    

的C代码:

#include <stdio.h> 

void func(char s[]) 
{ 
    printf("%s\n", s); 
} 

int main() 
{ 
    char s[10] = "abcd"; 

    func(s); 

    return 0; 
} 

感谢。


OS:

  • Linux版本的17年9月4日-C9(gcc版本4.9.2(Debian的4.9.2-10))

CPU:

  • vendor_id:GenuineIntel

  • CPU家族:6

  • 模型:63

  • 模型名称:Intel(R)至强(R)CPU @ 2.30GHz

回答

4

是的,这增加了NUL字符到字符串的结尾。实际上,它是零填充整个字符数组---阅读更多细节。

从阅读该指令可以明显看出,它在内存中的某处存储了0,尽管您不能说它实际上是将它放在了字符串的末尾。

movw $0x0,0x8(%rsp) 

你可以在这里看到,这个指令做了W¯¯ ORD MOV即具体而言,它将立即数0($0x0)移至内存位置0x8(%rsp),该位置与rsp寄存器中的地址偏移8字节。

如果您扩展您在其中检查代码的上下文,情况会变得更加清晰。考虑前面的指令:

movq $0x64636261,(%rsp) 

这确实一个q UAD-字MOV E中的直接价值0x64636261存储在rsp寄存器中的存储位置。那当然值,当然是字符串"abcd"

现在,一个字符是一个字节,0x64636261是4个字节,就像字符串"abcd"。为什么在这个世界上要完成一个8字节的移动?那么,因为编译器正在利用隐式的零扩展行为。当它使用具有双字立即数的四字移动指令时,双字立即数被​​隐式地零扩展为四字。所以你实际上在做的是将0x0000000064636261移动到(%rsp)

单词移动指令也是零扩展的:一个字节的立即数值被隐式地零扩展为一个完整的单词,然后单词0x0000被移到内存中,位于0x8(%rsp)

总的来说,我们已经将10个字节移动到内存中:来自四字移动的8个字节和来自移动字的2个字节。这个数字10应该看起来很熟悉 - 它是您在C代码中声明的s数组的大小!

有,指出了C语言的基本规则:

“如果有&hellip;较少的字符在字符串文字时使用比有元件在初始化已知大小的阵列数组,[数组]的其余部分应隐式地初始化为具有静态存储持续时间的对象。“

(C99 $ 6.7.8/21

这有效地意味着,在阵列的其余填充有0。

该数组的前4个字节用字符串"abcd"填充,然后接下来的6个字节用0填充。汇编代码只是尽可能地将商店分成多个最优方式:首先,它实现最大可能的商店,然后它实现尽可能最大的商店,而不会超出数组的最大长度。


对于代码的其余部分,让我们通过它走行由行:

  • sub $0x28,%rsp

rsp是包含堆栈指针寄存器。这是从堆栈指针中减去0x28字节,有效地预留了堆栈中的40个字节空间,以便本地使用该函数。它明确使用10个字节左右;剩下的空间可能是调用约定所需的,或者被分配为优化以保持对齐。

  • mov %fs:0x28,%rax

这将会从%fs:0x28%rax值,并将其存储。 fs是段寄存器,并且0x28是偏移量。现代的32位和64位操作系统不像使用旧的16位实模式那样使用分段寻址,但fs通常用于线程本地存储。因此,代码正在从线程本地存储块的起始位置读取偏移量为0x28的值,并将其置于rax寄存器中。

  • mov %rax,0x18(%rsp)

此存储从rax到内存中(我们只是装在里面的一个)的值。具体来说,它将它从堆栈指针(rsp)的偏移量0x18处加载到堆栈上。

我猜这些两行代码实现了一些类型的堆栈金丝雀,但我不能确定没有关于您的操作系统,编译器设置等更多信息。我的编译器不会生成这样的代码当我编译你的代码。

  • xor %eax,%eax

这一个简单的,但有点模糊。按位与寄存器进行XOR操作是将寄存器的内容置零的旧技巧。这也是by far the most optimal way of doing it,所以这是所有编译器将生成的代码。

现在,它可能看起来有点奇怪,它只是清零32位eax寄存器,而不是整个64位rax寄存器,但事实上,它是这样做的。 Virtually all instructions that operate on 32-bit registers in long mode implicitly zero the upper 32-bit half of the register。这是体系结构级别上的一个重要优化,由于编译器知道处理器将执行此操作,因此它会发出利用该代码的代码。 32位XOR指令较小,因此比它发射的更快,但行为相同。

为什么编译器发出代码来清除寄存器rax/eax?因为在我知道的所有x86调用约定中,该寄存器用于函数的返回值。您的main函数返回0,因此编译器正在安排返回值位于rax寄存器中。

+0

仍在阅读你的答案,多好的答案。我还添加了汇编代码的其余部分。 – dud3

+0

增加了一些系统和编译器信息。 – dud3

+0

但是为什么它会在之后将'eax'设置为零:'mov $ 0x0,%eax'? – dud3