1. 程式人生 > >2017-2018-1 20155226《信息安全系統設計基礎》第13周學習總結

2017-2018-1 20155226《信息安全系統設計基礎》第13周學習總結

miss 補碼 模式 3.1 返回值 test 結束 fgets clas

2017-2018-1 20155226《信息安全系統設計基礎》第13周學習總結

教材學習內容深度學習

章節練習題

3.58

long decode2(long x, long y, long z)
{
  int result = x * (y - z);
  if((y - z) & 1)
    result = ~result;
  return result;
}

3.60

A. x : %rdi n : %esi result : %rax mask : %rdx

B. result = 0 mask = 1

C. mask != 0

D. mask >>= n

E. result |= (x & mask)

long loop(long x, int n)
{
    long result = 0;
    long mask;
    for(mask = 1; mask != 0; mask >>= n)
    {
        result |= (x & mask);
    }
    return result;
}

3.61

long cread_alt(long *xp)
{
    static long tmp = 0;
    if(xp == 0)
    {
        xp = &tmp;
    }
    return *xp;
}

這個地方也是很無語,在我的環境下必須將tmp的存儲類型設置為靜態存儲,並且將gcc的優化設置為O3,這樣才能生成使用conditional transfer的指令(才能讓gcc相信優化是值得的。。):

00000000004004f0 <cread_alt>:
  4004f0:   48 85 ff                test   %rdi,%rdi
  4004f3:   b8 38 10 60 00          mov    $0x601038,%eax
  4004f8:   48 0f 44 f8             cmove  %rax,%rdi
  4004fc:   48 8b 07                mov    (%rdi),%rax
  4004ff:   c3                      retq   

3.62

typedef enum {MODE_A, MODE_B, MODE_C, MODE_D, MODE_E}
long switch3(long *p1, long *p2, mode_t action)
{
  long result = 0;
  switch(action)
  {
    case MODE_A:
      result = *p2;
      *p2 = *p1;
      break;
      
    case MODE_B:
      result = *p1 + *p2;
      *p1 = result;
      break;
      
    case MODE_C:
      *p1 = 59;
      result = *p2;
      break;
      
    case MODE_D:
      *p1 = *p2;
      
    case MODE_E:
      result = 27;
      break;
      
    default:
      result = 12;
  }
  return result;
}

3.63

long switch_prob(long x, long n)
{
  long result = x;
  switch(n)
  {
    case 0:
    case 2:
      result += 8;
      break;
    case 3:
      result >>= 3;
      break;
    case 4:
      result = (result << 4) - x;
    case 5:
      result *= result;
    default:
      result += 0x4b;
  }
  return result;
}

3.64

A. &A[i][j][k] = Xa + L(iST + j*T + k)

B. R = 7,S = 5,T = 13

3.65

A. rdx (每次移位8,即按行移動)

B. rax(每次移位120 = 8 * 15,按列移動)

C. 由B,M = 15

3.66

NR(n)是數組的行數,所以我們找循環的次數,即rdi,得到rdi = 3n.

NC(n)是數組的列數,所以我們應該找每次循環更新時對指針增加的值,這個值等於sizeof(long) * NC(n),即r8,得到r8 = 8 * (4n + 1).

綜上,可知兩個宏定義:

#define NR(n) (3*(n))
#define NC(n) (4*(n)+1)

3.67

A.

技術分享圖片

B. %rsp + 64

C. 通過以%rsp作為基地址,偏移8、16、24來獲取strA s的內容(由於中間夾了一個返回地址,所以都要加8)

D. 通過傳進來的參數%rdi(%rsp + 64 + 8),以此作為基地址,偏移8、16、24來寫入strB r

E.

技術分享圖片

F. 我記得我在看《C語言程序設計: 現代方法 2rd》的時候,裏面說傳遞聚合類型的變量可以使用指針,這樣比傳遞整個數據結構要快一些(當然寫操作會改變實參)。這個題目裏面也都是讀操作,可以發現編譯器自動進行了優化——傳遞了基地址而非復制了整個數據結構。返回就是在調用它的函數的棧幀中存入一個相關的數據結構。(這個題裏面process其實沒有棧幀,如果返回地址算eval的話)3.68

