2017-03-06 99 views
1

今天我開始學習機器語言。我用C寫了一個基本的「Hello World」程序,它打印出「Hello,world!」使用for循環十次。然後我用GNU調試拆卸主,在機器語言的代碼看(我的電腦有一個x86處理器和我設置GDB爲使用英特爾的語法):編譯的「Hello World」C程序如何使用機器語言存儲字符串?

[email protected]:~/Path/To/Code$ gdb -q ./a.out 
Reading symbols from ./a.out...done. 
(gdb) list 
1  #include <stdio.h> 
2 
3  int main() 
4  { 
5   int i; 
6   for(i = 0; i < 10; i++) { 
7    printf("Hello, world!\n"); 
8   } 
9   return 0; 
10  } 
(gdb) disassemble main 
Dump of assembler code for function main: 
    0x0804841d <+0>:  push ebp 
    0x0804841e <+1>:  mov  ebp,esp 
    0x08048420 <+3>:  and  esp,0xfffffff0 
    0x08048423 <+6>:  sub  esp,0x20 
    0x08048426 <+9>:  mov  DWORD PTR [esp+0x1c],0x0 
    0x0804842e <+17>: jmp  0x8048441 <main+36> 
    0x08048430 <+19>: mov  DWORD PTR [esp],0x80484e0 
    0x08048437 <+26>: call 0x80482f0 <[email protected]> 
    0x0804843c <+31>: add  DWORD PTR [esp+0x1c],0x1 
    0x08048441 <+36>: cmp  DWORD PTR [esp+0x1c],0x9 
    0x08048446 <+41>: jle  0x8048430 <main+19> 
    0x08048448 <+43>: mov  eax,0x0 
    0x0804844d <+48>: leave 
    0x0804844e <+49>: ret 
End of assembler dump. 
(gdb) x/s 0x80484e0 
0x80484e0: "Hello, world!" 

我瞭解大部分機器代碼以及每個命令的作用。如果我理解正確,地址「0x80484e0」被加載到esp寄存器中,以便可以使用該地址處的存儲器。我檢查了地址,並且毫不奇怪,它包含了所需的字符串。我現在的問題是 - 那根弦是如何到達那裏的?我找不到在該位置設置字符串的程序中的一部分。

我也不明白其他東西:當我第一次啓動程序時,eip指向,其中變量i在[esp + 0x1c]初始化。但是,esp指向的地址稍後會在程序中更改(至0x80484e0),但在更改後,仍然使用[esp + 0x1c]作爲「i」。當地址esp指向更改時,地址[esp + 0x1c]不應該改變嗎?

+1

它是在你的二進制文件中,當操作系統啓動你的程序時,它被加載到內存中,就像程序的機器代碼一樣。請注意,'[esp]'與'esp'不同,前者訪問內存,並不改變'esp'本身。 – Jester

+0

它加載到內存中。我假定該字符串的內存地址從0x8048441開始,到0x80484e0結束。請記住一個字符串是一個整數列表。 – Xorifelse

+0

@Jester啊,所以「mov DWORD PTR [esp],0x80484e0」實際上並不是指向一個新的地址,而只是將0x80484e0寫到它當前指向的地址? –

回答

2

我的二進制或程序是由機器代碼和數據組成的。在這種情況下,你在源代碼中放入的字符串,編譯器也僅僅是字節的數據,以及由於它被如何使用而被認爲是隻讀數據,所以取決於可能以.rodata或.text寫入的編譯器或編譯器可能使用的其他名稱。海灣合作委員會可能會把它叫做.rodata。程序本身在.text中。鏈接器出現時,鏈接時找到.text,.data,.bss,.rodata和其他任何可能的項目,然後連接點。在你調用printf的情況下,鏈接器知道它把字符串,字節數組放在了什麼位置,並且它被告知它的名字是什麼(一些內部的臨時名字毫無疑問),並且printf調用被告知這個名字,所以鏈接器在調用printf之前修改指令以將地址抓取到格式字符串。

Disassembly of section .text: 

0000000000400430 <main>: 
    400430: 53      push %rbx 
    400431: bb 0a 00 00 00   mov $0xa,%ebx 
    400436: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 
    40043d: 00 00 00 
    400440: bf e4 05 40 00   mov $0x4005e4,%edi 
    400445: e8 b6 ff ff ff   callq 400400 <[email protected]> 
    40044a: 83 eb 01    sub $0x1,%ebx 
    40044d: 75 f1     jne 400440 <main+0x10> 
    40044f: 31 c0     xor %eax,%eax 
    400451: 5b      pop %rbx 
    400452: c3      retq 
    400453: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 
    40045a: 00 00 00 
    40045d: 0f 1f 00    nopl (%rax) 



Disassembly of section .rodata: 

