Lecture 05 Machine Level Programming I Basics 機器級別的程序
文章目錄
- Lecture 05 Machine Level Programming I Basics 機器級別的程序
- intel 處理器的歷史和體系結構
- 芯片的構成
- AMD 公司(Advanced Micro Devices,先進的微型設備)
- C, 匯編, 機器代碼
- 定義
- 匯編/機器代碼
- C程序轉換為目標代碼
- 編譯為匯編代碼
- 匯編的特性:數據類型
- 匯編的特性:操作
- 機器指令解析示例
- 反匯編代碼
- 反匯編器 objdump
- 反匯編 gdb
- 匯編基礎:寄存器,操作數,移動
- 寄存器
- 移動數據 mov
- 簡單的內存地址模式
- 地址模式示例
- 實際中交換方法
- 完整的內存地址模式
- 地址計算指令 `lea`
- 示例
- 算術運算 和 邏輯運算
- 示例
- 《深入理解計算機系統》書籍學習筆記
intel 處理器的歷史和體系結構
- 復雜指令集電腦(complex instruction set computer)
- 精簡指令集電腦 Reduced Instruction Set Computers(RISC)
芯片的構成
broadwell 型號模型:
- 一個芯片有多個內核。
- 芯片的邊緣有許多接口連接其余的設備。
- DDR是連接到主存儲器的方式,即所謂的DRAM 動態 RAM。
- PCI 是與外圍設備的連接。
- SATA 是與不同類型盤的連接。
- 以太網接口,連接到一個網絡。
因此,所有集成到單個芯片上的不僅僅是處理器本身,而是很多邏輯單元粘在一起所組成的更大的系統。
AMD 公司(Advanced Micro Devices,先進的微型設備)
緊隨Intel公司的后面,相對落后一點,但是價格便宜。
C, 匯編, 機器代碼
定義
- 架構(ISA: Instruction set architecture, 指令集架構)
需要理解或編寫匯編/機器代碼的處理器設計部分。
指令和指令集:這是編譯器的目標,為你提供一系列指令,告訴機器確切地做什么。
發明硬件地人們想到了各種巧妙地實施指令方式,其中一些非常快,但需要大量地硬件,有些很慢,但根本不需要太多硬件。因此他們設法創建了這種稱為指令集架構地抽象。
編譯器地目標就是他們。
而如何最好地實現它是硬件研究者地工作。
-
微架構
對架構的補充。
低級別地東西,如何實現它被稱為微結構 -
代碼形式
- 機器代碼
處理器執行的字節級程序。 - 匯編代碼
機器代碼的文本表示形式。
- 機器代碼
一些指令架構集:
* intel: x86,IA32, Itaniu, x86-64.
* ARM (Acorn RISC Machine,橡樹種子精簡指令機器)
ARM指令體系結構。
他們向公司出售使用其涉及的許可權力,他們真正賣的是知識產權而不是芯片。
匯編/機器代碼
處理器部分:
-
PC: Program counter 程序計數器
存儲下一條指令的地址。 -
Register file 寄存器文件,寄存器集
大量使用的程序文件 -
Condition Code 條件碼
狀態寄存器。
存儲最近的算術或者邏輯運算的結果狀態:產生的值為0?為正值或者負值?
用于實現條件分支
存儲部分:
- Memory 內存
字節可尋址數組
代碼和用戶數據
用于支持程序的堆棧
內存是你可以邏輯地認為只是一個字節數組,這就是機器程序員所看到的。
如前所述,它實際上是一種用不同方式實現虛構對象,操作系統和硬件之間存在一種協作,他們稱之為虛擬內存,使處理器上運行的每個程序看起來擁有自己獨立的字節數組,它們可以訪問。即使它們實際上在物理內存內部都是共享這些字節數組。
C程序轉換為目標代碼
你有一個程序,是c程序,包含多個文件,將使用一些庫代碼。
編譯過程:將你寫的代碼內容,轉換為機器代碼,并將其與編譯后的,編譯器為庫生成合并代碼,最終生成一個文件,可執行文件。
步驟:
- 文本形式的c程序文件,通過編譯器生成文本形式的匯編代碼
- 匯編代碼,通過匯編器生成二進制的目標程序(字節形式)
- 通過鏈接器,將不同的文件融合在一起,包含你單獨的文件,已編譯版本和庫代碼,最終生成一個可執行程序。
- 實際有一些庫在程序首次開始執行時動態導入的。
匯編器:
- 將
.s
匯編文件轉換為.o
目標文件 - 二進制編碼指令
- 幾乎完整的可執行代碼映像
- 缺少不同文件中代碼之間的鏈接(鏈接器來完成)
鏈接器:
- 解決文件之間的引用
- 與靜態運行時(run-time)庫結合使用,例如:malloc(),printf()等
- 一些庫是動態鏈接的。當程序開始執行時鏈接。
編譯為匯編代碼
- c編碼
long plus(long x, long y);void sumstore(long x, long y, long *dest)
{long t = plus(x,y);*dest = t;
}
- 匯編碼
運行命令,生成匯編代碼:gcc -Og -S sum.c
-Og: O optimize 優化。指定編譯器做什么樣的優化的規范。
如果不給它指示,它將生成完全未經過優化的代碼,實際上很難讀該代碼,它的運行過程非常繁瑣。
-O1: 這是過去打開優化器的過程,gcc 做了很多優化,為了優化目的,使代碼很難理解。
因此,最近幾代GCC中的一個出現這個名未g的調式級別
.file "sum.c".text.globl sumstore.type sumstore, @function
sumstore:
.LFB0:.cfi_startprocpushq %rbx.cfi_def_cfa_offset 16.cfi_offset 3, -16movq %rdx, %rbxcall plusmovq %rax, (%rbx)popq %rbx.cfi_def_cfa_offset 8ret.cfi_endproc
.LFE0:.size sumstore, .-sumstore.ident "GCC: (GNU) 8.5.0 20210514 (Red Hat 8.5.0-15.0.2)".section .note.GNU-stack,"",@progbits
以句點開頭的.
,這些實際上指示它們是別的東西,它們與某些被需要的信息有關,要給調試器提供,使他能夠定位程序的各個部分,一些信息告訴鏈接器,這是一個全局定義的函數,還有一些其他信息,我們暫時不需要考慮,忽視這些信息,是它們更具有可讀性。
百分號前綴%: 寄存器名稱
pushq: 將東西推到棧上。
movq: 將它從一個地方復制到另一個地方。
call:調用一些過程
popq: 和pushq相對的命令,從棧中取出東西。
ret:特定函數的返回。
每一行都是一個指令(用文本寫的),每條都將變成目標代碼文件中的一個實際指令。
匯編的特性:數據類型
-
整型數據類型:1,2,4,8 字節
在整數數據類型,它們不區分符號與無符號的存儲方式。
地址和指針,都是以數字形式存儲在計算機中。 -
浮點數數據類型:4,8,10 字節
-
代碼;一系列指令編碼的字節序列
-
沒有聚合類型:數組和結構體
只是在內存中巧妙地分配了字節
匯編的特性:操作
-
實現算術運算方法通過寄存器和內存數據
-
在內存和寄存器之間轉換數據
- 從內存中將數據加載到寄存器
- 將寄存器的數據存儲到內存
-
轉移控制
- 非條件跳轉 到/從 過程
- 條件分支
機器指令解析示例
- c代碼
將t的值存存儲到dest指定的位置。
*dest = t
- 匯編代碼
movq %rax, (%rbx)
移動8字節值到內存:4字
操作數:
* t: 寄存器 %rax* dest: 寄存器 %rbx* *dest: 內存 M[%rbx]
- 目標代碼
3 字節指令。
0x40059e: 48 89 03
存儲地址 0x40059e
拓展:
變量的所有名稱,在匯編代碼級別,機器代碼級別完全丟失,東西都變成了寄存器和內存中的某個位置。
反匯編代碼
先生成目標代碼:gcc -Og sum.c -c
反匯編器 objdump
objdump -d sum.o
- 用于檢查目標代碼
- 分析一系列指令的代碼
- 產生匯編代碼的進士索引
- 可以在a.out(可執行文件) 或者 .o(目標文件)運行
反匯編得到的匯編代碼:
0000000000000000 <sumstore>:0: 53 push %rbx1: 48 89 d3 mov %rdx,%rbx4: e8 00 00 00 00 callq 9 <sumstore+0x9>9: 48 89 03 mov %rax,(%rbx)c: 5b pop %rbxd: c3 retq
反匯編 gdb
gdb 是一個非常強大的調試程序。
你可以單步檢查程序并對其中的程序進行一些操作,如果它的源代碼可用,可以用它來調試。
安裝gdb:
yum install gdb
調試程序:
gdb sum
disassemble sumstore
gdb 作用:
- 可以單步檢查程序并對其中的程序進行一些操作。(源代碼調試)
- 可以用來反匯編
反匯編是一種可以用作任何逆向工程工具的工具。
反匯編Microsoft Word:
Microsoft Word 和其他程序一樣,只是一個可執行文件,而那個可執行文件只是一堆編碼指令的字節。
如果你能找到文件位置,應用程序的實際可執行文件的位置。
objdump -d WINWORD.EXE
匯編基礎:寄存器,操作數,移動
寄存器
如果使用的是%r 開頭的寄存器,你會得到64位。
如果使用的是 %e 開頭的寄存器版本,你會得到32位。
%e 版本指示較大%r 實體低32位。
實際用法更多,你也可以引用低階16位,和低8位。
從IA32 到 x86-64的變化之一是將寄存器數量增加一倍。
移動數據 mov
指令:
movq Source, Dest
操作數類型:
-
立即數 Immediate: 整型常數
示例:$0x400, $-533
和C常量,但是以$
為前綴
編碼1,2,4字節 -
寄存器 Register :16個寄存器中的一個
示例:%rax, %r13
%rsp
保留自己的特殊用途
其他的寄存器有特殊用途對于特殊指令。 -
內存 Memory: 在寄存器給出的地址上有8個連續字節的內存
最簡單的示例:(%rax)
各種其他“地址模式”
注意事項:
- 將立即值作為目的地沒有意義,它是常數
- 出于硬件設計者的方便,它不允許你直接從一個內存位置復制到另一個內存位置。
你需要兩個指令,一個從內存中讀取值,將其復制到寄存器。第二個是在寄存器中取值并將其寫入內存。
q: quad 四字節
簡單的內存地址模式
- 正常模式
(R) Mem[Reg[R]]
寄存器R指定內存地址。
示例:
movq (%rcx), $rax
- 位移模式(Displacement)
D(R) Mem(Reg[R] + D)
寄存器R指定內存區域開始的位置。
常量位移D指出偏移量。
示例:
movq 8(%rbp), %rdx
地址模式示例
- c語言代碼
void swap(long *xp, long*yp)
{long t0 = *xp;long t1 = *yp;*xp = t1;*yp = t0;
}
- 匯編代碼
運行命令:gcc -S -Og swap.c
swap:movq (%rdi), %rax # t0 = *xpmovq (%rsi), %rdx # t1 = *ypmovq %rdx, (%rdi) # *xp = t1movq %rax, (%rsi) # *yp = t0ret
寄存器對應的值:
- %rdi xp
- %rsi yp
- %rax t0
- %rdx t1
操作流程:
實際中交換方法
實際中我們只會使用中間變量來進行交換:
void swap(long *xp, long*yp)
{long t0 = *xp;*xp = *yp;*yp = t0;
}
我們用命令得到匯編代碼,會發現和上面的匯編代碼是一樣的。
為什么?
回到前面我們所說的mov指令。它不允許你直接從一個內存位置復制到另一個內存位置。
你需要兩個指令,一個從內存中讀取值,將其復制到寄存器。第二個是在寄存器中取值并將其寫入內存。
所以 *xp = *yp
的執行就是:
long t1 = *yp
*xp = t1
所以實際中我們這么只使用一個中間變量進行操作,和使用兩個中間變量進行操作并沒有多大影響。
只是代碼更簡潔一點而已。
完整的內存地址模式
- 常用形式
D(Rb, Ri, S) Mem[Reg[Rb] + S*Reg[Ri]+D]
D: Displacement, 位移。恒定位移 1,2,4 字節
Rb: Base Register, 基礎寄存器。 16個寄存器中的一個。
Ri: Index Register, 索引寄存器。特別是%rsp。
S: Scale, 縮放。 1,2,4,8 固定是這些數。
這是實現數組索引的一種自然方式。
如果這是一組數組索引,我們必須通過我的數據類型的字節數來縮放索引值,如果它是一個int我們必須將索引縮放四倍,如果它是long,我們必須將其縮放八倍。(這就是S必須是1,2,4,8這些數)
- 特殊形式
缺失其中一些項。
(Rb,Ri) Mem[Reg[Rb]+Reg[Ri]] D(Rb,Ri) Mem[Reg[Rb]+Reg[Ri]+D] (Rb,Ri,S) Mem[Reg[Rb]+S*Reg[Ri]]
示例:
0xf000 = 1111 0000 0000 0000
2 * 0xf000 = 二進制左移1位 = 0001 1110 0000 0000 0000 = 0x1e000
2 * 0xf000 = 2 * 15 = 30 = 0x1e000
地址計算指令 lea
lea : load effective address, 加載有效地址。
對上面內存地址模式的運用。
leaq Src, Dst
Src : 地址模式表達式
Dst : 設置dst為用表達式表示的地址
使用:
-
計算沒有內存引用的地址
例如:p = &x[i]
-
計算算術表達式: x + k*y
k = 1,2,4,或8
示例
- c 代碼
long m12(long x)
{return x*12;
}
- 匯編碼
教程中得到:
leaq (%rdi,%rdi,2), %rax # t <- x+x*2
salq $2, %rax # return t<<2
我得到:
leaq (%rdi,%rdi,2), %rdx
leaq 0(,%rdx,4), %rax
算術運算 和 邏輯運算
- 兩個操作數的指令
格式 計算
addq Src,Dest Dest = Dest + Src
subq Src,Dest Dest = Dest - Src
imulq Src,Dest Dest = Dest * Src
salq Src,Dest Dest = Dest << Src
sarq Src,Dest Dest = Dest >> Src
shrq Src,Dest Dest = Dest >> Src
xorq Src,Dest Dest = Dest ^ Src
andq Src,Dest Dest = Dest & Src
orq Src,Dest Dest = Dest | Src
- 一個操作數的指令
incq Dest Dest = Dest + 1
decq Dest Dest = Dest - 1
negq Dest Dest = - Dest
notq Dest Dest = ~Dest
注意事項:
- 操作數的順序與你期望他們的順序相反,源操作數在前,目的操作數在后面。
示例
- c代碼
long arith(long x, long y, long z)
{long t1 = x+y;long t2 = z+t1;long t3 = x+4;long t4 = y * 48;long t5 = t3 + t4;long rval = t2 * t5;return rval;
}
- 匯編代碼
leaq (%rdi,%rsi), %rax # t1
addq %rdx, %rax # t2
leaq (%rsi,%rsi,2), %rdx # 3y
salq $4, %rdx # t4 = 4 * 3y
leaq 4(%rdi,%rdx), %rcx # t5
imulq %rcx, %rax # rval
- 寄存器對應的變量
%rdi x
%rsi y
%rdx z
%rax t1,t2, rval
%rdx t4
%rcx t5
《深入理解計算機系統》書籍學習筆記
《深入理解計算機系統》學習筆記 - 第一課 - 課程簡介
《深入理解計算機系統》學習筆記 - 第二課 - 位,字節和整型
《深入理解計算機系統》學習筆記 - 第三課 - 位,字節和整型
《深入理解計算機系統》學習筆記 - 第四課 - 浮點數
《深入理解計算機系統》學習筆記 - 第四課 - 機器級別的程序