這題考察的是內存對齊。通過結構體成員的位置逐漸縮小範圍:

    int t 為8(%rsi),所以4<B<=8
    long u 為32(%rsi),所以24 < 8 + 4 + 2*a <= 32,得到6<A<=10
    long y 為184(%rdi),所以176 < 4*A*B <= 184,得44 < A*B <=46。

所以AB = 45 或者AB = 46,結合A, B各自的範圍,只可能為A = 9, B = 5.

3.69

A. 根據第4、5行的指令, idx的值為(bp + 40i + 8),由第1、2行指令,這裏的8是因為第一個int first整數和內存對齊的原因,所以每一個a_struct的大小為40字節。

由於0x120 - 0x8 = 280字節,所以CNT = 280/40 = 7.

B. 由第6、7行指令知,idx和x數組內元素都是signed long類型的。由於整個a_struct數據類型大小為40字節,所以其內部應該為85 = 8 + 84:

typedef struct
{
    long idx;
    long x[4];
}

3.70

A.

e1.p : 0

e1.y : 8

e2.x : 0

e2.next : 8

B. 16 bytes

C.

void proc(union ele *up)
{
    up->x = *(up->e2.next->e1.p) - up->e2.next->e1.y;
}

3.71

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE_OF_BUFFER 10

int good_echo(void)
{
    char *buffer = calloc(SIZE_OF_BUFFER, sizeof(char));
    if (buffer == NULL)
    {
        fprintf(stderr, "Error: failed to allocate buffer.\n");
        return -1;
    }
    while(1)
    {
        fgets(buffer, SIZE_OF_BUFFER, stdin);
        if (strlen(buffer) == SIZE_OF_BUFFER-1) /*兩種情況,一種是剛好輸入了能填滿緩沖區的字符數,另一種是大於緩沖區,一次不能讀完*/
        {
            fputs(buffer, stdout);
            if (buffer[SIZE_OF_BUFFER-1-1] == ‘\n‘)/*剛好輸入了能填滿緩沖區的字符數,結束讀入*/
            {
                break;
            }
            memset(buffer, 0, strlen(buffer));/*清空緩沖區,因為要通過strlen判斷讀入了多少字符,繼續讀入*/
        }
        else if (strlen(buffer) < SIZE_OF_BUFFER-1)/*一定是最後一次讀入,結束讀入*/
        {
            fputs(buffer, stdout);
            break;
        }
        else
        {
            break;
        }
    }
    free(buffer);
    return 0;
}

int main(int argc, char const *argv[])
{
    return good_echo();
}

3.72

A. andq $-16, X這條指令相當於將低4位置零,也就是使得rax中保存的8n+30對16取整。所以s2-s1為8n+30對16取整的結果。

B. p的值為rsp(r8)-15對16取整的結果,確保了p數組的起始地址為16的整數倍。

C. 8n + 30對16取整有兩種可能:一種是8n本身就是16的整數倍即n = 2k,此時取整後為8n+16; 另一種是8n = 16k + 8即n = 2k + 1,此時取整後為8n + 24。由System V AMD64 ABI標準可知,s1的地址為16的整數倍(即結尾為0000),所以s2的地址也肯定是16的整數倍(結尾為0000)。又因p是由s2減15對16取整得到的結果,所以p和s2之間肯定相差2字節,即e2 = 2 bytes. 所以e1最大為(n為奇數) :8n + 24 - 16 - 8n = 8 bit, 最小為(n為偶數):8n + 16 -16 - 8n = 0.(這個題我估計沒有考慮到ABI標準對於棧幀對齊的問題,s1的地址本來就應該是16的整數倍)

D. 由A B C可知,這種方法保證了s2 和 p的起始地址為16的整數倍,而且保證了e1最小為8n,能夠存儲p數組。

浮點數部分並未測試

3.73

find_range:
    vxorps %xmm1, %xmm1, %xmm1
    vucomiss %xmm0, %xmm1
    ja .L5
    jp .L8
    movl $1, %eax
    je .L3
    .L8:
    seta %al
    movzbl %al, %eax
    addl $2, %eax
    ret
    .L5:
    movl $0, %eax
    .L3:
    rep;ret

3.74

find_range:
    vxorps %xmm1, %xmm1, %xmm1
    vucomiss %xmm0, %xmm1
    cmova $0, %eax
    cmove $1, %eax
    cmovb $2, %eax
    cmovp $3, %eax
    rep;ret

3.75

A. 每一個復數變量使用兩個%xmm寄存器傳送。

B. 通過%xmm0和%xmm1返回一個復數類型值。

教材學習內容總結

3.1歷史觀點

  • X86 尋址方式經歷三代:

    DOS時代的平坦模式,不區分用戶空間和內核空間,很不安全
    8086的分段模式
    IA32的帶保護模式的平坦模式
  • Linux使用平坦尋址方式,使程序員將整個存儲空間看做一個大的字節數組。

    3.2程序編碼

  • ISA:指令集體系結構,機器級程序的格式和行為,它定義了處理器狀態、指令的格式以及每條指令對狀態的影響。
  • 程序計數器(通常稱為PC,用%eip表示),指示將要執行的下一條指令在存儲器中的地址。
  • 整數寄存器文件:存儲地(對應於C語言的指針)或整數數據。
    條件碼寄存器:保存著最近執行的算數或邏輯指令的狀態信息,用來實現控制或者數據流中的條件變化。
  • 浮點寄存器:用來存放浮點數據。

    編譯過程:

  • C預處理器插入宏和頭文件:gcc -E xxx.c -o xxx.i
  • 編譯器產生源代碼的匯編代碼:gcc -S xxx.i -o xxx.s
  • 匯編器化成二進制目標代碼:gcc -c xxx.s -o xxx.o
  • 鏈接器生成最終可執行文件:gcc xxx. -o xxx
  • objdump -d xxx.o -o反匯編
  • 建立函數調用棧幀的匯編代碼:

    pushl   %ebp 將寄存器%ebp中的內容壓入程序棧
    movl    %esp,%ebp  將%ebp中的內容放入寄存器%esp
    ......
    popl    %ebp
    寄存器%ebp中內容出棧
    ret 返回結果

    註意:

  1. 64位機器上想要得到32代碼:gcc -m32 -S xxx.c

  2. Ubuntu中 gcc -S code.c (不帶-O1)產生的代碼更接近教材中代碼(刪除"."開頭的語句)
  3. 找到程序的字節表示:(gdb) x/17xb sum
  4. 二進制文件可以用od命令查看,也可以用gdb的x命令查看。有些輸出內容過多,我們可以使用 more或less命令結合管道查看,也可以使用輸出重定向來查看od code.o | more od code.o > code.txt

3.4訪問信息

寄存器

一個IA32中央處理單元(CPU)包含一組8個存儲32位值的寄存器。用來存儲整數數據和指針。

%eax    %ax (%ah %al)  通用寄存器
%ecx    %cx (%ch %cl)  通用寄存器
%edx    %dx  (%dh %dl)   通用寄存器
%ebx    %bx  (%bh %bl)   通用寄存器
%esi    %si             用來操縱數組
%edi    %di             用來操縱數組
%esp    %sp             操縱棧幀
%ebp    %bp             操縱棧幀
尋址方式
  • 根據操作數的不同類型,尋址方式可分為以下三種:
  1. 立即數尋址方式:操作數為常數值,寫作$後加一個整數。
  2. 寄存器尋址方式:操作數為某個寄存器中的內容。
  3. 存儲器尋址方式:根據計算出來的地址訪問某個存儲器的位置。
  • 尋址模式:一個立即數偏移Imm,一個基址寄存器Eb,一個變址寄存器Ei,一個比例因子s(必須為1,2,4,8)有效地址計算為:Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s

    數據傳送指令
  • MOV相當於C語言的賦值‘=‘
  • mov S,D S中的字節傳送到D中

