![](/img/trans.png)
[英]a Simple “Hello World” Inline Assembly language Program in C/C++
[英]How does a compiled “Hello World” C program store the String using machine language?
所以我今天開始學習機器語言。 我用C語言編寫了一個基本的“ Hello World”程序,其中顯示了“ Hello,world!”。 使用for循環十次。 然后,我使用Gnu Debugger來反匯編main並以機器語言查看代碼(我的計算機具有x86處理器,並且我將gdb設置為使用intel語法):
user@PC:~/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 <puts@plt>
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]不應該更改嗎?
二進制或程序由機器代碼和數據組成。 在這種情況下,您在源代碼中放入的字符串,編譯器也將這些只是字節的數據,並且由於使用方式而被視為只讀數據,因此取決於編譯器可能位於.rodata或.text中或編譯器可能使用的其他名稱。 Gcc可能將其稱為.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 <puts@plt>
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)
編譯器將對該指令進行編碼,但可能會將地址保留為零或某些填充
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)
取消鏈接,它只需填充此地址/偏移量,以便鏈接器稍后填充。
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。 編譯器足夠聰明,可以知道即使全局常量也不會更改const,因此它可以將該值硬編碼到數學中,而無需從ram讀取,但是它必須從ram讀取的其他兩個變量因此生成指令鏈接器在鏈接時填充地址零。
在您的情況下,您的只讀/常量數據是字節的集合,並且不是數學運算,因此將源文件中定義的字節放置在內存中,因此可以將它們作為printf的第一個參數指向。
二進制文件不僅僅是機器代碼。 而且,編譯器和鏈接器可以將內容放置在內存中,以便機器代碼獲得,機器代碼本身不必編寫將由其余機器代碼使用的每個值。
編譯器將字符串“硬連接”到目標代碼中,然后鏈接程序將其“硬連接”到機器代碼中。
並不是說字符串是嵌入到代碼中的,也不是存儲在數據區域中的,這意味着如果您使用了指向該字符串的指針並嘗試對其進行更改,則會出現異常。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.