20179215《Linux內核原理與分析》第二周作業
這一周主要了解了計算機是如何工作的,包括現在存儲程序計算機的工作模型、X86匯編指令包括幾種內存地址的尋址方式和push、pop、call、re等幾個重要的匯編指令。主要分為兩部分進行這周的學習總結。第一部分對學習內容進行總結,第二部分對實驗進行分析(反匯編一個C程序)。
一、學習內容
1、現在計算機絕大多數采用馮諾依曼體系結構,邏輯上可以抽象成:

以程序員的角度看:

2、幾種尋址方式
? movl %eax,%edx edx=eax 寄存器尋址
? movl \(0x123,%edx edx=0x123 立即尋址 ###? movl 0x123,%edx edx=*(int32_t)0x123 直接尋址 ###? movl (%ebx),%edx edx=(int32_t)ebx 間接尋址 ###? movl 4(%ebx),%edx edx=(int32_t)(ebx+4) 變址尋址 ####其中%..代表寄存器,\)..代表取出數據,(%..)代表取出寄存器中所存儲的數據,b、w、l、q分別代表8位、16位、32位和64位。
3、push、pop、call等幾個重要的匯編指令。
eg (1) pushl %eax 意思是把eax寄存器壓棧。分解來看相當于第一步:subl $4 esp,第二步:movl %eax (%esp),來解釋一下:首先我們要知道esp是指堆棧棧頂,那么由于棧的一般生長方向為自上向下增長,進行push壓棧指令時棧頂指針向下移動4個字節(因為是32位機),之后把eax放入當前內存位置。
(2) popl %eax意思是把eax寄存器出棧。分解來看相當于第一步: movl(%esp), %eax第二步:add \(4 %esp,道理同(1)逆。 ###(3) call 0X12345 意思是函數調用,過程是首先將當前CPU獲取內存的指令壓棧保存,賦予新值,CPU下次就從新地址來取指令了,即實現了函數調用。 #二、實驗分析 ##首先創建main.c文件,即:touch main.c,之后用如下命令輸入一段C語言代碼: ###\)vi main.c,之后用gedit main.c 查看編寫好的C程序,如下圖所示:

然后用如下命令反匯編:$gcc -S -o main.s main.c -m32,之后用gedit main.c 查看結果,如下圖所示:




由于“.”開頭的大都是用于鏈接輔助信息,實際并不會執行,所以可以直接忽略。刪除所有點開頭的內容,留下來的是純匯編代碼。那么此段程序簡化后就變成如下形式:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $20, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $10, (%esp)
call f
addl $30, %eax
分析如下圖:


Eip寄存器 從這條指令執行完自動執行下有一條指令
Ebp寄存器 總是指向堆棧的棧底 概念的是相對的,跳出該函數進入其他函數堆棧有其相應的棧底
Esp寄存器 總是指向堆棧的棧頂
Eax寄存器 函數的返回值默認使用該寄存器存儲返回給上一級函數
分析如下:
程序從main函數開始運行,因為之前還有別的代碼運行,所以把當前ebp進行壓棧,之后指針向下一個字節,相當于在下個字節內填充數據,之后調用f函數,而當前eip指向call f的下一條指令(eip在進行call f指令后壓棧,下回跳轉到eip繼續執行)。那么對于即將要執行的f函數來說首先要將當前ebp壓棧,那么和main函數類似,指針下移一個字節,把10放進寄存器eax中,再把寄存器eax給esp,當前eip指向call g的下一條指令(當前eip在進行call g指令后壓棧),之后調用g函數,同樣先將ebp壓棧,之后esp,ebp指向同一個位置,之后ebp指針向上移動兩個字節把當前字節內內容放到eax寄存器中,將eax儲存的數加20后出棧,返回到f函數的leave命令,撤銷函數堆棧后返回到main函數的leave命令,將eax的當前值30加30,之后撤銷函數堆棧,返回程序運行值。