00000000004005e0 <_IO_stdin_used>: 
    4005e0: 01 00     add %eax,(%rax) 
    4005e2: 02 00     add (%rax),%al 
    4005e4: 48      rex.W 
    4005e5: 65 6c     gs insb (%dx),%es:(%rdi) 
    4005e7: 6c      insb (%dx),%es:(%rdi) 
    4005e8: 6f      outsl %ds:(%rsi),(%dx) 
    4005e9: 2c 20     sub $0x20,%al 
    4005eb: 77 6f     ja  40065c <__GNU_EH_FRAME_HDR+0x68> 
    4005ed: 72 6c     jb  40065b <__GNU_EH_FRAME_HDR+0x67> 
    4005ef: 64 21 00    and %eax,%fs:(%rax) 

編譯器將已編碼的該指令,但可能留下的地址爲0或者一些填充

400440: bf e4 05 40 00   mov $0x4005e4,%edi 

,以便鏈接可以在以後的填充它。 gnu反彙編器試圖分解沒有意義的.rodata(和.data等)塊,所以忽略它試圖解釋從地址0x4005e4開始的字符串的指令。

鏈接對象的解體前顯示了這兩個部分的.text和.RODATA

Disassembly of section .text.startup: 

0000000000000000 <main>: 
    0: 53      push %rbx 
    1: bb 0a 00 00 00   mov $0xa,%ebx 
    6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 
    d: 00 00 00 
    10: bf 00 00 00 00   mov $0x0,%edi 
    15: e8 00 00 00 00   callq 1a <main+0x1a> 
    1a: 83 eb 01    sub $0x1,%ebx 
    1d: 75 f1     jne 10 <main+0x10> 
    1f: 31 c0     xor %eax,%eax 
    21: 5b      pop %rbx 
    22: c3      retq 

0000000000000000 <.rodata.str1.1>: 
    0: 48      rex.W 
    1: 65 6c     gs insb (%dx),%es:(%rdi) 
    3: 6c      insb (%dx),%es:(%rdi) 
    4: 6f      outsl %ds:(%rsi),(%dx) 
    5: 2c 20     sub $0x20,%al 
    7: 77 6f     ja  78 <main+0x78> 
    9: 72 6c     jb  77 <main+0x77> 
    b: 64 21 00    and %eax,%fs:(%rax) 

斷開鏈接它只是墊這個地址/鏈接器偏移到後來練習I.

10: bf 00 00 00 00   mov $0x0,%edi 

還注意該對象只包含在.rodata中的字符串。與圖書館和其他項目鏈接,使它成爲一個完整的程序清楚地增加了更多.rodata,但鏈接器管理所有這些。

也許更容易看到這個例子

void more_fun (unsigned int, unsigned int, unsigned int); 

unsigned int a; 
unsigned int b=5; 
const unsigned int c=7; 

void fun (void) 
{ 
    more_fun(a,b,c); 
} 

拆解爲對象

Disassembly of section .text: 

0000000000000000 <fun>: 
    0: 8b 35 00 00 00 00  mov 0x0(%rip),%esi  # 6 <fun+0x6> 
    6: 8b 3d 00 00 00 00  mov 0x0(%rip),%edi  # c <fun+0xc> 
    c: ba 07 00 00 00   mov $0x7,%edx 
    11: e9 00 00 00 00   jmpq 16 <fun+0x16> 

Disassembly of section .data: 

0000000000000000 <b>: 
    0: 05      .byte 0x5 
    1: 00 00     add %al,(%rax) 
    ... 

Disassembly of section .rodata: 

0000000000000000 <c>: 
    0: 07      (bad) 
    1: 00 00     add %al,(%rax) 
    ... 

以任何理由,你必須掛靠看到.bss段。這個例子的要點是函數的機器碼在.text中,未初始化的全局在.bss中,初始化的全局是.data,而const初始化的全局是.rodata。編譯器足夠聰明,知道常量即使是全局也不會改變,所以它可以將該值硬編碼到數學中,而不需要從RAM中讀取,但是它必須從RAM讀取的其他兩個變量纔會生成一條指令地址零在鏈接時由鏈接器填充。

在你的情況你的只讀/ const數據是一個字節的集合,它不是一個數學運算,所以你的源文件中定義的字節被放置在內存中,以便它們可以被指向作爲printf的第一個參數。

還有更多的二進制不只是機器碼。編譯器和鏈接器可以將內容放置在內存中以供機器代碼獲取,機器代碼本身不必編寫將被其餘機器代碼使用的每個值。

+0

將對象部分rodata顯示爲數據'dw 1,2'' db「Hello,World!」'更好,而不是使用產生奇怪球指令序列的文本的反彙編。 – rcgldr

+0

當然,然後代碼是不是乾淨的閱讀(-s代替-c在編譯),重要的是要理解它只是字節數組的字節......處理器不會看到ascii ... –

+0

我假設上面已經提到的人 - 要看看發生了什麼......如果沒有,然後使用-s或使用-save-temps進行編譯,並且饋送給彙編器的程序集將不會被刪除。 –

0

編譯器'硬連線'到目標代碼的字符串和鏈接器然後'硬連線'到機器代碼。

不是說字符串嵌入到代碼中,而不是存儲在數據區域中,這意味着如果你拿一個指向字符串的指針並試圖改變它,你會得到一個異常。

+0

這可能再次被修復在運行時啓動,使黑客的工作變得困難。 –

相關問題