標籤:
根據163MOOC學院中國科學技術大學孟寧孟老師課程所寫得部落格
肖沖沖 原創作品請註明出處
《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一,電腦的工作過程
電腦的基本原理是儲存程式和程式控制(馮﹒諾依曼體系),簡單來說,我們需要先把需要進行操作的指令(程式)和資料先輸入到電腦的存放裝置中,然後電腦將嚴格執行需要執行的指令,包括從那個地址取數(或指令),進行什麼操作(加減移位等),然後再送回到什麼地址。
從硬體上來說,組成現代CPU的基礎元件的有超大規模的邏輯門,定時器,以及高效能的cache和高精度時鐘。邏輯門提供邏輯仲裁功能,運算器精於各種運算,cache提高資料交換效率。
從軟體上來說,我認為我們的程式就是與電腦尤其是CPU上面的各種寄存器進行資料互動來使用電腦的硬體功能,比如網路通訊,就是我們的程式使用網路介面進行資料互動。或者最經典的C語言列印“hello world”這個程式,簡單說來就是我們的程式控制CPU相關的寄存器在顯存裡面寫入了“hello world”這個字串顯示的相關指令和資料。在這個過程中,軟體控制硬體目的就是,我們的顯示器上面顯示出了“hello world”,而這一過程本身可以抽象為軟體和顯示裝置之間的資料互動。
總得來說,在電腦上,軟體承擔著主動向硬體進行資料互動的任務,而硬體是被動的接收和響應處理這些資料(當然你得按照正確的時序來操作還得保證輸入正確的資料)。那麼有沒有硬體是可以主動去做一些事情呢,當然是有得,在ASIC裡面,都是先通過軟體(現在大部分是FGPA級實現)來進行類比驗證,然後再通過純硬體實現相關功能。
而我們電腦的工作過程,我認為就是軟體操作這些硬體的過程。
至於如何來操作這些東西,我們需要瞭解CPU的寄存器,基礎的彙編指令等。
下面我跟著孟老師的課程,反組譯碼一段C語言代碼來簡單介紹下這個過程和涉及的相關知識。
二,彙編代碼工作過程中堆棧過程的變化
至於如何到實驗樓進行相關實驗,在孟老師的視頻講得非常詳細,這裡略過不表。
我的過程如下:
實驗樓裡面的虛擬機器,使用非常方便。
這個是我們的目標c代碼,很簡單的一段代碼。
編譯命令如下:
1 int g(int x){2 return x + 3;3 }4 int f(int x){5 return g(x);6 }7 int main(void){8 return f(8) + 1;9 }
簡單的C語言代碼
使用如下命令產生彙編代碼:
gcc -S -o main.s main.c -m32
注意:此處-m32參數是反編譯成32位的彙編指令,因為實驗樓所提供的虛擬機器是64位的,所以加上該參數。我實驗了下,如果不加入該參數,所得到的指令會摻雜64位的指令。比如movq和pushq等。
所產生的彙編代碼如下(該代碼已經將一些標誌符等進行了刪減以利於分析):
1 .file "main.c" 2 g: 3 pushl %ebp 4 movl %esp, %ebp 5 movl 8(%ebp), %eax 6 addl $3, %eax 7 popl %ebp 8 ret 9 f:10 pushl %ebp11 movl %esp, %ebp12 subl $4, %esp13 movl 8(%ebp), %eax14 movl %eax, (%esp)15 call g16 leave17 ret18 main:19 pushl %ebp20 movl %esp, %ebp21 subl $4, %esp22 movl $8, (%esp)23 call f24 addl $1, %eax25 leave26 ret
反編譯出來的彙編代碼
下面進入正題。
首先來說下幾個基礎點:
1,幾個寄存器的名稱和作用
- EIP 32位指令寄存器 用於存放下一次需要執行的指令地址,在當前的指令地址被取出之後,會自動的+1
- EBP 擴充基址指標寄存器其記憶體放一個指標,該指標指向系統棧最上面一個棧幀的底部
- ESP 棧指標,用於指向棧的棧頂(下一個壓入棧的活動記錄的頂部),而EBP為幀指標,指向當前活動記錄的底部
- EAX 是一種32位通用寄存器。 EAX寄存器稱為累加器,AX寄存器是算術運算的主要寄存器,所有的輸入、輸出只使用AL或AX人作為資料寄存器
2,函數調用的堆棧是由多個堆棧疊加起來得。我的理解如示,函數example()佔用了如下的一段空間,而它是由函數內的多個棧疊加起來得(由於當初堆棧學得不太好,此處不知道理解得是否正確?):
3,函數的傳回值預設由EAX寄存器儲存返回給上一級的函數。
4,pushl %ebp,將當前ebp壓棧同時esp的值被修改。
5,在程式中,
enter = push %ebp
movl %esp,%ebp
leae = movl %ebp,%esp
popl %ebp
下面看具體分析:
1 .file "main.c" 2 g: ;函數g 3 pushl %ebp ;將ebp壓棧,同時esp的值減去4個位元組,棧向下增長一段 4 movl %esp, %ebp ;採用寄存器定址的方式,將esp的值賦給ebp 5 movl 8(%ebp), %eax ;將ebp變址定址加8,將該地址的棧空間儲存的值賦給eax 6 addl $3, %eax ;將eax裡面儲存的資料加3,即實現 8 + 3的操作,然後將結果賦值給eax,此時eax=11 7 popl %ebp ;將ebp出棧,此時esp加上4個位元組,指向了原先儲存著leavel的棧位置 8 ret ;將eip出棧,回退到上一個跳轉時的位置下一段指令,即f函數中的leave處 9 10 f: ;函數f11 pushl %ebp ;將ebp壓棧,同時esp的值減去4個位元組,棧向下增長一段12 movl %esp, %ebp ;採用寄存器定址的方式,將esp的值賦給ebp13 subl $4, %esp ;將esp的值減4個位元組,即棧向下再增長一段14 movl 8(%ebp), %eax ;將ebp變址定址加8,將該地址的棧空間儲存的值賦給eax15 movl %eax, (%esp) ;將eax的值賦給esp所指向的地址的棧空間16 call g ;跳轉到函數g,執行之後EIP指向leave的值將會儲存到棧中17 leave ;leave指令此時將ebp的值賦值給esp,然後將ebp出棧,然後esp將指向儲存著上一個函數跳轉時的下一段指令,即函數main中的addl處 18 ret ;將eip出棧,回退到上一個跳轉時的位置下一段指令,即main函數中的addl處19 20 main: ;程式的進入點21 pushl %ebp ;將ebp壓棧,同時esp的值減去4個位元組,棧開始向下增長22 movl %esp, %ebp ;採用寄存器定址的方式,將esp的值賦給ebp23 subl $4, %esp ;將esp的值減4個位元組,即棧向下增長一段24 movl $8, (%esp) ;對esp所指向的棧空間賦值為825 call f ;跳轉入函數f,轉向f段代碼,此時EIP指向函數f26 addl $1, %eax ;將eax中的值加1並存入eax,此時eax = 1227 leave ;此時將ebp的值賦值給esp,然後esp將指向棧尾0,然後將ebp出棧,同樣ebp也將指向棧尾028 ret ;將eip出棧,因為沒有下一條指令,程式至此執行完畢。
彙編代碼中堆棧的變化 時間不多了,後續補充一個流程圖來詳細描述該堆棧過程。該篇文章裡面也許會有很多錯誤的地方,尤其最後組譯工具堆棧分析,我應該還沒能完全理解孟老師的意思,懇請各位指正,互相交流學習。
Linux核心分析作業(1)——電腦是如何工作得?