contributed by < atlantis0914
>
queue.[ch]
和連帶的檔案。qtest
的行為和裡頭的技巧此作業主要檢視:
原文提到關鍵字 Explicit memory management,可以參考CMU的投影片:
Dynamic memory allocation Page 4:
節錄如下:
Explicit allocator:應用程式動態allocate記憶體,需自行free。
例: C語言的malloc與free。
Implicit allocator:應用程式動態allocate記憶體,不需自行free。
例: 大部分支援Gargage collection的程式語言如:Java, ML以及Lisp。
Pointer-based data structure:
指的是類似Linked list這類資料型態的實作中,包含了pointers指向相同的型態。
此作業的目標為利用Linked list實作queue,包含基本的如 inserd head, insert tail, remove head以及free之類的操作,queue.h實作了queue與Linked list的資料結構:
示意圖:
一個Linked list組成的queue,包含List head與List tail, queue中的元素(element)以List表示。然而要對此Linked list中的資料作操作與管理的話,需要再實作一層封裝,也就這裡的queue_t
結構。目前queue_t
只有一個成員,為指向List head的指標。
實作下列函式:
q_new
: 新建一個空的queue結構。queue_t
的資料結構成員:q_free
:將指定的queue進行free的動作,包含list中的內容。q_insert_head
: 在queue所指向的List head的位置新增一個List,即新增一個元素至此q_insert_tail
: 在queue所指向的List tail的位置新增一個List,注意此操作必須在的時間複雜度完成。q_remove_head
: 從queue所指向的List head的位置移除一個list。注意此函式的第二個參數若不為NULL pointer的話,需將此list所指向的字串,以第三個參數bufsize
所要求的長度存放置該pointer所指向的位置,以達到pop
的效果。q_size
:計算此queue的總長度。注意操作必須在的時間複雜度完成。q_reverse
:將此queue中所有元素反向排列。注意題目要求不得以宣告額外的記憶體空間。在qtest.c
中使用了signal
來處理process收到特定的訊號時的後續行為,並且搭配setjmp
與longjmp
使得程式碼能從例外(exception)發生後恢復正常運行。
為了理解setjmp
與longjmp
的運作,設計新的實驗並搭配gdb追蹤過程:
仿造qtest.c
中,程式碼處理signal以及setjmp
與longjmp
之間的行為,撰寫jump.c
:
在main
進入時,呼叫signal
使得process收到SIGSEGV時,進入事先準備好的sigsegvhandler
執行。test
函式呼叫sigsetjmp
時,設定好程式碼從siglongjmp
返回的位置。 注意到sigsetjmp
傳入的參數以及回傳值,可以參考man
:
SYNOPSIS
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
DESCRIPTION
The setjmp() function saves various information about the calling environment (typically, the stack pointer, the instruction pointer, possibly the values of other registers and the signal mask) in the buffer env for later use by longjmp(). In this case, setjmp() returns 0.
setjmp
將process當前的stack pointer, instruction pointer以及其他暫存器的內容儲存在env
內。此時setjmp
會回傳0。
再來看longjmp
:
SYNOPSIS
void longjmp(jmp_buf env, int val);
void siglongjmp(sigjmp_buf env, int val);
DESCRIPTION
The longjmp() function uses the information saved in env to transfer control back to the point where setjmp() was called and to restore ("rewind") the stack to its state at the time of the setjmp() call. In addition, and depending on the implementation (see NOTES), the values of some other registers and the process signal mask may be restored to their state at the time of the setjmp() call.
Following a successful longjmp(), execution continues as if setjmp() had returned for a second time. This "fake" return can be distinguished from a true setjmp() call because the "fake" return returns the value provided in val. If the programmer mistakenly passes the value 0 in val, the "fake" return will instead return 1.
longjmp
利用setjmp
已經儲存好的env
內容,回到setjmp
一開始被呼叫的位置,導致setjmp
被執行兩次,而第二次的回傳值取決於longjmp
的第二個參數val
。
實際搭配gdb執行程式碼:
先讓斷點停在test
函式最後的位置,然後執行r
:
可以看到sigsetjmp
第一次被執行後回傳0,所以程式碼印出Got here from function call
。
此時利用gdb發送SIGSEGV給process:
可以看到sigsetjmp
被執行第二次,此時回傳值取決於sigsegvhandler
內的siglongjmp
的參數val
,因此程式碼印出Got here from long jump
。
想要知道sigsetjmp
將哪些內容存放至env
,使用gdb將env
印出。
在此之前先了解一下env的結構,一樣使用gdb:
jmp_buf
是一個指向jmp_buf_tag
的指標型態,成員__jmp_buf
是一個長度為8的陣列,每個元素的型態是long
。推測__jmp_buf
就是man
中說明用來儲存stack pointer, instruction pointer以及其他暫存器內容的變數。
再來看env
的內容:
搭配參照目前的register的內容:
當下列印出來的暫存器的內容並不等於第一次呼叫sigsetjmp
時,存入env
的值,只是希望對jmp_buf
印出的內容能有感覺。
可以看到env
的__jmpbuf[2]
與__jmpbuf[3]
與r12
和r13
一致,至於其他內容呢?
__jmpbuf[1]
與__jmpbuf[6]
的值雖一致,但無法在register列表中找出有關連的,同樣的__jmpbuf[7]
也是一樣的狀況,完全無法得知這些數值的意義。
因此希望觀察程式碼進入sigsetjmp
時,將哪些數值存放到env
,或者對這些數值做了其他操作。
再次透過gdb,斷點訂在test
函式內,呼叫sigsetjmp
的位置,執行r
之後再單步執行進入sigsetjmp
,由於這部份是使用assembly實作,可以使用disas看到sigsetjmp
的內容:
根據x86的calling convention: rdi
, rsi
, rdx
, rcx
, r8
與r9
存放著caller呼叫函式時,傳入的前6個參數,因此rdi
的值就是指向env
的記憶體位址,而從0x00007ffff7a22b87 <+19>
到0x00007ffff7a22b87 <+35>
的程式碼可以推測在做這樣的事:
至於0x00007ffff7a22b83 <+19>: mov %rax,0x8(%rdi)
將什麼樣的數值存放至env[0].__jmpbuf[1]
呢?
觀察0x00007ffff7a22b73 <+3>
到0x00007ffff7a22b73 <+15>
:
可以看到rax
的數值做了xor的加密動作,使用的key為%fs:0x30
內的數值,然後作了做算數位移運算。所以我們從print env
觀察到的數值有部份被加密了。
關於xor加密可以參考:
XOR cipher
只要得知金鑰(key),就可以反向得到原始的數值。
jmp_buf
中的數值再次根據x86的calling convention,可以知道函式被呼叫之前,return address會被push至stack,因此0x00007ffff7a22b97 <+39>
到0x00007ffff7a22b97 <+57>
這段程式碼推測在將return address作加密的動作:
關於x86的calling convention,可以參考CMU的課程講義:
Machine Prog: Procedures
為了驗證推測,重新改寫jump.c
的test
函式:
利用inline assembly仿造原本的加密流程,寫一段解密的程式碼,並將之前推測可能是加密過的return address的數值傳入,可以得到:
從結果發現sigsetjmp
保存的return address為0x5555555547b3
,利用gdb檢查一下:
可以看到0x00005555555547b3 <+45>: test %eax,%eax
剛好是sigsetjmp
保存的return address,因此驗證之前的推測。
inline assembly可以參考:
關於GNU Inline Assembly