實驗四 第三章 程式的機器級表示
本章學習内容是彙編語言,一定要能讀懂
3.1-3.7中練習重點:3.1,3.3,3.5,3.6,3.9,3.14,3.15,3.16,3.22,3.23,3.27,3.29,3.30,3.33,3.34
X86 尋址方式經曆三代:
1 DOS時代的平坦模式,不區分使用者空間和核心空間,很不安全
2 8086的分段模式
3 IA32的帶保護模式的平坦模式
對于機器級程式設計來說,其中兩種抽象尤為重要
1 機器級程式的格式和行為,定義為指令集體系結構(ISA),它定義了處理器狀态,指令的格式,以及每條指令對狀态的影響
2 機器級程式使用的存儲器位址是虛拟位址,提供的存儲器模型看上去是一個非常大的位元組數組
資料格式:由于是從16位體系結構擴充成32位,intel用術語字(word)表示16位資料類型,是以32位為雙字(double words),64位數為4字(quad words)
對于ISA,要有以下基本觀念:IA32的ISA和x86-64的ISA,以及其他大多數ISA,在抽象時都将指令按順序執行抽象。但是處理器的硬體可以并發地執行許多指令,并且采用了一些safeguards來確定并行執行之後的結果和一條一條順序執行的結果一樣
一個IA32 CPU包含一組8個存儲32位值的寄存器,用以存整數資料和指針:eax,ecx,edx,ebx,esi,edi,esp,ebp.大多數情況下前六個都用作通用寄存器,eax,ecx,edx的存儲和恢複慣例不同于ebx,edi,esi(前三者為被調用者儲存,後三者為調用者儲存,詳見3.7.3);最後兩個用于存儲指針,由于在過進行中非常重要,分别指向棧幀的頂部和底部,必須保持
關于ISA的指令:
一是,ISA中每條指令占用位元組數不等,常用的指令所需的位元組數少,不太常用的指令的位元組數多,這樣的話,相對于每條指令占用等長位元組數的ISA,這種占用位元組數不等的ISA為程式産生的總空間要更少。
二是,ISA中的指令在設計時,達到了一個效果,對于一個二進制指令串,從某個位元組開始,譯碼的結果是唯一的,這是達到了編碼理論中“唯一可譯性”的要求。
(我覺得大概正是反彙編器正是利用了唯一可譯性,進而将一串指令位元組序列分割開來的)
IA機器代碼和原始的C代碼差别很大
1 程式計數器(PC)指令将要執行的下一條指令在存儲器中的位址
2 整數寄存器檔案包含8個命名的位置,分别存儲32位的值
3 一些浮點寄存器存放浮點資料
gcc -S xxx.c -o xxx.s 獲得彙編代碼,也可以用objdump -d xxx 反彙編
注意: 64位機器上想要得到32代碼:gcc -m32 -S xxx.c
MAC OS中沒有objdump, 有個基本等價的指令otool
Ubuntu中 gcc -S code.c (不帶-O1) 産生的代碼更接近教材中代碼(删除"."開頭的語句)
二進制檔案可以用od 指令檢視,也可以用gdb的x指令檢視。 有些輸出内容過多,我們可以使用 more或less指令結合管道檢視,也可以使用輸出重定向來檢視
od code.o | more
od code.o > code.txt
當一個源檔案生成了'.o'的目标二進制檔案後,無法直接檢視。
但是還是有個檢視目标代碼檔案内容的方法,就是對'.o'目标檔案使用反彙編器。它的輸出還是二進制檔案,但是,反彙編器将這些二進制按照指令進行了分段。讓我們知道哪一段是一個指令(格式上與彙編器産生的彙編檔案一樣,分行的)
表中不同資料的彙編代碼字尾
C聲明 | Intel資料類型 | 彙編代碼字尾 | 位元組 |
char | b | 1 | |
short | 字 | w | 2 |
int | 雙字 | l | 4 |
long int | |||
long long int | ----- | - | |
char * | |||
float | 單精度 | s | |
double | 雙精度 | 8 | |
long double | 擴充精度 | t | 10/12 |
資料傳送指令有三個變種:movb(傳送位元組)movw(傳送字)movl(傳送雙字)
寄存器:
esi edi可以用來操縱數組
esp ebp用來操縱棧幀
對于寄存器,特别是通用寄存器中的eax,ebx,ecx,edx,要了解32位的eax,16位的ax,8位的ah,al都是獨立的,通過下面例子說明:
假定目前是32位x86機器,eax寄存器的值為0x8226,執行完addw $0x8266, %ax指令後eax的值是多少?
解析:0x8226+0x826=0x1044c, ax是16位寄存器,出現溢出,最高位的1會丢掉,剩下0x44c,不要以為eax是32位的不會發生溢出
操作數的三種類型:立即數、寄存器、存儲器
立即數:即常數值
寄存器:表示某個寄存器内容
存儲器:根據計算出來的位址(通常稱有效位址)通路某個存儲器位置
是以尋址方式也有多種,如:立即數尋址、寄存器尋址、絕對尋址、間接尋址、變址尋址、伸縮化 的變址尋址……
有效位址的計算方式 Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
區分MOV,MOVS,MOVZ三個指令
MOV相當于C語言的指派”=“
MOVS将作了符号擴充的位元組傳送到字
MOVZ将作了零擴充的位元組傳送到字
(不能從記憶體位址直接MOV到另一個記憶體位址,要用寄存器中轉一下)
mov族(mov指令還有很多兄弟指令如movb、movw、movsb、movzb)、pop、push
movsb、movzb分别為符号擴充、零擴充,它們隻拷貝一個位元組,源操作數均為單位元組,并設定目的操作數中其餘的位,效果如下:
初始假設:%dh=8D %eax=98765432
1 movb %dh,%al ;%eax=9876548D
2 movsbl %dh,%eax ;%eax=FFFFFF8D(目的操作數高24位設為源位元組最高位,在這裡為很顯然為1,是以前24位為全F)
3 movzbl %dh,%eax ;%eax=0000008D(目的操作數高24位被設為0)
pushl指令等價于:
subl $4,%esp
movl %ebp,(%esp) //注意這裡的括号引起的差别
popl指令等價于:
movl (%esp),%eax
addl $4,%esp
棧頂元素的位址是所有棧中元素位址中最低的
1.指針其實是位址,間接引用指針就是将該指針放在一個寄存器中 ,然後在間接存儲器引用中引用這個寄存器
2.局部變量通常儲存在寄存器中,而不是存儲器(個人猜測應該是局部變量屬于動态配置設定,局部變量是以被動态置入寄存器,而非存儲器)
例如調用exchange:
int a = 4;
int b = exchange(&a,3);
printf("a=%d,b=%d\n",a,b);
列印出a=3,b=4
(&(取址)建立一個指針,在本例中,該指針指向儲存局部變量a的位置.然後函數exchange用3覆寫存儲在a中的值,但是傳回4作為函數的值)
算術和邏輯操作
移位操作 :sall==shll(填0) sarl(算術右移,填符号位) shrl(邏輯右移,填0)
特殊算術操作:imull(有符号64位乘法) mull(無符号64位乘法) cltd(轉換為四字) idivl(有符号除法) divl(無符号除法)
使用
gcc –S –o main.s main.c -m32
指令編譯成彙編代碼