3.6控制

條件碼

描述最近的算數或者邏輯操作的屬性,可以檢測這些寄存器來執行條件分支指令。

  • CF:進位標誌,最近操作使高位產生進位,用來檢測無符號操作數的溢出
  • ZF:零標誌,最近操作得出的結果為0
  • SF:符號標誌,最近操作得到的結果為負數
  • OF:溢出標誌,最近操作導致一個補碼溢出-正溢出或負溢出。
訪問條件碼的讀取方式
  1. 根據條件碼的某個組合,將一個字節設置成0或1;
  2. 跳轉到程序某個其他的部分;
  3. 有條件的傳送數據。
  • SET指令根據t=a-b的結果設置條件碼

    跳轉指令及其編碼
  • 控制中最核心的是跳轉語句:

        有條件跳轉(實現if,switch,while,for)

    無條件跳轉jmp(實現goto)

  • 當執行PC相關的尋址時,程序計數器的值是跳轉指令後面那條指令的地址,而不是跳轉指令本身的地址。

    翻譯條件分支
  • 將條件和表達式從C語言翻譯成機器代碼,最常用的方式是結合有條件和無條件跳轉。
  • C語言中if-else語句的通用形式:

    if(test-expr)
    then-statement
    else
    else-statement
    匯編結構:
    t=test-expr;
    if!(t)
    goto false;
    then-statement
    goto done;
    false:
    else-statement
    done:
    循環
  • do-while循環
  1. C語言中do-while語句的通用形式:

    do
    body-statement
    while(test-expr);
  2. 匯編結構:

    loop:
    body-statement
    t=test-expr;
    if(t)
    goto loop;
  • while循環
  1. C語言中while語句的通用形式:

    while(test-expr)
    body-statement
  2. 匯編結構:

      t=test-expr;
    if(!t)
    goto done;
    loop:
    body-statement
    t=test-expr;    
    if(t)
    goto loop;
    done:
  • for循環
  1. C語言中for語句的通用形式:

    for(init-expr;test-expr;update-expr)
    body-statement
  2. 匯編結構

    init-expr
    t=test-expr;
    if(!t)
    goto done;
    loop:
    body-statement
    update-expr;
    t=test-expr;
    if(t)
    goto loop;
    done:

3.7過程

  • 數據傳遞、局部變量的分配和釋放通過操縱程序棧來實現。

    棧幀結構
  • 為單個過程分配的棧叫做棧幀,寄存器%ebp為幀指針,而寄存器指針%esp為棧指針,程序執行時棧指針移動,大多數信息的訪問都是相對於幀指針。
  • 棧向低地址方向增長,而棧指針%esp指向棧頂元素。

    轉移控制
  • call:目標是指明被調用過程起始的指令地址,效果是將返回地址入棧,並跳轉到被調用過程的起始處。
  • ret:從棧中彈出地址,並跳轉到這個位置。
  • 函數返回值存在%eax中

    寄存器使用慣例
  • 程序寄存器是唯一能被所有過程共享的資源,調用者保存寄存器 和 被調用者保存寄存器是分開的,對於哪一個寄存器保存函數調用過程中的返回值要有統一的約定。

教材及課堂學習和總結

  • 問題和解決方法已在測試中列出

代碼托管

嘗試一下記錄「計劃學習時間」和「實際學習時間」,到期末看能不能改進自己的計劃能力。這個工作學習中很重要,也很有用。
耗時估計的公式
:Y=X+X/N ,Y=X-X/N,訓練次數多了,X、Y就接近了。

參考:軟件工程軟件的估計為什麽這麽難,軟件工程 估計方法

  • 計劃學習時間:25小時

  • 實際學習時間:20小時

(有空多看看現代軟件工程 課件
軟件工程師能力自我評價表)

參考資料

  • 《深入理解計算機系統V3》學習指導
  • ...

2017-2018-1 20155226《信息安全系統設計基礎》第13周學習總結