转汇编
http://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html
?
阮一峰的網(wǎng)絡(luò)日志???首頁(yè)???檔案- 上一篇:加密貨幣的本質(zhì)????
- 下一篇:Docker?入門教程
分類:
?- 理解計(jì)算機(jī)
匯編語言入門教程
日期:?2018年1月21日
感謝?贊助本站
?
學(xué)習(xí)編程其實(shí)就是學(xué)高級(jí)語言,即那些為人類設(shè)計(jì)的計(jì)算機(jī)語言。
但是,計(jì)算機(jī)不理解高級(jí)語言,必須通過編譯器轉(zhuǎn)成二進(jìn)制代碼,才能運(yùn)行。學(xué)會(huì)高級(jí)語言,并不等于理解計(jì)算機(jī)實(shí)際的運(yùn)行步驟。
計(jì)算機(jī)真正能夠理解的是低級(jí)語言,它專門用來控制硬件。匯編語言就是低級(jí)語言,直接描述/控制 CPU 的運(yùn)行。如果你想了解 CPU 到底干了些什么,以及代碼的運(yùn)行步驟,就一定要學(xué)習(xí)匯編語言。
匯編語言不容易學(xué)習(xí),就連簡(jiǎn)明扼要的介紹都很難找到。下面我嘗試寫一篇最好懂的匯編語言教程,解釋 CPU 如何執(zhí)行代碼。
一、匯編語言是什么?
我們知道,CPU 只負(fù)責(zé)計(jì)算,本身不具備智能。你輸入一條指令(instruction),它就運(yùn)行一次,然后停下來,等待下一條指令。
這些指令都是二進(jìn)制的,稱為操作碼(opcode),比如加法指令就是00000011。編譯器的作用,就是將高級(jí)語言寫好的程序,翻譯成一條條操作碼。
對(duì)于人類來說,二進(jìn)制程序是不可讀的,根本看不出來機(jī)器干了什么。為了解決可讀性的問題,以及偶爾的編輯需求,就誕生了匯編語言。
匯編語言是二進(jìn)制指令的文本形式,與指令是一一對(duì)應(yīng)的關(guān)系。比如,加法指令00000011寫成匯編語言就是 ADD。只要還原成二進(jìn)制,匯編語言就可以被 CPU 直接執(zhí)行,所以它是最底層的低級(jí)語言。
二、來歷
最早的時(shí)候,編寫程序就是手寫二進(jìn)制指令,然后通過各種開關(guān)輸入計(jì)算機(jī),比如要做加法了,就按一下加法開關(guān)。后來,發(fā)明了紙帶打孔機(jī),通過在紙帶上打孔,將二進(jìn)制指令自動(dòng)輸入計(jì)算機(jī)。
為了解決二進(jìn)制指令的可讀性問題,工程師將那些指令寫成了八進(jìn)制。二進(jìn)制轉(zhuǎn)八進(jìn)制是輕而易舉的,但是八進(jìn)制的可讀性也不行。很自然地,最后還是用文字表達(dá),加法指令寫成 ADD。內(nèi)存地址也不再直接引用,而是用標(biāo)簽表示。
這樣的話,就多出一個(gè)步驟,要把這些文字指令翻譯成二進(jìn)制,這個(gè)步驟就稱為 assembling,完成這個(gè)步驟的程序就叫做 assembler。它處理的文本,自然就叫做 aseembly code。標(biāo)準(zhǔn)化以后,稱為 assembly language,縮寫為 asm,中文譯為匯編語言。
每一種 CPU 的機(jī)器指令都是不一樣的,因此對(duì)應(yīng)的匯編語言也不一樣。本文介紹的是目前最常見的 x86 匯編語言,即 Intel 公司的 CPU 使用的那一種。
三、寄存器
學(xué)習(xí)匯編語言,首先必須了解兩個(gè)知識(shí)點(diǎn):寄存器和內(nèi)存模型。
先來看寄存器。CPU 本身只負(fù)責(zé)運(yùn)算,不負(fù)責(zé)儲(chǔ)存數(shù)據(jù)。數(shù)據(jù)一般都儲(chǔ)存在內(nèi)存之中,CPU 要用的時(shí)候就去內(nèi)存讀寫數(shù)據(jù)。但是,CPU 的運(yùn)算速度遠(yuǎn)高于內(nèi)存的讀寫速度,為了避免被拖慢,CPU 都自帶一級(jí)緩存和二級(jí)緩存。基本上,CPU 緩存可以看作是讀寫速度較快的內(nèi)存。
但是,CPU 緩存還是不夠快,另外數(shù)據(jù)在緩存里面的地址是不固定的,CPU 每次讀寫都要尋址也會(huì)拖慢速度。因此,除了緩存之外,CPU 還自帶了寄存器(register),用來儲(chǔ)存最常用的數(shù)據(jù)。也就是說,那些最頻繁讀寫的數(shù)據(jù)(比如循環(huán)變量),都會(huì)放在寄存器里面,CPU 優(yōu)先讀寫寄存器,再由寄存器跟內(nèi)存交換數(shù)據(jù)。
寄存器不依靠地址區(qū)分?jǐn)?shù)據(jù),而依靠名稱。每一個(gè)寄存器都有自己的名稱,我們告訴 CPU 去具體的哪一個(gè)寄存器拿數(shù)據(jù),這樣的速度是最快的。有人比喻寄存器是 CPU 的零級(jí)緩存。
四、寄存器的種類
早期的 x86 CPU 只有8個(gè)寄存器,而且每個(gè)都有不同的用途。現(xiàn)在的寄存器已經(jīng)有100多個(gè)了,都變成通用寄存器,不特別指定用途了,但是早期寄存器的名字都被保存了下來。
- EAX
- EBX
- ECX
- EDX
- EDI
- ESI
- EBP
- ESP
上面這8個(gè)寄存器之中,前面七個(gè)都是通用的。ESP 寄存器有特定用途,保存當(dāng)前 Stack 的地址(詳見下一節(jié))。
我們常常看到 32位 CPU、64位 CPU 這樣的名稱,其實(shí)指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4個(gè)字節(jié)。
五、內(nèi)存模型:Heap
寄存器只能存放很少量的數(shù)據(jù),大多數(shù)時(shí)候,CPU 要指揮寄存器,直接跟內(nèi)存交換數(shù)據(jù)。所以,除了寄存器,還必須了解內(nèi)存怎么儲(chǔ)存數(shù)據(jù)。
程序運(yùn)行的時(shí)候,操作系統(tǒng)會(huì)給它分配一段內(nèi)存,用來儲(chǔ)存程序和運(yùn)行產(chǎn)生的數(shù)據(jù)。這段內(nèi)存有起始地址和結(jié)束地址,比如從0x1000到0x8000,起始地址是較小的那個(gè)地址,結(jié)束地址是較大的那個(gè)地址。
程序運(yùn)行過程中,對(duì)于動(dòng)態(tài)的內(nèi)存占用請(qǐng)求(比如新建對(duì)象,或者使用malloc命令),系統(tǒng)就會(huì)從預(yù)先分配好的那段內(nèi)存之中,劃出一部分給用戶,具體規(guī)則是從起始地址開始劃分(實(shí)際上,起始地址會(huì)有一段靜態(tài)數(shù)據(jù),這里忽略)。舉例來說,用戶要求得到10個(gè)字節(jié)內(nèi)存,那么從起始地址0x1000開始給他分配,一直分配到地址0x100A,如果再要求得到22個(gè)字節(jié),那么就分配到0x1020。
這種因?yàn)橛脩糁鲃?dòng)請(qǐng)求而劃分出來的內(nèi)存區(qū)域,叫做 Heap(堆)。它由起始地址開始,從低位(地址)向高位(地址)增長(zhǎng)。Heap 的一個(gè)重要特點(diǎn)就是不會(huì)自動(dòng)消失,必須手動(dòng)釋放,或者由垃圾回收機(jī)制來回收。
六、內(nèi)存模型:Stack
除了 Heap 以外,其他的內(nèi)存占用叫做 Stack(棧)。簡(jiǎn)單說,Stack 是由于函數(shù)運(yùn)行而臨時(shí)占用的內(nèi)存區(qū)域。
請(qǐng)看下面的例子。
int main() {int a = 2; int b = 3; }上面代碼中,系統(tǒng)開始執(zhí)行main函數(shù)時(shí),會(huì)為它在內(nèi)存里面建立一個(gè)幀(frame),所有main的內(nèi)部變量(比如a和b)都保存在這個(gè)幀里面。main函數(shù)執(zhí)行結(jié)束后,該幀就會(huì)被回收,釋放所有的內(nèi)部變量,不再占用空間。
如果函數(shù)內(nèi)部調(diào)用了其他函數(shù),會(huì)發(fā)生什么情況?
int main() {int a = 2; int b = 3; return add_a_and_b(a, b); }上面代碼中,main函數(shù)內(nèi)部調(diào)用了add_a_and_b函數(shù)。執(zhí)行到這一行的時(shí)候,系統(tǒng)也會(huì)為add_a_and_b新建一個(gè)幀,用來儲(chǔ)存它的內(nèi)部變量。也就是說,此時(shí)同時(shí)存在兩個(gè)幀:main和add_a_and_b。一般來說,調(diào)用棧有多少層,就有多少幀。
等到add_a_and_b運(yùn)行結(jié)束,它的幀就會(huì)被回收,系統(tǒng)會(huì)回到函數(shù)main剛才中斷執(zhí)行的地方,繼續(xù)往下執(zhí)行。通過這種機(jī)制,就實(shí)現(xiàn)了函數(shù)的層層調(diào)用,并且每一層都能使用自己的本地變量。
所有的幀都存放在 Stack,由于幀是一層層疊加的,所以 Stack 叫做棧。生成新的幀,叫做"入棧",英文是 push;棧的回收叫做"出棧",英文是 pop。Stack 的特點(diǎn)就是,最晚入棧的幀最早出棧(因?yàn)樽顑?nèi)層的函數(shù)調(diào)用,最先結(jié)束運(yùn)行),這就叫做"后進(jìn)先出"的數(shù)據(jù)結(jié)構(gòu)。每一次函數(shù)執(zhí)行結(jié)束,就自動(dòng)釋放一個(gè)幀,所有函數(shù)執(zhí)行結(jié)束,整個(gè) Stack 就都釋放了。
Stack 是由內(nèi)存區(qū)域的結(jié)束地址開始,從高位(地址)向低位(地址)分配。比如,內(nèi)存區(qū)域的結(jié)束地址是0x8000,第一幀假定是16字節(jié),那么下一次分配的地址就會(huì)從0x7FF0開始;第二幀假定需要64字節(jié),那么地址就會(huì)移動(dòng)到0x7FB0。
七、CPU 指令
7.1 一個(gè)實(shí)例
了解寄存器和內(nèi)存模型以后,就可以來看匯編語言到底是什么了。下面是一個(gè)簡(jiǎn)單的程序example.c。
int add_a_and_b(int a, int b) { return a + b; } int main() { return add_a_and_b(2, 3); }gcc 將這個(gè)程序轉(zhuǎn)成匯編語言。
$ gcc -S example.c上面的命令執(zhí)行以后,會(huì)生成一個(gè)文本文件example.s,里面就是匯編語言,包含了幾十行指令。這么說吧,一個(gè)高級(jí)語言的簡(jiǎn)單操作,底層可能由幾個(gè),甚至幾十個(gè) CPU 指令構(gòu)成。CPU 依次執(zhí)行這些指令,完成這一步操作。
example.s經(jīng)過簡(jiǎn)化以后,大概是下面的樣子。
_add_a_and_b:push %ebxmov %eax, [%esp+8] mov %ebx, [%esp+12] add %eax, %ebx pop %ebx ret _main: push 3 push 2 call _add_a_and_b add %esp, 8 ret可以看到,原程序的兩個(gè)函數(shù)add_a_and_b和main,對(duì)應(yīng)兩個(gè)標(biāo)簽_add_a_and_b和_main。每個(gè)標(biāo)簽里面是該函數(shù)所轉(zhuǎn)成的 CPU 運(yùn)行流程。
每一行就是 CPU 執(zhí)行的一次操作。它又分成兩部分,就以其中一行為例。
push %ebx這一行里面,push是 CPU 指令,%ebx是該指令要用到的運(yùn)算子。一個(gè) CPU 指令可以有零個(gè)到多個(gè)運(yùn)算子。
下面我就一行一行講解這個(gè)匯編程序,建議讀者最好把這個(gè)程序,在另一個(gè)窗口拷貝一份,省得閱讀的時(shí)候再把頁(yè)面滾動(dòng)上來。
7.2 push 指令
根據(jù)約定,程序從_main標(biāo)簽開始執(zhí)行,這時(shí)會(huì)在 Stack 上為main建立一個(gè)幀,并將 Stack 所指向的地址,寫入 ESP 寄存器。后面如果有數(shù)據(jù)要寫入main這個(gè)幀,就會(huì)寫在 ESP 寄存器所保存的地址。
然后,開始執(zhí)行第一行代碼。
push 3push指令用于將運(yùn)算子放入 Stack,這里就是將3寫入main這個(gè)幀。
雖然看上去很簡(jiǎn)單,push指令其實(shí)有一個(gè)前置操作。它會(huì)先取出 ESP 寄存器里面的地址,將其減去4個(gè)字節(jié),然后將新地址寫入 ESP 寄存器。使用減法是因?yàn)?Stack 從高位向低位發(fā)展,4個(gè)字節(jié)則是因?yàn)?的類型是int,占用4個(gè)字節(jié)。得到新地址以后, 3 就會(huì)寫入這個(gè)地址開始的四個(gè)字節(jié)。
push 2第二行也是一樣,push指令將2寫入main這個(gè)幀,位置緊貼著前面寫入的3。這時(shí),ESP 寄存器會(huì)再減去 4個(gè)字節(jié)(累計(jì)減去8)。
7.3 call 指令
第三行的call指令用來調(diào)用函數(shù)。
call _add_a_and_b上面的代碼表示調(diào)用add_a_and_b函數(shù)。這時(shí),程序就會(huì)去找_add_a_and_b標(biāo)簽,并為該函數(shù)建立一個(gè)新的幀。
下面就開始執(zhí)行_add_a_and_b的代碼。
push %ebx這一行表示將 EBX 寄存器里面的值,寫入_add_a_and_b這個(gè)幀。這是因?yàn)楹竺嬉玫竭@個(gè)寄存器,就先把里面的值取出來,用完后再寫回去。
這時(shí),push指令會(huì)再將 ESP 寄存器里面的地址減去4個(gè)字節(jié)(累計(jì)減去12)。
7.4 mov 指令
mov指令用于將一個(gè)值寫入某個(gè)寄存器。
mov %eax, [%esp+8]這一行代碼表示,先將 ESP 寄存器里面的地址加上8個(gè)字節(jié),得到一個(gè)新的地址,然后按照這個(gè)地址在 Stack 取出數(shù)據(jù)。根據(jù)前面的步驟,可以推算出這里取出的是2,再將2寫入 EAX 寄存器。
下一行代碼也是干同樣的事情。
mov %ebx, [%esp+12]上面的代碼將 ESP 寄存器的值加12個(gè)字節(jié),再按照這個(gè)地址在 Stack 取出數(shù)據(jù),這次取出的是3,將其寫入 EBX 寄存器。
7.5 add 指令
add指令用于將兩個(gè)運(yùn)算子相加,并將結(jié)果寫入第一個(gè)運(yùn)算子。
add %eax, %ebx上面的代碼將 EAX 寄存器的值(即2)加上 EBX 寄存器的值(即3),得到結(jié)果5,再將這個(gè)結(jié)果寫入第一個(gè)運(yùn)算子 EAX 寄存器。
7.6 pop 指令
pop指令用于取出 Stack 最近一個(gè)寫入的值(即最低位地址的值),并將這個(gè)值寫入運(yùn)算子指定的位置。
pop %ebx上面的代碼表示,取出 Stack 最近寫入的值(即 EBX 寄存器的原始值),再將這個(gè)值寫回 EBX 寄存器(因?yàn)榧臃ㄒ呀?jīng)做完了,EBX 寄存器用不到了)。
注意,pop指令還會(huì)將 ESP 寄存器里面的地址加4,即回收4個(gè)字節(jié)。
7.7 ret 指令
ret指令用于終止當(dāng)前函數(shù)的執(zhí)行,將運(yùn)行權(quán)交還給上層函數(shù)。也就是,當(dāng)前函數(shù)的幀將被回收。
ret可以看到,該指令沒有運(yùn)算子。
隨著add_a_and_b函數(shù)終止執(zhí)行,系統(tǒng)就回到剛才main函數(shù)中斷的地方,繼續(xù)往下執(zhí)行。
add %esp, 8上面的代碼表示,將 ESP 寄存器里面的地址,手動(dòng)加上8個(gè)字節(jié),再寫回 ESP 寄存器。這是因?yàn)?ESP 寄存器的是 Stack 的寫入開始地址,前面的pop操作已經(jīng)回收了4個(gè)字節(jié),這里再回收8個(gè)字節(jié),等于全部回收。
ret最后,main函數(shù)運(yùn)行結(jié)束,ret指令退出程序執(zhí)行。
八、參考鏈接
- Introduction to reverse engineering and Assembly, by Youness Alaoui
- x86 Assembly Guide, by University of Virginia Computer Science
(完)
文檔信息
- 版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名(創(chuàng)意共享3.0許可證)
- 發(fā)表日期:?2018年1月21日
?
相關(guān)文章
- 2018.10.16:?exFAT 文件系統(tǒng)指南 國(guó)慶假期,我拍了一些手機(jī)視頻,打算存到新買的移動(dòng)硬盤。
- 2018.07.16:?CAP 定理的含義 分布式系統(tǒng)(distributed system)正變得越來越重要,大型網(wǎng)站幾乎都是分布式的。
- 2018.05.09:?根域名的知識(shí) 域名是互聯(lián)網(wǎng)的基礎(chǔ)設(shè)施,只要上網(wǎng)就會(huì)用到。
- 2018.01.11:?加密貨幣的本質(zhì) 現(xiàn)在,各種加密貨幣(cryptocurrency)不計(jì)其數(shù)。
廣告(購(gòu)買廣告位)
Go 語言課程
React 框架課程
留言(95條)
以前在學(xué)校的時(shí)候看過王爽的匯編語言第二版,那個(gè)時(shí)候還是很喜歡一些偏底層的東西的,計(jì)算機(jī)原理等書。現(xiàn)在接觸到高級(jí)語言之后就全忘了。。。
2018年1月21日 19:40?|?#?|?引用
阮老師JS的閉包是不是在棧中的內(nèi)存占用不回收呢?會(huì)一直占用?還是說閉包的空間是v8申請(qǐng)的椎的空間?
2018年1月21日 20:25?|?#?|?引用
平時(shí)接觸不到這些細(xì)節(jié),但非常喜歡這方面的知識(shí),懇請(qǐng)阮老師推薦一些這方面的權(quán)威書籍。;)
2018年1月21日 22:23?|?#?|?引用
感謝分享
2018年1月22日 02:09?|?#?|?引用
為什么我們用到EBX就push EBX,而用到EAX卻沒push EAX呢?
2018年1月22日 08:01?|?#?|?引用
@jimmy
我的理解是 EAX 屬于最頻繁使用的通用寄存器,所以約定沒有必要保留它的值。
2018年1月22日 09:36?|?#?|?引用
@kailin:參考鏈接里面,我已經(jīng)提供了兩篇文章。
@zeon:是的,閉包屬于 Stack 里面的幀不回收,詳見?https://blog.sessionstack.com/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks-3f28b94cfbec
2018年1月22日 09:38?|?#?|?引用
基本的點(diǎn)都講到了,但不是計(jì)算機(jī)專業(yè)的讀者估計(jì)還是會(huì)有點(diǎn)懵,建議結(jié)合下內(nèi)存總線和硬件指令的電路原理。
2018年1月22日 09:41?|?#?|?引用
關(guān)注阮大有一陣時(shí)間了,從來沒留過言,剛好最近在重新看王爽的《匯編語言》,沒想到阮大最近發(fā)布了這篇文章,必須手動(dòng)來贊了!
2018年1月22日 10:17?|?#?|?引用
阮老師牛逼!阮老師,這個(gè)匯編語言現(xiàn)在市場(chǎng)上一般企業(yè)這方面人的需求不大吧?
2018年1月22日 10:31?|?#?|?引用
== 它會(huì)先取出 ESP 寄存器里面的地址,將其減去4個(gè)字節(jié),然后將新地址寫入 ESP 寄存器。使用減法是因?yàn)?Stack 從高位向低位發(fā)展,4個(gè)字節(jié)則是因?yàn)?的類型是int,占用4個(gè)字節(jié)。得到新地址以后, 3 就會(huì)寫入這個(gè)地址開始的四個(gè)字節(jié)。==
這個(gè)地方不懂啊
2018年1月22日 10:58?|?#?|?引用
引用jimmy的發(fā)言:
為什么我們用到EBX就push EBX,而用到EAX卻沒push EAX呢?
EAX用于保存返回值,這個(gè)值肯定會(huì)被覆蓋,所以需要調(diào)用著保存。
rbx,rbp,r12-r15是被調(diào)用著保存寄存器,如果被調(diào)用著需要使用,就需要壓入stack中。
其他寄存器是調(diào)用者保存寄存器,調(diào)用者如果需要調(diào)用前后這些值保持一致,則需要自己保存起來
2018年1月22日 11:41?|?#?|?引用
可以看看 小甲魚的匯編教程視頻
2018年1月22日 12:04?|?#?|?引用
intel格式的匯編寄存器之前不用加%吧
2018年1月22日 12:41?|?#?|?引用
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _add_a_b
.p2align 4, 0x90
_add_a_b: ## @add_a_b
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %esi
addl -8(%rbp), %esi
movl %esi, %eax
popq %rbp
retq
.cfi_endproc
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi3:
.cfi_def_cfa_offset 16
Lcfi4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi5:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $1, %edi
movl $2, %esi
movl $0, -4(%rbp)
callq _add_a_b
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
2018年1月22日 13:24?|?#?|?引用
引用阮老師小迷弟的發(fā)言:
阮老師牛逼!阮老師,這個(gè)匯編語言現(xiàn)在市場(chǎng)上一般企業(yè)這方面人的需求不大吧?
一般開發(fā)中確實(shí)用不到,也很少有人會(huì)用到匯編去做開發(fā)。但是如果你不了解匯編,就沒有辦法真正了解Java虛擬機(jī)中的各種概念。只有真正學(xué)習(xí)過匯編的人,才能真正理解各種概念
2018年1月22日 15:27?|?#?|?引用
引用xxxx的發(fā)言:
一般開發(fā)中確實(shí)用不到,也很少有人會(huì)用到匯編去做開發(fā)。但是如果你不了解匯編,就沒有辦法真正了解Java虛擬機(jī)中的各種概念。只有真正學(xué)習(xí)過匯編的人,才能真正理解各種概念
不用理解JAVA虛擬機(jī)的概念。造汽車的人,無需了解鋼鐵是如何煉成的。
很多技術(shù)是層疊的,做頂層業(yè)務(wù)的,是需也不太可能將所有底層技術(shù)進(jìn)行追溯學(xué)習(xí)的。
2018年1月22日 16:41?|?#?|?引用
0xFFFF0010(_main+16)這里指的是為_add_a_and_b建立的幀嗎?也是占4個(gè)字節(jié)嗎?那_main+16又是指什么?
2018年1月22日 17:41?|?#?|?引用
一個(gè)匯編指令對(duì)應(yīng)的機(jī)器碼不一定只有一個(gè)。比如,匯編器會(huì)根據(jù)mov后面的操作數(shù)將mov翻譯成不同的機(jī)器碼。以前一直想不明白,后來終于在依照51單片機(jī)手冊(cè)用C寫模擬器時(shí)弄明白了。同時(shí)也知道了寄存器A、AX、EAX、RAX。
個(gè)人還是覺得看看CMU的CSAPP對(duì)計(jì)算機(jī)入門很有幫助,不論平時(shí)工作是做哪方面的開發(fā):JS,PHP,Python,Bash,Java,C-like(如果書里的入門內(nèi)容都不會(huì),估計(jì)也做不了C-like開發(fā))。
2018年1月22日 18:22?|?#?|?引用
當(dāng)初學(xué)匯編,被指令搞的頭暈眼花的,習(xí)慣了又發(fā)現(xiàn)還有32位64位的坑。。
2018年1月22日 18:37?|?#?|?引用
看完阮老師的文章另我想起很久以前做出版時(shí)經(jīng)常出現(xiàn) postscript 錯(cuò)誤 stack overflow,
萬分感謝阮老師分享,另我了解底層技術(shù)!
2018年1月22日 23:02?|?#?|?引用
為什么一提匯編就要上x86的匯編呢……x86匯編很麻煩的……
比如我一開始用的匯編就是PS2的CPU(EE)的MIPS匯編,覺得真是簡(jiǎn)單清晰啊……
2018年1月23日 05:49?|?#?|?引用
全部忘記了,就像失憶一樣。
2018年1月23日 09:33?|?#?|?引用
引用阮老師小迷弟的發(fā)言:
阮老師牛逼!阮老師,這個(gè)匯編語言現(xiàn)在市場(chǎng)上一般企業(yè)這方面人的需求不大吧?
基礎(chǔ)扎實(shí)了,什么高級(jí)語言搞不定!
2018年1月23日 09:34?|?#?|?引用
intel 格式匯編沒有% AT&T 匯編有% 但是格式和intel 相反
2018年1月23日 20:09?|?#?|?引用
通俗易懂。阮老師帶我們輕松復(fù)習(xí)了一遍丟掉好久的知識(shí)。:-)
2018年1月24日 08:18?|?#?|?引用
講解生動(dòng),十分感人,Heap和Stack百度搜過N多次,這是第一次看到結(jié)合‘堆’、‘棧’語義的簡(jiǎn)單解釋,非常好,以后應(yīng)該都能記住了。
贊 贊 贊
2018年1月24日 09:49?|?#?|?引用
計(jì)算機(jī)組成原理...今年剛考完...汗
2018年1月24日 12:52?|?#?|?引用
我請(qǐng)教一個(gè)問題,如文中所述:比如,內(nèi)存區(qū)域的結(jié)束地址是0x8000,第一幀假定是16字節(jié),那么下一次分配的地址就會(huì)從0x7FF0開始;第二幀假定需要64字節(jié),那么地址就會(huì)移動(dòng)到0x7FB0。
我想知道是怎樣分配每一幀的大小呢,比如_add_a_and_b:,我該怎樣知道分配多少內(nèi)存給這一幀呢?
2018年1月25日 09:59?|?#?|?引用
AT&T 匯編,感覺更舒服,而且目前unix linux 系列支撐
2018年1月25日 14:51?|?#?|?引用
引用阮老師的小粉絲的發(fā)言:
我請(qǐng)教一個(gè)問題,如文中所述:比如,內(nèi)存區(qū)域的結(jié)束地址是0x8000,第一幀假定是16字節(jié),那么下一次分配的地址就會(huì)從0x7FF0開始;第二幀假定需要64字節(jié),那么地址就會(huì)移動(dòng)到0x7FB0。
我想知道是怎樣分配每一幀的大小呢,比如_add_a_and_b:,我該怎樣知道分配多少內(nèi)存給這一幀呢?
?
C語言的數(shù)據(jù)類型都有大小。編譯器可以根據(jù)函數(shù)內(nèi)定義的所有局部變量(其實(shí)真實(shí)情況更復(fù)雜一些,還有static,const等修飾符會(huì)影響),一次性“分配”出相應(yīng)數(shù)量的內(nèi)存(就是將sp的值減去相應(yīng)的大小)。函數(shù)返回時(shí),會(huì)先清理掉自己的局部變量(用leave把bp恢復(fù)到sp),再將sp所指地址的內(nèi)容恢復(fù)到bp,然后ret。阮老文章里的例子過于簡(jiǎn)單,有些指令沒用上。
P.S.
匯編指令也是會(huì)分成幾個(gè)步驟執(zhí)行(所謂的指令周期,機(jī)器周期,時(shí)鐘周期),所以如果有說錯(cuò)或不清楚的地方,勿噴,多包涵。
2018年1月25日 15:21?|?#?|?引用
請(qǐng)教您一個(gè)問題,Heap是先進(jìn)先出的嗎?我在StackOverflow上看到的是Heap沒有一個(gè)明確的頂,所以它可以隨時(shí)進(jìn)入和出去。
2018年1月25日 15:42?|?#?|?引用
引用alexsaurora的發(fā)言:
請(qǐng)教您一個(gè)問題,Heap是先進(jìn)先出的嗎?我在StackOverflow上看到的是Heap沒有一個(gè)明確的頂,所以它可以隨時(shí)進(jìn)入和出去。
堆棧,堆棧,堆是堆,棧是棧。
(信號(hào)是信號(hào),信號(hào)量是信號(hào)量,一個(gè)是signal,一個(gè)是semaphore。回想起了好些迷惑的術(shù)語翻譯)
=======
進(jìn)程中heap跟在data區(qū)域的后面(請(qǐng)參考任何一個(gè)進(jìn)程的maps)。heap所占用的內(nèi)存是C庫(kù)調(diào)用brk系統(tǒng)調(diào)用向操作系統(tǒng)(暫時(shí)不考慮Windows)申請(qǐng)的(詳細(xì)內(nèi)容可以man brk家族的文檔),操作系統(tǒng)只是維護(hù)brk的位置,C庫(kù)會(huì)負(fù)責(zé)管理申請(qǐng)到的內(nèi)存。
以前debug segfault時(shí),看過Android的malloc實(shí)現(xiàn),其實(shí)就是Doug Lea的dlmalloc(wiki有詳細(xì)介紹,Android源碼也可以隨便看)。dlmalloc會(huì)根據(jù)程序的需求將操作系統(tǒng)給的連續(xù)內(nèi)存分成內(nèi)存塊,每個(gè)塊的頭部保存著大小、是否已分配等信息。塊是內(nèi)存對(duì)齊的,相鄰塊是緊挨著的(因此,可以合并)。當(dāng)代碼調(diào)用malloc時(shí),malloc會(huì)查找滿足需求的塊,如果找不到,就會(huì)再次調(diào)用brk向操作系統(tǒng)申請(qǐng)。當(dāng)代碼調(diào)用free時(shí),free會(huì)更新塊頭部的信息,可能還會(huì)把相鄰的空閑塊合并,組成更大的塊。
實(shí)際情況比描述的要復(fù)雜,并且也有其他實(shí)現(xiàn)方式。dlmalloc的數(shù)據(jù)結(jié)構(gòu)和擴(kuò)展分區(qū)的結(jié)構(gòu)很類似。從第一個(gè)塊開始捋,順藤摸瓜,就可以遍歷所有塊。我嘴比較笨,描述不清楚,網(wǎng)上有很多形象的圖片可以看。
2018年1月25日 18:22?|?#?|?引用
感謝分享,學(xué)過微機(jī)原理,沒用上
2018年1月26日 17:17?|?#?|?引用
引用zeon的發(fā)言:
阮老師JS的閉包是不是在棧中的內(nèi)存占用不回收呢?會(huì)一直占用?還是說閉包的空間是v8申請(qǐng)的椎的空間?
JS里的閉包,都是在堆中申請(qǐng)的,由GC管理,不是這里的棧,“JS棧”與匯編或C語言中的棧是兩個(gè)概念。匯編棧不存在GC,由函數(shù)調(diào)用與返回來自動(dòng)更新SP指針實(shí)現(xiàn)的。JS函數(shù)與這兒的函數(shù)是兩種東西。
2018年1月28日 17:49?|?#?|?引用
忘的差不多了。
2018年1月29日 12:19?|?#?|?引用
這一句話是錯(cuò)誤的:“32 位 CPU 的寄存器大小就是4個(gè)字節(jié)”。32位CPU容量是可拓展的,可修正為32位CPU的的最大尋址范圍是4G。
2018年1月30日 09:41?|?#?|?引用
阮老師好像有10天的時(shí)間沒更新文章了!
2018年1月31日 10:21?|?#?|?引用
阮老師,您好,我是將在今年畢業(yè)的一名大學(xué)生,對(duì)前端很感興趣,希望你能給我學(xué)習(xí)前端的建議的大概方向書籍網(wǎng)站資源等。我看過了你的JavaScript標(biāo)準(zhǔn)參考教程,覺得寫得很通俗易懂。希望您能看到,等待您的寶貴建議。
2018年2月 1日 16:26?|?#?|?引用
想知道這個(gè)留言系統(tǒng)
2018年2月 2日 09:46?|?#?|?引用
引用jimmy的發(fā)言:
為什么我們用到EBX就push EBX,而用到EAX卻沒push EAX呢?
好久沒看匯編,忘得差不多了。
好像是eax里面一般保存的是返回值,所以執(zhí)行過程中ebx寄存器需要先把內(nèi)容壓棧,
使用完恢復(fù),eax則不用,因?yàn)樽罱K的返回值就在里面。
2018年2月 2日 18:11?|?#?|?引用
引用張春星的發(fā)言:
== 它會(huì)先取出 ESP 寄存器里面的地址,將其減去4個(gè)字節(jié),然后將新地址寫入 ESP 寄存器。使用減法是因?yàn)?Stack 從高位向低位發(fā)展,4個(gè)字節(jié)則是因?yàn)?的類型是int,占用4個(gè)字節(jié)。得到新地址以后, 3 就會(huì)寫入這個(gè)地址開始的四個(gè)字節(jié)。==
這個(gè)地方不懂啊
ESP始終指向棧頂,棧從高地址向低地址增長(zhǎng),push 2,push 3分別把兩個(gè)
參數(shù)壓入棧中,此時(shí)的棧頂?shù)闹羔樢驗(yàn)閴喝雰蓚€(gè)4字節(jié)的Int類型,指向初始地址
減8的內(nèi)存單元,然后函數(shù)調(diào)用返回后,add esp,8就是直接設(shè)置esp指向的位置,
進(jìn)行加8操作后,棧頂指針回到函數(shù)調(diào)用前的位置,這個(gè)叫棧平衡,然后好像是intel,
還是windowis,他們的編譯器規(guī)定函數(shù)調(diào)用,由調(diào)用者恢復(fù)棧平衡,所以最后是main函數(shù)部分做加8這個(gè)操作。
2018年2月 2日 18:18?|?#?|?引用
那,printf 那種的函數(shù)會(huì)被編譯成什么呢?
2018年2月 4日 23:18?|?#?|?引用
引用zeon的發(fā)言:
阮老師JS的閉包是不是在棧中的內(nèi)存占用不回收呢?會(huì)一直占用?還是說閉包的空間是v8申請(qǐng)的椎的空間?
建議了解瀏覽器內(nèi)存回收機(jī)制。閉包是因?yàn)橐恢北3忠藐P(guān)系,所以不會(huì)被回收
2018年2月 5日 09:38?|?#?|?引用
阮老師的文章很贊
2018年2月 6日 17:15?|?#?|?引用
引用zhanghang的發(fā)言:
計(jì)算機(jī)組成原理...今年剛考完...汗
你好,可能理解力不夠。我還是沒看懂的一點(diǎn)是,匯編語言只是二進(jìn)制的文本形式,那最后計(jì)算機(jī)是直接識(shí)別運(yùn)行這個(gè)匯編代碼嗎?感覺還差一步,就是匯編語言 -> 機(jī)器碼
2018年2月 6日 17:54?|?#?|?引用
為啥稱呼為x86
2018年2月 7日 21:51?|?#?|?引用
mov %eax, [%esp+8]?
mov %ebx, [%esp+12]
怎么感覺應(yīng)該是
mov %eax, [%esp+4]?
mov %ebx, [%esp+8]
2018年2月 8日 01:24?|?#?|?引用
引用Silen的發(fā)言:
mov%eax, [%esp+8]?
mov%ebx, [%esp+12]
怎么感覺應(yīng)該是
mov%eax, [%esp+4]?
mov%ebx, [%esp+8]
?
同問,比較疑惑,如果push寫入棧時(shí)是從低位開始寫,那讀取應(yīng)該也是從低位開始讀吧?那地址應(yīng)該是?
[%esp+4](取到2),?
[%esp+8](取到3)?
這樣吧?
2018年2月 8日 17:57?|?#?|?引用
引用rus的發(fā)言:
?
同問,比較疑惑,如果push寫入棧時(shí)是從低位開始寫,那讀取應(yīng)該也是從低位開始讀吧?那地址應(yīng)該是?
[%esp+4](取到2),?
[%esp+8](取到3)?
這樣吧?
%ebx 占了4個(gè)字節(jié)
2 本身占了4個(gè)字節(jié)
讀取數(shù)據(jù)時(shí)得返回到數(shù)據(jù)開始的位置
是這樣吧?
2018年2月 8日 23:43?|?#?|?引用
阮大寫的很好,非計(jì)算機(jī)專業(yè)的我也能理解,很缺乏這方面知識(shí),看完學(xué)會(huì)了很多,感謝.
2018年2月11日 00:18?|?#?|?引用
阮大佬寫的東西總是那么通俗易懂
在這不光學(xué)到了知識(shí),平常寫文章的思路也清晰了
-.-
2018年2月27日 13:30?|?#?|?引用
大神
量子計(jì)算機(jī)的底層語言方面的能來一篇么。
2018年3月 4日 09:41?|?#?|?引用
"得到結(jié)果5,再將這個(gè)結(jié)果寫入第一個(gè)運(yùn)算子 EAX 寄存器"
結(jié)果是如何返回的呢?阮老師。感覺EBX寄存器被臨時(shí)拿來用恢復(fù)原樣,但EAX寄存器存的固定的是內(nèi)存全部回收后返回值么?
2018年3月 8日 11:25?|?#?|?引用
引用GD的發(fā)言:
?
%ebx 占了4個(gè)字節(jié)
2本身占了4個(gè)字節(jié)
讀取數(shù)據(jù)時(shí)得返回到數(shù)據(jù)開始的位置
是這樣吧?
?
[%esp]--> %ebx?
[%esp+4] --> call 語句的下一條指令的地址
[%esp+8] --> 第一個(gè)參數(shù)
[%esp+12] --> 第二個(gè)參數(shù)
正是因?yàn)閇%esp+4] 是call 語句的下一條指令地址,才會(huì)有Stack OverFlow。
2018年3月 8日 17:13?|?#?|?引用
期待下一講。百度一大堆教程,沒幾個(gè)看得懂的,而且頭疼。
阮老師這一講精彩,期待更多內(nèi)容
2018年3月10日 20:47?|?#?|?引用
引用kailin’的發(fā)言:
平時(shí)接觸不到這些細(xì)節(jié),但非常喜歡這方面的知識(shí),懇請(qǐng)阮老師推薦一些這方面的權(quán)威書籍。;)
https://item.jd.com/12006637.html?可以看這本書
2018年3月14日 12:02?|?#?|?引用
阮老師,我正在學(xué)匯編,突然看到這篇文章讓我對(duì)匯編又有了更深的理解。但是我發(fā)現(xiàn)老師你文中的匯編代碼是 Intel 風(fēng)格和 AT&T 風(fēng)格的混用體
2018年3月19日 15:32?|?#?|?引用
同問,
mov%eax, [%esp+8]?
mov%ebx, [%esp+12]
為什么不是
mov%eax, [%esp+4]?
mov%ebx, [%esp+8]
2018年3月27日 16:39?|?#?|?引用
看了很多文章都不太通透,當(dāng)然現(xiàn)在還是不太通透,但比過去好太多了,老師的講解的很細(xì)致,非常感謝。
2018年3月27日 20:54?|?#?|?引用
樓主寫的太好了,簡(jiǎn)單易懂,高人。
2018年3月29日 10:16?|?#?|?引用
舉例來說,用戶要求得到10個(gè)字節(jié)內(nèi)存,那么從起始地址0x1000開始給他分配,一直分配到地址0x100A,如果再要求得到22個(gè)字節(jié),那么就分配到0x1020
這里沒看懂,22個(gè)字節(jié)不是應(yīng)該分配到0x1016嗎?,我感覺32個(gè)字節(jié)才應(yīng)該是0x1020
2018年3月30日 01:18?|?#?|?引用
六、內(nèi)存模型:Stack
這里也不太懂后進(jìn)先出,main和add_a_and_b,我覺得后進(jìn)棧的應(yīng)該是add_a and b,那么先出的也應(yīng)該是它,但是從圖看感覺是main先完成的,然后再進(jìn)行的add_a and b,這里就很懵了,希望大哥們指點(diǎn)迷津
2018年3月30日 01:34?|?#?|?引用
寫的很不錯(cuò)
2018年4月 8日 19:59?|?#?|?引用
引用William_ch的發(fā)言:
?
[%esp]--> %ebx?
[%esp+4] --> call 語句的下一條指令的地址
[%esp+8] --> 第一個(gè)參數(shù)
[%esp+12] --> 第二個(gè)參數(shù)
正是因?yàn)閇%esp+4] 是call 語句的下一條指令地址,才會(huì)有Stack OverFlow。
我理解其實(shí)應(yīng)該是因?yàn)?#43;4的位置放了一條函數(shù)指針,也就是返回main函數(shù)的入口,指針存放的是地址,大小4字節(jié),所以數(shù)據(jù)2和3對(duì)應(yīng)的是+8和+12,不知道這樣理解是否正確,還請(qǐng)指正,謝謝~
2018年4月12日 10:08?|?#?|?引用
有點(diǎn)疑問:
push %ebx
mov %eax, [%esp+8]?
mov %ebx, [%esp+12]
這里ebx存的是什么?是push esp的那兩個(gè)參數(shù)嗎?
那在函數(shù)_add_a_and_b下
mov %eax, [%esp+8] 和?
mov %ebx, [%esp+12] 為什么不是減少地址而是加?
2018年4月13日 12:18?|?#?|?引用
引用shine的發(fā)言:
有點(diǎn)疑問:
push %ebx
mov%eax, [%esp+8]?
mov%ebx, [%esp+12]
這里ebx存的是什么?是push esp的那兩個(gè)參數(shù)嗎?
那在函數(shù)_add_a_and_b下
mov%eax, [%esp+8] 和
mov%ebx, [%esp+12] 為什么不是減少地址而是加?
?
ebx原來保存的數(shù)據(jù)先取出來,防止原數(shù)據(jù)被覆蓋;
加是因?yàn)闂^(qū)是從高位地址開始分配,esp當(dāng)前保存的總是低地址
2018年4月25日 11:31?|?#?|?引用
關(guān)于+4 +8的,我覺得其實(shí)其中還有一塊存放著上一個(gè)函數(shù)的return地址(32位地址占4個(gè)) 所以是+8去取參數(shù)2的開頭地址。
2018年5月 6日 22:10?|?#?|?引用
請(qǐng)教一下這些圖片是用什么軟件畫出來的?
2018年5月30日 15:44?|?#?|?引用
阮老師講的很通俗易懂,思路清晰,我一個(gè)計(jì)算機(jī)原理小白都看的一知半解的了,希望以后有更多這樣的文章
2018年6月 1日 16:58?|?#?|?引用
從零學(xué)匯編,只有會(huì)這方面的人才懂得我這份熱情
2018年6月11日 13:12?|?#?|?引用
引用阮一峰的發(fā)言:
@jimmy
我的理解是 EAX 屬于最頻繁使用的通用寄存器,所以約定沒有必要保留它的值。
哈哈,我覺得是因?yàn)槌绦蛑械膔eturn,假設(shè)把原程序修改一下,改成:
int add_a_and_b(int a, int b) {
return a + b;
}
int main() {
return add_a_and_b(2, 3) + 3; // 修改此處代碼
}
那么,在_add_a_and_b函數(shù)返回時(shí),就不需要再?gòu)膬?nèi)存中取出結(jié)果了,直接把后面累加的3存到EBX中,然后執(zhí)行add %eax, %ebx即可。
這樣就少了一次從內(nèi)存中取出數(shù)據(jù)的操作(可能還有存入內(nèi)存的操作)。
2018年6月11日 17:58?|?#?|?引用
第一個(gè)問題:
為什么Stack要設(shè)計(jì)成從高位到低位?
這樣做,每次向Stack內(nèi)執(zhí)行push的時(shí)候,ESP都要執(zhí)行減法操作,這樣性能難道比"從低位到高?
位,每次執(zhí)行加法操作"更好?
還是說,因?yàn)闉榱俗x取數(shù)據(jù)的時(shí)候執(zhí)行加法( [%esp+8] )而做出的優(yōu)化?
第二個(gè)問題:
計(jì)算機(jī)是怎么知道3存在0x0000到0x0004之間,2存在0x0005到0x0008之間?
畢竟ESP只是記住了當(dāng)前Stack所存儲(chǔ)的數(shù)據(jù)的最低位地址!
難道是寫死在程序里的?就像例子中的
mov %eax, [%esp+8]?
mov %ebx, [%esp+12]
一樣,程序已經(jīng)寫死了偏移量?
如果我的問題描述不夠明確,您可以通過郵箱聯(lián)系我,這兩個(gè)問題我很想知道
2018年6月11日 18:19?|?#?|?引用
講得真好! 不過,語句講得太少了。
2018年6月21日 16:17?|?#?|?引用
棒,看了很多講解匯編原理的,大都晦澀難懂,唯有這篇看了以后令人茅塞頓開
2018年7月19日 00:43?|?#?|?引用
我想請(qǐng)問一下為什么我的GCC編譯出來的匯編指令全是movl popq 以及.cfi_startproc之類的很復(fù)雜的指令,與老師的大不相同?
2018年7月21日 00:11?|?#?|?引用
引用kimika的發(fā)言:
我想請(qǐng)問一下為什么我的GCC編譯出來的匯編指令全是movl popq 以及.cfi_startproc之類的很復(fù)雜的指令,與老師的大不相同?
因?yàn)槟阌玫氖?4位的(q代表以4字節(jié)為單位操作),必需在gcc后面加個(gè)“-m32”
2018年7月24日 23:46?|?#?|?引用
原來留言不可以超過1200字……我只好分開發(fā)了……
(↑↑↑上面的xxx其實(shí)也是我……)
只能說阮老師簡(jiǎn)化得太多了,我表示很遺憾……
那我盡量講清楚吧
開始寫這個(gè)已經(jīng)半夜了……如果以上有錯(cuò)誤其實(shí)很正常,請(qǐng)立即指出但要多多包涵。
引用Singu的發(fā)言:第一個(gè)問題:
為什么Stack要設(shè)計(jì)成從高位到低位?
這樣做,每次向Stack內(nèi)執(zhí)行push的時(shí)候,ESP都要執(zhí)行減法操作,這樣性能難道比"從低位
到高?
位,每次執(zhí)行加法操作"更好?
還是說,因?yàn)闉榱俗x取數(shù)據(jù)的時(shí)候執(zhí)行加法( [%esp+8] )而做出的優(yōu)化?
第二個(gè)問題:
計(jì)算機(jī)是怎么知道3存在0x0000到0x0004之間,2存在0x0005到0x0008之間?
畢竟ESP只是記住了當(dāng)前Stack所存儲(chǔ)的數(shù)據(jù)的最低位地址!
難道是寫死在程序里的?就像例子中的
mov%eax, [%esp+8]?
mov%ebx, [%esp+12]
一樣,程序已經(jīng)寫死了偏移量?
如果我的問題描述不夠明確,您可以通過郵箱聯(lián)系我,這兩個(gè)問題我很想知道
這兩個(gè)問題被問了很多遍啊……我想解決它……
能一起提出這兩個(gè)問題的人很厲害啊,因?yàn)檫@兩個(gè)問題是相互關(guān)聯(lián)的!!!
2018年7月25日 01:30?|?#?|?引用
以32位x86為例:
信息1. call [addr]等價(jià)于push %eip加jmp [addr],無非%eip是不能直接操作的(就是說
push %eip是無效指令)
(注:%eip是存儲(chǔ)下一條指令地址的寄存器)
信息2. push [data]等價(jià)于sub sizeof(data),%esp加mov [data],[%esp],也就是說,在
push時(shí)%esp只能減小,這是歷史遺留問題……(你要自己設(shè)計(jì)cpu當(dāng)然可以定義為增加)
信息3. ret(無參)等價(jià)于pop %eip(有跳轉(zhuǎn)效果,因?yàn)橹苯有薷?eip相當(dāng)于跳轉(zhuǎn),jmp指
令內(nèi)部原理就是修改%eip)
信息4. 對(duì)于現(xiàn)代32位x86,正確(完整且沒經(jīng)過優(yōu)化的)函數(shù)應(yīng)該大概長(zhǎng)這樣:
// gcc -S test.cpp -o test.s -m32 (64位機(jī)器一定要加-m32,指定使用32位
)
void foo(int,int);
int test() {
int a = 3;
int b = 2;
foo(2,3);
foo(a,b);
return 999;
}
(注:這是真實(shí)的gcc輸出)
(詳見下一條)
2018年7月25日 01:30?|?#?|?引用
__Z4testv:(使用一般調(diào)用協(xié)議,詳情請(qǐng)查詢“ABI”,有歷史遺留問題
)
pushl %ebp(保存調(diào)用者設(shè)置的%ebp)
movl %esp, %ebp(保存調(diào)用者設(shè)置的%esp)
(注意:調(diào)用者的%esp被保存%ebp里,以上兩句指令可以縮寫為enter,
這是另一個(gè)指令)
(從此以后%ebp成為棧空間尋址的基準(zhǔn),因此%ebp全稱為(擴(kuò)展)基址指
針寄存器)
(問:根據(jù)信息1和2,在%ebp - 4上的是什么東西?)
(答:返回地址)
(問:那么在%ebp - 8上的是什么東西?)
(答:保存的%esp)
(問:那么,在%ebp上的是什么東西?)
(答:不知道~這個(gè)地址原則上不可以訪問,它屬于調(diào)
用者的棧空間)
(問:那么%ebp - 12呢?)
(答:變量a,見下)
subl $40, %esp(為call預(yù)留%esp,也就是棧空間,共計(jì)40字節(jié),由
編譯器計(jì)算得)
(這個(gè)40很復(fù)雜,用于保存局部變量、傳出的參數(shù)等,不展開,因?yàn)檫@個(gè)
由編譯器決定)
(40字節(jié)不是都被使用了,因?yàn)槲覀儾]有開優(yōu)化,你能否計(jì)算出有多少
字節(jié)沒被使用?)
(答:有16字節(jié)被浪費(fèi)了,16=40-8-sizeof(a)-sizeof(b)-
sizeof(傳出參數(shù)消耗的棧),詳情見下)
movl $3, -12(%ebp)(預(yù)留了就可以使用這種方法保存變量,這個(gè)相當(dāng)于int a
= 3)
movl $2, -16(%ebp)(相當(dāng)于int b = 2,具體地址由編譯器分配
)
(還沒完,見下一條)
2018年7月25日 01:31?|?#?|?引用
movl $3, 4(%esp)(這里還是被編譯器優(yōu)化過了,相當(dāng)于
push %eax,見信息2,mov比push快,無非棧空間必需夠大)
(注:別忘了esp=ebp-40)
movl $2, (%esp)(所以這兩句話相當(dāng)于先push $3,再push $2)
call __Z3fooii(干一點(diǎn)其他事情,不用管棧空間變量,因?yàn)闂?臻g比%esp地
址高,被保護(hù))
movl -16(%ebp), %eax(先從棧空間加載數(shù)據(jù)到%eax寄存器)
movl %eax, 4(%esp)(再把%eax里的數(shù)據(jù)轉(zhuǎn)入%esp,因?yàn)閙ov指令一次只能操作
一個(gè)單位的內(nèi)存)
(拓展:局部變量的賦值如果不開優(yōu)化也是類似這么寫的)
movl -12(%ebp), %eax
movl %eax, (%esp)
call __Z3fooii
movl $999, %eax (設(shè)置返回值,返回值存在%eax里,歷史遺留問題)
leave (恢復(fù)調(diào)用者的%ebp和%esp,這句指令也有另一種寫法,大家可以思考一下
)
ret (真正的返回,思考:根據(jù)信息3,為什么這樣用是安全的,棧不是被動(dòng)過了
嗎?)
ps: 是不是覺得編譯器超強(qiáng)大(我還沒開優(yōu)化呢……)
ps: 軟件底層概念超多,極耗腦力,還有極多歷史遺留問題,這種問題真的很難一次性給答
案……我已經(jīng)盡力了,打字打的我累死
2018年7月25日 01:32?|?#?|?引用
其實(shí)我覺得阮老師對(duì)匯編的掌握也不夠多(或許只是文章篇幅太短?)同學(xué)們別問了……(阮老師要再想寫這方面的文章,講真的可以聯(lián)系我)
(我發(fā)的東西格式是不是出了點(diǎn)問題……)
2018年7月25日 01:38?|?#?|?引用
有一個(gè)疑問,esp寄存器中存放的函數(shù)的地址是起始地址和結(jié)束地址還是只是結(jié)束地址呢?不好意思哦,可能問題有點(diǎn)白癡。
2018年8月 5日 23:49?|?#?|?引用
引用Singu的發(fā)言:
第一個(gè)問題:
為什么Stack要設(shè)計(jì)成從高位到低位?
這樣做,每次向Stack內(nèi)執(zhí)行push的時(shí)候,ESP都要執(zhí)行減法操作,這樣性能難道比"從低位到高?
位,每次執(zhí)行加法操作"更好?
還是說,因?yàn)闉榱俗x取數(shù)據(jù)的時(shí)候執(zhí)行加法( [%esp+8] )而做出的優(yōu)化?
第二個(gè)問題:
計(jì)算機(jī)是怎么知道3存在0x0000到0x0004之間,2存在0x0005到0x0008之間?
畢竟ESP只是記住了當(dāng)前Stack所存儲(chǔ)的數(shù)據(jù)的最低位地址!
難道是寫死在程序里的?就像例子中的
mov%eax, [%esp+8]?
mov%ebx, [%esp+12]
一樣,程序已經(jīng)寫死了偏移量?
如果我的問題描述不夠明確,您可以通過郵箱聯(lián)系我,這兩個(gè)問題我很想知道
第一個(gè)問題, 因?yàn)閔eap從低到高分配性能高,stack只能從高到低嘍,地址兩頭往中間拱,空間利用率高啊
第二個(gè)問題, 什么偏移量,什么寫死,32位系統(tǒng),除8,就是4啊
個(gè)人理解,歡迎拍
2018年9月20日 11:20?|?#?|?引用
寫得真好,期待第二課
2018年9月21日 17:04?|?#?|?引用
確實(shí)學(xué)到知識(shí)了,謝謝~
2018年9月30日 10:17?|?#?|?引用
引用齊軍的發(fā)言:
第二個(gè)問題, 什么偏移量,什么寫死,32位系統(tǒng),除8,就是4啊
第二個(gè)問題,我覺得是編譯器寫死的
2018年10月 9日 13:50?|?#?|?引用
太感謝了,前段時(shí)間想學(xué)匯編,看朱邦復(fù)老先生的《組合語言的藝術(shù)》,看不太懂,他還推薦《ZEN of Assembly Language》
2018年10月16日 19:39?|?#?|?引用
引用阮一峰的發(fā)言:
@jimmy
我的理解是 EAX 屬于最頻繁使用的通用寄存器,所以約定沒有必要保留它的值。
南大的《計(jì)算機(jī)系統(tǒng)基礎(chǔ)》上說,每個(gè)指令集體系有一個(gè)約定,規(guī)定哪些寄存器是調(diào)用者保存,哪些寄存器由被調(diào)用者來保存
2018年10月23日 14:29?|?#?|?引用
「幀」的英文叫什么?segment?
2018年12月12日 23:08?|?#?|?引用
高級(jí)語言學(xué)的不少,但是對(duì)內(nèi)存、CPU對(duì)程序的執(zhí)行還是不了解,所以,看看匯編確實(shí)受益匪淺,感謝!!!
2018年12月27日 18:54?|?#?|?引用
引用Silen的發(fā)言:
mov%eax, [%esp+8]?
mov%ebx, [%esp+12]
怎么感覺應(yīng)該是
mov%eax, [%esp+4]?
mov%ebx, [%esp+8]
?
第一天看匯編,看了阮老師提供的英文鏈接,在調(diào)用函數(shù)“ _add_a_and_b"時(shí),首先會(huì)在stack中保存main函數(shù)中下一條指令“ add %esp, 8”的地址,距離“_main”有4條指令,所以是“_main+4*4”即“_main+16”。此時(shí)
ESP 寄存器會(huì)再減去 4個(gè)字節(jié)(累計(jì)減去12)。
然后再執(zhí)行子函數(shù)中的“push %ebx”這時(shí),push指令會(huì)再將 ESP 寄存器里面的地址減去4個(gè)字節(jié)(累計(jì)減去16)。所以感覺阮老師在文中此處的解釋應(yīng)該有誤(原文解釋此時(shí)累計(jì)減去12)。
所以
mov%eax, [%esp+8] 對(duì)應(yīng)數(shù)據(jù)“2”
mov%ebx, [%esp+12] 對(duì)應(yīng)數(shù)據(jù)“3”
2019年1月22日 17:35?|?#?|?引用
引用立猛的發(fā)言:
intel 格式匯編沒有% AT&T 匯編有% 但是格式和intel 相反
是的,阮老師的這篇文章寫的有問題。
2019年2月24日 17:13?|?#?|?引用
我覺得通過匯編來深刻理解高級(jí)語言的原理不是最大的作用,最大的作用應(yīng)該就是反匯編寫外掛了....,要學(xué)寫外掛,匯編是第一個(gè)要掌握的東西
2019年2月25日 10:10?|?#?|?引用
引用hazdzz的發(fā)言:
「幀」的英文叫什么?segment?
frame吧
2019年2月26日 18:46?|?#?|?引用
我要發(fā)表看法
您的留言 (HTML標(biāo)簽部分可用)
您的大名:
??-必填
電子郵件:
??-必填,不公開
個(gè)人網(wǎng)址:
??-我信任你,不會(huì)填寫廣告鏈接
記住個(gè)人信息?
??- 點(diǎn)擊按鈕
微博?|?推特?|?GitHub
2019 ??我的郵件?|
轉(zhuǎn)載于:https://www.cnblogs.com/maweiwei/p/10499530.html
總結(jié)
- 上一篇: 通过Ajax来简单的实现局部刷新(主要为
- 下一篇: 边分治学习笔记