2017-2018-1 20155226《信息安全系統設計基礎》第13周學習總結
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 返回結果
註意:
64位機器上想要得到32代碼:
gcc -m32 -S xxx.c
- Ubuntu中
gcc -S code.c
(不帶-O1)產生的代碼更接近教材中代碼(刪除"."開頭的語句) - 找到程序的字節表示:
(gdb) x/17xb sum
二進制文件可以用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 操縱棧幀
尋址方式
- 根據操作數的不同類型,尋址方式可分為以下三種:
- 立即數尋址方式:操作數為常數值,寫作$後加一個整數。
- 寄存器尋址方式:操作數為某個寄存器中的內容。
- 存儲器尋址方式:根據計算出來的地址訪問某個存儲器的位置。
尋址模式:一個立即數偏移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:溢出標誌,最近操作導致一個補碼溢出-正溢出或負溢出。
訪問條件碼的讀取方式
- 根據條件碼的某個組合,將一個字節設置成0或1;
- 跳轉到程序某個其他的部分;
- 有條件的傳送數據。
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循環
C語言中do-while語句的通用形式:
do body-statement while(test-expr);
匯編結構:
loop: body-statement t=test-expr; if(t) goto loop;
- while循環
C語言中while語句的通用形式:
while(test-expr) body-statement
匯編結構:
t=test-expr; if(!t) goto done; loop: body-statement t=test-expr; if(t) goto loop; done:
- for循環
C語言中for語句的通用形式:
for(init-expr;test-expr;update-expr) body-statement
匯編結構
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周學習總結