程序的機器級表示

1, 操作數提示符:

三種:

立即數:常數值,  在ATT格式的彙編代碼中, 書寫格式是  $ + 整數, 如:$-123   $0X12

寄存器:如:32位的%eax   16位的%ax  8位的%al

存儲器引用:

Imm(Eb)   表示地址爲:Eb+Imm    

Imm(Eb, Ei)   地址爲:Eb(基址)+ Ei(變址) + Imm(立即數偏移), 

Imm(Eb, Ei, s)  地址爲: Eb + Ei * s + Imm


2, 字節傳送指令:

mov  同等傳送, 即倆者的大小一致 (如:movb byte,  movew   word, movel  longword(DW))

movs/ movz   不同等傳送, 即倆者的大小可以不一致, 如:movsbl 表示將源byte傳送到目的longword, 用1 或0補充剩餘位(取決於源byte的最高位是否爲1), movzbl 與movsbl相似, 用0補充剩餘位

push & pop



3, 特殊指令:

lea:  直接上例:

 lea  4(%eax) %ebx   //表示 %ebx 裏面的值爲%eax + 4 。       
而 mov 4(%eax) %ebx  //表示 %ebx裏面的值爲【eax+4】這個地址的值

其實, 複雜的來說, lea 表示獲取源地址的偏移地址, 對於上例來說, 偏移地址當然是%eax + 4 咯。




int fun (unsigned x) 
{
    int val = 0;
    while (x) {
         val ^= x;
         x >>= 1;
    }
    return val & 0X1;
}
這個函數是測試x二進制裏面1的個數的奇偶, 奇返回1, 偶返回0;

理解, 第一着重於移位操作, 每次移位  都使前一位移動至後一位(即使得n位與n-1位之間的運算成爲可能), 第二 着重於異或操作,對於1bit ^ 1bit來說, 恰好是查看這倆者的1的總個數是否爲偶,    第三着重於第一位,  每次移位操作  都使得前一位都能跟當前第一位進行異或操作, 而這一位的結果是這一位上的1的個數是否爲偶。loop, 測試前前一位。。。。。 again and again  until it ends.



int fun (unsigned x) {
     int val = 0;
     int i;
     for (i = 0; i< 32; i++) {
           val = (val <<1) | (x & 0x1);
           x >>= 1;
     }
     return val;
}
這是獲取x的位反轉製造出來的鏡像



4, switch指令較條件跳轉的優越性

條件跳轉, 在asm中, 是以類似cmp   +   jne    等指令實現的。 對於N種情況來說, 就意味着要N對cmp-jne這些指令。

switch對於條件轉換來說, 是利用了跳轉表來進行的(其實就是地址偏移),例如情況爲100-106, 即只需要計算當前值-100 的大小(k), 跳轉到jt【k】所指的位置即可。

其實這個也說明了某種問題, 也就是對於疏散的情況集合, 對於跳轉表的使用會不會添加空白無用的區域浪費呢?

to be continued.......


5, Union的作用:

類似struct可以存放數據, 不同在於, Union所有數據共用內存, 即Union的大小爲數據中大小最大的, 而不是大小之和。

用處: 類型轉換。。。注意小端法讀取數據


6, 爲什麼要數據對齊:

條件:1,平臺獲取數據都是以固定字節數獲取的。 2,有些數據不規則,如:struct{int a, char c;}A;   其標準大小是5Bytes

導致的情況: 比如固定字節數爲8, 不對齊數據就意味着第一次讀取, 獲得  A1 以及A2的部分數據, 爲了讀取A2的值, 必須讀取兩次,以及需要進行高低位的修補。。。。

所以數據對齊, 是一種以空間換時間的策略。

這也是爲什麼   sizeof(A)!= 5  而是8(test in VS2013)。

測試:

struct A{
int a;
char c;
int b;
};
void testSizeOf()
{
	printf("size of A is %d\n", sizeof(A));

	A k = { 1, 'c', 2 };
	printf("A:\na: %d\nc: %c\nb: %d\n\n", k.a, k.c, k.b);
	printf("A:\na: %x\nc: %x\nb: %x\n\n", k, k, k);         //其中%x表示十六進制整數。   科普:\x 表示其後面兩位爲十六進制數,常用於printf cout等。
}

結果爲:
size of A is 12
A:
a: 1
c:  c
b: 2

A:
a:  1
c:   cccccc63         // 63是 'c'的十六進制ASCII字符表示形式
b:  2


表明:

1, 對於結構體k來說, 執行printf多次輸出,其過程類似於文件流讀取(讀即前進)。

2, 結構體A中, c佔用的字節爲4, 其中多餘3字節作爲補充。


注:char *c 對象大小爲4,  因爲裏面裝的是地址。。。。。       void *v 大小亦是4,同理。


這也表明了數據位置排列的重要性。一般來說  應按照從大到小排列。 因爲這樣可以縮小對齊要求的字節數,如4->>2。(???若是從小到大, 會怎樣?   會呵呵。。。)



7, 理解指針:

1) 指針指向函數:

int func(int a, int *b){...}

typedef int (*fp) (int a, int *b);

fp = func;
int result = fp(a, &b);
其實函數調用call也是移動到某一指令, 跟指針有差別嗎?

2)指針跟數組無異:

int a[n],      *(a+1) = a [1];

3) 改變類型後  尋址的區別:

char *cp;
(int*)cp + 7  => (cp, 4, 7)的位置
(int*)(cp+7) =>  (cp, 1, 7)的位置


8, 存儲器的越界引用以及緩衝區溢出:

常見的做法是 類似於char chs[8], strcpy(chs, "1234...........");導致緩衝區溢出, 覆蓋其他地址的值(被保存的寄存器的值, 以及返回地址)

防範:

1, 棧隨機化:  即同一個程序, 運行時的棧的位置不固定。
2, 棧破壞檢測: 即在保存寄存器附近放置哨兵值, 程序運行後檢查哨兵值是否發生改變, 若有, 立即終止程序。當然  這意味着哨兵值必須保存倆處, 而另一處唯一的保障是,只讀,不能被修改, 似乎有點薄弱。。。。
3, 設置不能執行區域機制, 限制可執行區域。

總結(自我感覺):
1, 每次調用其他函數是, 會將參數列表反序壓棧 + 返回地址。   接着是%ebp  %ebx(或許這個跟參數有關, 因爲每次push ebx之後在函數結尾都需要pop ebx 意味着ebx是一個需要保存的值, 不能改變, 這個豈不是傳值參數傳遞不影響參數的值的依據?)     如果此時有申請緩衝區(char buffer【8】)則在此後面開闢一個*8(8byte)的空間,(其序號排列順序是從自棧頂到棧底升序) 如果對這個緩衝區進行strcpy等操作, 就有可能修改到ebp ebx等  甚至是返回地址。
2, 在有保護的代碼中, 常常是局部變量比buffer更靠近棧頂, 這樣buffer溢出,不會破壞局部變量的值。


注: sizeof(“1234”)is 5; because of ‘\0’(0x00);   但是strlen返回的是4, 故在malloc的調用時, 應該malloc ( strlen(buf)+1 ).  記得要對返回值進行NULL判斷。


9. AGE OF 64:

特殊數字: 0x101010101010101        72340172838076673


發佈了64 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章