花了半個多月,補完王爽老師的彙編語言後,跟着CMU的視頻課+課本,學完了第三章的知識,最深的感觸就是CSAPP不管是視頻仍是書的質量都很是的硬,不愧它的盛名。(lab6和課後的家庭做業還沒作,以後再補) 如今來對前面所學作一個總結。(大體按照CMU視頻的順序進行)java
1.使用指令新建、編輯、彙編、連接彙編語言程序 ①新建並編輯源代碼 命令:getdit sum.c 說明:gedit是一個GNOME桌面環境下兼容UTF-8的文本編輯器。使用vi或者vim一樣能夠實現新建與編輯。 ②預處理【sum.c -> sum.i】 命令:gcc -E sum.c -o sum.i 【sum.c -> sum.i】 說明:預處理時,編譯器會將C源代碼中包含的的頭文件編譯進來 ③編譯 【sum.i -> sum.s】 命令:gcc -S sum.i -o sum.s 說明:gcc首先檢查代碼的規範性,是否有語法錯誤,肯定代碼實際要作的工做,讓後將代碼翻譯成彙編語言 ④彙編【sum.s -> sum.o】 命令:gcc -c sum.s -o sum.o 說明:gcc進行彙編階段,將編譯階段生成的」.s」文件轉成二進制目標代碼(可重定位目標文件) ⑤連接【sum.o -> sum】 命令:gcc sum.o -o sum 說明:連接過程將有關的目標文件彼此鏈接起來,使得全部目標文件成爲一個可以執行的統一總體。 ⑥執行 命令:./sum 說明:執行可執行文件,輸出結果算法
2.數據格式 b(byte):字節 w(word):1字=2字節 l(double words):雙字=4字節 q(quad words):四字=8字節 對應的mov指令爲: movb movw movl movqvim
al:1字節 ax:2字節 eax:4字節 rax:8字節c#
3.數據傳送 傳內存數據的格式:
傳地址的格式:
擴展:
擴展分爲零擴展和符號擴展,若要擴展後保持相同的數,對於有符號數,符號擴展(即高位補原來的最高位)能夠保證擴展先後相同,對於無符號數,零擴展能夠保證擴展先後相同。指令,以字節->字爲例,其餘一樣格式。 零擴展:movzbw dl,ax 符號擴展:movsbw dl,ax 另,符號擴展多一個cltq指令,表示將%(eax)符號擴展->rax 另,當給32位寄存器賦值時,總會將高位自動改成0數組
4.寄存器做用性能優化
0-63 | 0-31 | 0-15 | 8-15 | 0-7 | 使用慣例 |
---|---|---|---|---|---|
%rax | %eax | %ax | %ah | %al | 保存返回值 |
%rbx | %ebx | %bx | %bh | %bl | 被調用者保存 |
%rcx |
%ecx | %cx | %ch | %cl | 第4個參數 |
%rdx |
%edx | %dx | %dh | %dl | 第3個參數 |
%rsi |
%esi | %si | 無 | %sil | 第2個參數 |
%rdi |
%edi | %di | 無 | %dil | 第1個參數 |
%rbp | %ebp | %bp | 無 | %bpl | 被調用者保存 |
%rsp | %esp | %sp | 無 | %spl | 棧指針 |
%r8 |
%r8d | %r8w | 無 | %r8b | 第5個參數 |
%r9 |
%r9d | %r9w | 無 | %r9b | 第6個參數 |
%r10 | %r10d | %r10w | 無 | %r10b | 調用者保存 |
%r11 | %r11d | %r11w | 無 | %r11b | 調用者保存 |
%r12 | %r12d | %r12w | 無 | %r12b | 被調用者保存 |
%r13 | %r13d | %r13w | 無 | %r13b | 被調用者保存 |
%r14 | %r14d | %r14w | 無 | %r14b | 被調用者保存 |
%r15 | %r15d | %r15w | 無 | %r15b | 被調用者保存 |
前六個參數分別存在 rdi rsi rdx rcx r8 r9 中,如有更多的參數,則放在棧上,且棧頂爲第7個參數,其次第8個參數... |
|||||
rbx rdx r12~r15 是被調用者保存,在函數體中若是要調用別的函數,能夠把有用的數據放在這些被調用者保存的寄存器中,這樣在被調用的函數中,若是要改變這些寄存器的值,老是會事先入棧保存並在return前彈出。 |
|||||
而調用者保存的寄存器,在調用函數前,不但願被修改的有用數據老是要先保存在棧中,在調用函數後再彈出使用。 |
5.算術和邏輯操做 注意算術右移(SAR)和邏輯右移(SHR),算術右移是有符號數的右移,高位補1,邏輯右移是無符號數的右移,高位補0.markdown
imulq:有符號全乘法 mulq:無符號全乘法 【上面兩條指令都須要一個參數在%rax中】 idivq:有符號除法 divq:無符號除法 乘積存在%rdx(高64位)和%rax(低64位) 商存在%rax中,餘數存在%rdx中。less
question:爲何家庭做業1中的imulq指令乘積存在rdx中...??
編輯器
1.Condition code 條件碼函數
條件碼 | 英文 | 含義 |
---|---|---|
CF | Carry Flag (for unsigned) | 無符號數相加時的進位標記 |
ZF | Zero Flag | 結果是0 |
SF | Sign Flag (for signed) | 符號標記,運算結果最高有效位爲1(負數),則SF置爲1 |
OF | Overflow Flag (for signed) | 溢出標記,表示有符號數的溢出(兩個同符號數相加結果爲不一樣符號,就會有這個溢出) |
lea不會設置這四個標誌位。 | ||
cmpq用於比較大小,testq用於將某個數和0比較 |
CF和OF的區別:
(參考了大佬的blog) ①首先須要知道,計算機對數值的存儲採用補碼形式存儲,一來避免了+0和-0的尷尬,二來數值的加法和減法能夠統一爲補碼的加法。在彙編語言層面,定義變量的時候,沒有 signed和unsignde 之分,彙編器通通將你輸入的整數字面量看成有符號數(最高位的符號位根據輸入的數值符號決定)處理成二進制補碼存入到計算機中,只有這一個標準!彙編器不會區分有符號仍是無符號而後用兩個標準來處理,它通通看成有符號的!而且通通匯編成補碼!
②那麼,有符號和無符號數在計算機中是怎麼區分並對他們的運算採用不一樣的策略呢?有一個重要的點是,補碼是一個強大的設計,其統一了無符號數和有符號數的加法運算是相同的,即從0位到高位一個個相加,且相加的時候再加上從前面的進位。【因此用同一個加法器便可】那麼,無符號數和有符號數在加法運算的時候並不區分,用同一個加法器便可
,只是對結果擁有不一樣的解釋權罷了【可是乘法運算用不了同一套了,能力有限】
③ OF、CF、SF標誌。 先看CF標誌位,書上說CF標誌位只對無符號數有意義,首先明白一點,即便是兩個有符號數相加,也會致使CF的變更,並非說有符號數,編譯器不設置CF位
。由於CF的標誌位的變更是因爲最高有效位(若是對於8位數,就是第8位)向更高位(第9位)產生了進位或者借位而產生,而對於有符號數來講,最高位是符號位,它的變更和數值位的變更意義不同。因此對於有符號數,CF也可能發生變更,可是它的變更是沒意義的。而若是是無符號數,它的變更就意味中8位的內存或寄存器不足以保存數據,由於數據產生了進位或借位。
再看OF標誌位,它只對有符號數有意義,由於兩個標準的8位有符號數據(標準指的是賦值的時候不要賦超過有符號數範圍的數字,因爲截斷,便是8位能保存,保存進來的數據數值大小早就產生了變化),這2個數據只有同號(都爲正或爲負)相加纔會溢出,也就是結果超過有符號數的範圍
。例如2個正數,符號位(第8位)都爲0,相加後發生溢出,符號位因爲第7位的進位變成了1,兩個正數相加變爲了負數?由此對OF產生了做用,如此來講OF的做用是因爲符號位發生變化,若是是兩個無符號數,最高位表明的並非符號意義,產生了變更也是無心義的,因此說OF只對有符號數有意義。 最後SF標誌,有了上面的介紹,就能理解SF看的是最高位的符號位意義,對於無符號數來講,最高位表明的是數值意義,並非符號意義。
④可愛又可怕的c語言。 爲何又扯到 c 了?由於大多數遇到有符號仍是無符號問題的朋友,都是c裏面的 signed 和 unsigned 聲明引發的,那爲何開頭是從彙編講起呢?由於咱們如今用的c編譯器,都是將c語言代碼編譯成彙編語言代碼,而後再用匯編器彙編成機器碼的。搞清楚了彙編,就至關於從根本上明白了c,並且,用機器的思惟去考慮問題,必須用匯編。(我通常遇到什麼奇怪的c語言的問題都是把它編譯成彙編來看。) C 是可愛的,由於c符合kiss 原則,對機器的抽象程度剛恰好,讓咱們即提升了思惟層面(比彙編的機器層面人性化多了),又不至於離機器太遠 (像c# ,Java之類就太遠了)。當初K&R 版的c就是高級一點的彙編……:-) C又是可怕的,由於它把機器層面的全部的東西都反應了出來,像這個有沒有符號的問題就是一例(java就不存在這個問題,由於它被設計成全部的整數都是有符號的)。爲了說明c的可怕特舉一例:
#include <stdio.h>
#include <string.h>
int main() {
int x = 2;
char * str = "abcd";
int y = (x - strlen(str) ) / 2;
//注:原做者這樣寫,編譯器可能會對其優化,直接使用右移移位指令而不是採用除法指令,改爲3便可看到
printf("%d\n",y);
}
複製代碼
結果應該是 -1 可是卻獲得:2147483647 。爲何?由於strlen的返回值,類型是size_t,也就是unsigned int ,與 int 混合計算時,int類型被自動轉換爲unsigned int了,結果天然出乎意料。。。 觀察編譯後的代碼,除法指令爲 div ,意味無符號除法。解決辦法就是強制轉換,變成 int y = (int)(x - strlen(str) ) / 2; 強制向有符號方向轉換(編譯器默認正好相反),這樣一來,除法指令編譯成 idiv 了。咱們知道,就是一樣狀態的兩個內存單位,用有符號處理指令 imul ,idiv 等獲得的結果,與用 無符號處理指令mul,div等獲得的結果,是大相徑庭的!因此牽扯到有符號無符號計算的問題,特別是存在討厭的自動轉換時,要倍加當心!(這裏自動轉換時,不管gcc仍是cl都不提示!!!) 爲了不這些錯誤,建議,凡是在運算的時候,確保你的變量都是 signed 的。
2.Conditional branches 條件分支 注:greater和less是有符號的,above和below是無符號的。
注:原則:老是先判斷!test而後決定是否轉向else
(上面這個是條件控制轉移)
Conditional move條件傳送
注:這是一種分支預測優化技術,基本思想是把then代碼和else都執行獲得兩個結果,而後纔會選擇使用哪個結果,看起來彷佛浪費時間但事實是若是是簡單的計算,會更有效率,學到性能優化時會明白緣由。【這是一種流水線技術,當代碼運行時到達一個分支,他們會試着猜想分支結果,這被稱爲分支預測技術,而且他們很是擅於預測,98%的時候他們都能猜對,因此他們能夠在路上預測suta曲線,並開始朝這個方向前進,只要猜想正確,就會很是有效率,可是若是分支猜錯了,必需要阻止它並轉向另外一個方向從新開始】
3.Loops 循環 do while:
while:
for:
4.switch語句 Switch語句利用了Jump Table跳轉表機制,跳轉表機制避免了須要順序地判斷各個case(O[n])【或者二分算法O(logn)】,而能夠根據偏移直接跳轉到那個代碼塊case(O(1)) 注:switch語句老是用ja (max of x) 先判斷是否爲默認,如果默認的話直接跳轉默認,不是默認的話才利用跳轉表機制,以x的值爲索引去查跳轉表,而後跳到那個跳轉表中存着的分支代碼的地址。
若是不是從0開始的,那麼會給一個偏移量【因此總變成從0開始因此】
若是case值很稀疏,那麼編譯器會優化成if-else語句
棧比較特別,他是自高地址往下生長的。 callq:
1.下條指令的地址入棧 2.rip轉到函數調用的入口處 retq:
(假定棧頂是想要跳轉的地址): push rip 傳遞數據:
rdi rsi rdx rcx r8 r9傳遞數據,多餘的在棧中傳遞(第7個在棧頂)。 這裏的參數要求是整形或指針浮點類型的參數是由另一組單獨的寄存器來傳遞的. 運行方式:
若是是單線程的運行模型Single threaded model,在函數嵌套調用中,會不斷地往壓棧,在任什麼時候刻只有一個函數在運行,在函數返回時,直接能夠根據棧頂的地址返回。因此能夠不斷地調用函數和返回函數來實現複雜的工做。 棧幀:(不肯定對錯)
棧幀是從剛進入這個函數後,return前棧多出的那部分,包括了在該函數中的局部變量,函數再調用其餘函數的返回地址及參數超過6時的傳參 以及其餘。<也就是控制權在該函數時所分配的全部棧內存> 需知但不須要理解:
咱們一般能夠看到,程序常常在棧上分配比實際需求多的空間,這是由於,有一些約定要求內存地址保持對齊,對齊的方式能夠有不少種,這有點含糊不清,但不用擔憂是否會有未使用的空間和函數(目前不須要理解).<或許其中一個緣由是canary金絲雀> rsp與rbp:
在棧中,咱們須要的只是在棧中爲每一個被調用且未返回的過程保留一個棧幀。一般一個棧幀由兩個指針分隔,一是棧指針RSP,一是基指針RBP,但基指針是一個可選項,通常不會使用除了很是特殊的狀況,因此這個寄存器實際上並不會在你的程序中以幀指針的形式出現,它將被用做常規寄存器。
若是是分配固定棧幀的,如這裏分配16,那麼在結尾編譯器會釋放掉這個空間。可是若是是可變長的數組或內存緩衝區
時,編譯器不知道分配多少空間,那麼就會採用基指針來記錄一開始的棧幀,而後在結束後返回時令rps=基指針
被調用者保存:
(rbp,rbx,r12~r13),例如A調用B,若是B可能會改變A中的這些寄存器的話,那麼在B中會先入棧。【幫調用者擦屁股】 這裏有一點困惑,之因此常函數或者常量參數儘可能加const的緣由,應該就是爲了能讓編譯器知道是不會改變的,就免去了保存的過程,嗎???
調用者保存:
(除了上面那些寄存器)例如A調用B,若是調用B可能會改變這些寄存器的值,那就先在A調用B前先存起來【本身的東西本身保護好】
今天先到這裏,明天再續。
內容:
內容: