上一篇已經(jīng)得到了C語(yǔ)言入門(mén)程序?qū)?yīng)的匯編程序。C語(yǔ)言程序:
#include <stdio.h>
int main()
{
printf("hello,world\n");
}
編譯后的匯編程序:
.file "hello.c"
.intel_syntax
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "hello,world\12\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
push ebp
mov ebp, esp
sub esp, 8
and esp, -16
mov eax, 0
add eax, 15
add eax, 15
shr eax, 4
sal eax, 4
mov DWORD PTR [ebp-4], eax
mov eax, DWORD PTR [ebp-4]
call __alloca
call ___main
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
leave
ret
.def _printf; .scl 2; .type 32; .endef
先看匯編程序的第一行:
.file "hello.c"
好像沒(méi)見(jiàn)過(guò)這寫(xiě)法炬搭,什么意思?查資料去……
查資料途中,又看見(jiàn)一種ARM匯編涯呻,和上一篇提到的ATT匯編和Intel匯編是什么關(guān)系?
原來(lái)腻要,ARM和X86是兩種不同的CPU架構(gòu)复罐,在ARM架構(gòu)的CPU上使用ARM匯編,在X86架構(gòu)的CPU上使用X86匯編雄家。而X86匯編又有兩種語(yǔ)法格式效诅,即ATT格式和Intel格式。(另外ATT匯編語(yǔ)法是跨平臺(tái)的趟济,還可以在其他架構(gòu)的CPU上使用乱投,比如Power架構(gòu)。)
ATT語(yǔ)法中寄存器前面有%咙好,如%eax篡腌,在Intel語(yǔ)法中則直接寫(xiě)成eax。(ARM匯編比X86匯編多許多寄存器勾效,并且寄存器用R0嘹悼、R1……表示。)
現(xiàn)在可以確定上面編譯出來(lái)的是Intel格式的匯編程序了层宫,再繼續(xù)看第一行代碼:
.file "hello.c"
經(jīng)過(guò)查資料得知杨伙,這種“.xx”的符號(hào)都是匯編器as的指令,匯編器as即上一篇提到的gcc編譯工作第3步——將.s文件匯編成.o目標(biāo)文件——中使用的工具萌腿。下面是匯編器as的官方文檔地址:
https://sourceware.org/binutils/docs/as/
所有“.xx”指令都能在文檔中查到限匣,添加注釋如下:
.file "hello.c" /*指示as我們將要啟動(dòng)一個(gè)新的邏輯文件。*/
.intel_syntax /*使用英特爾匯編程序語(yǔ)法進(jìn)行匯編毁菱。*/
.def ___main; .scl 2; /*.scl class 設(shè)置符號(hào)的存儲(chǔ)類值米死。*/ .type 32; /* .type int 把整數(shù)int作為類型屬性記錄進(jìn)符號(hào)表表項(xiàng)锌历。*/ .endef /*.def name 開(kāi)始為符號(hào)name定義調(diào)試信息;該定義一直擴(kuò)展到.endef遇到指令為止峦筒。*/
.section .rdata,"dr" /* .section name[, "flags"] 使用.section命令將后續(xù)的代碼匯編進(jìn)一個(gè)定名為name的段究西。可選參數(shù)使用了引號(hào)物喷,它將被視為該段的標(biāo)志(flags)卤材。每個(gè)標(biāo)記是單個(gè)的字符。d:數(shù)據(jù)段峦失,r:只讀段扇丛。*/
LC0:
.ascii "hello,world\12\0" /*把匯編好的每個(gè)字符串(在字符串末不自動(dòng)追加零字節(jié))存入連續(xù)的地址。*/
.text /*通知as把后續(xù)語(yǔ)句匯編到編號(hào)為0的子段尉辑。*/
.globl _main /*使符號(hào) _main 對(duì)連接器ld可見(jiàn)帆精。*/
.def _main; .scl 2; .type 32; .endef
_main:
push ebp
mov ebp, esp
sub esp, 8
and esp, -16
mov eax, 0
add eax, 15
add eax, 15
shr eax, 4
sal eax, 4
mov DWORD PTR [ebp-4], eax
mov eax, DWORD PTR [ebp-4]
call __alloca
call ___main
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
leave
ret
.def _printf; .scl 2; .type 32; .endef
知道了這些指令的大概意思,現(xiàn)在來(lái)看代碼主體:
_main:
push ebp
mov ebp, esp
sub esp, 8
and esp, -16
mov eax, 0
add eax, 15
add eax, 15
shr eax, 4
sal eax, 4
mov DWORD PTR [ebp-4], eax
mov eax, DWORD PTR [ebp-4]
call __alloca
call ___main
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
leave
ret
這些匯編指令看起來(lái)眼熟多了材蹬,ebp和eax這些寄存器是bp实幕、ax寄存器的32位版本,它們的關(guān)系類似于ax和al堤器。眼熟歸眼熟昆庇,但除了知道 “call _printf” 指令是調(diào)用 “_printf” 子函數(shù)以及 “ret” 指令是結(jié)束 “_mian” 函數(shù)并返回之外,上面一堆指令的作用是啥闸溃?還是不知道整吆。不過(guò)看匯編代碼只是為了理解C語(yǔ)言的語(yǔ)法,而不是編譯原理辉川,所以這些與C語(yǔ)言源代碼功能(打印“hello,world”)無(wú)關(guān)的內(nèi)容就暫且不管了表蝙。
因此就剩下了一行代碼:
call _printf
查了下printf函數(shù)的源碼以及底層實(shí)現(xiàn)原理的東西,發(fā)現(xiàn)涉及到的知識(shí)還是挺多的乓旗,為了防止偏離目標(biāo)方向太遠(yuǎn)府蛇,就先不研究它了。匯編中向屏幕輸出字符串屿愚,直接向顯存的某行某列寫(xiě)入內(nèi)容就行了汇跨,代碼例如:
data segment
db 'hello,world'
data ends
code segment
......
mov ax,0b800h
mov es,ax
mov bx,160*10+40*2 //在屏幕第11行第41列開(kāi)始寫(xiě)入內(nèi)容
mov si,0
mov cx,11
s:mov al,[si]
mov es:[bx],al
inc si
inc bx
inc bx
loop s
......
code ends
printf函數(shù)最終的實(shí)現(xiàn)原理也與之類似,只不過(guò)多了格式處理妆距、獲取權(quán)限等一些步驟穷遂。
好了,入門(mén)程序就先學(xué)到這里娱据,下一篇開(kāi)始學(xué)習(xí)C語(yǔ)言變量等內(nèi)容蚪黑。