2017-04-01 91 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