contributed by < 0n3t04ll
>
說明
在 queue.h 中看到 queue structure 的註解有說要加入其他 fields 來更有效率的實作 q_size(), q_insert_tail() ,在此我添加了 tail pointer 指向 queue 的尾端,用 size 在新增刪除中更新 queue 的大小,考慮到 size 應該不會出現負數,我用 unsigned int 來保存 data 。
透過 malloc 要一個 queue structure 的空間並初始化,需要判斷 malloc 成功與否。
用一個 element pointer curr 來遍歷整個 queue。
用另一個 element pointer tmp 來接收準備要被 free 掉的 element ,在 curr 更新後再 free 掉 tmp
要確認 newh, value 都有 malloc 成功,失敗就要 free 掉之前 malloc 的空間。
以 strlen(), strcpy() 來存資料,新增的同時也要更新 q->size ,由於增加了 tail pointer ,所以我透過 q->size 判斷是否要更新 q->tail
。
先判斷 newh, value 是否 malloc 成功,其中一個失敗就要 free 掉先前 malloc 的。
insert tail 的時候要特別注意 tail, head 的更新。
先判斷 queue 的 pointer 和 size,確保不為 NULL 或 empty。
用 tmp pointer 接收原本的 head 再對 queue 做更新,再判斷 sp 地址的有無來決定要不要複製 head->value ,最後 free 掉 tmp。
由於在增減 queue 的過程就在維護 queue 的 size ,故在此只需回傳 q->size 即可
reverse 的部份我是以三個 pointer: prev, curr, last 來遍歷整個queue ,每經過一個 element 就修正 next pointer ,使其指回 prev ,最後更新這三個 pointer 。
head, tail 的更新是當所有 element reverse 後將 head assign 給 tail , last assign 給 head。
之前有用過 valgrind 一個一個測試過 qtest ,大致上跟 Naetw 說的一樣,會在13、14的時候壞掉,不過那時有看到 Naetw 跟老師說會 PR ,所以我就乖乖的等更新。
實際跑過一次是沒有問題的,只不過在測試13、14、15的時後跑的特別慢,讓我一度以為壞掉。
「路見不平,拿 patch 來填」,請見 PR #5
jserv
修改了 Makefile 中的 CFLAGS ,添加 sanitizer 參數,若有發生 UAF, heap overflow, memory leak 就會報錯。
參數如下:
因為沒有問題,所以什麼都沒顯示。
qtest.c main function 中的 while loop 主要是用來接收外部傳進來的參數,像是 verbose level, testcase file 等等,在此先忽略避免程式區塊過大。
後續主要程式如下:
queue_init() 負責初始化 queue pointer 為 NULL 和註冊 sigsegv, sigalrm 的 handler 。
init_cmd() 用 add_cmd() 函數把跟 queue 無關的 cmd 用 cmd_ptr 保存並串成 linked list 。
之所以用 linked list ,個人猜測是為了之後修改做的彈性設定。
下面是 cmd 的 structure :
而 console_init() 用了一樣的方式串連跟 queue 相關的 cmd 成 linked list 。
真正處理 cmd 的輸入和操作是在 cmd_select() 內部 ,可以分為從檔案讀取 cmd 或是從 stdin 讀取。
讀取後後進入 interpret_cmd ,在裡面做 parse 接著傳給 interpret_cmda() 操作該 cmd ,方法是遍歷 cmd_list 並用 strcmp() 比對 cmd name 並且用該 cmd structure 中的 function ptr 執行 cmd 。
cmd 操作都是透過 function ptr ,而在我比對所有的 cmd 後發現執行自己寫的 queue 操作前都會有一段應該跟 exception handler 相關的 statement ,以 do_new() 的 13~15 為例:
從名稱上來看應該是 exception 的前置作業,進入後發現用了 sigsetjmp ,該 function 從 manual 來看是用來保存 stack context/ environment ,好在處理 interrupt 或 error 後可以繼續執行。
而專司處理 signal 的兩個 handler 內部都是執行 harness.c 中的 trigger expression
其中的 siglongjmp() 根據 manual 描述為回復在 sigsetjmp 中保存的 env 好返回先前執行狀態以便繼續執行,達到 exception handler 的效果。
因為對 signal 處理過程不熟,所以決定親自用 gdb 跟蹤一次。
若用單純的下斷點對 signal 是行不通的,必須要用 handle 處理,根據這份manual,必須用
讓該 sigsegv pass 到 handler 才能繼續,不然 gdb 只會卡死在發生 sigsegv 的指令上。
發現過程如下:
這邊我是參考 0xff77 同學的描述
至此完成了一個 exception handler 的過程。
在跟進 harness.c 後,發現裡面有個比較特別的 test_malloc, test_free ,在 harness.h 找了一下後發現程式測試的 malloc, free 會被 test_malloc, test_free 取代:
下面為 test_malloc 程式碼,我把程式碼分為數個片段搭配文字解釋。
line 122 中用 noallocate_mode 來決定要不要 malloc 。
比較有趣的是 fail_allocation() ,用隨機的方式判斷 malloc 有沒有成功,我以前都是直接測完全失敗或完全成功,用隨機的方式進行測試應該是模擬 malloc 突然壞掉的情形下有無做好檢查。
malloc 出來的 chunk 還要再包一層 block_ele_t , block_ele_t 在實作上用到了之前在 C_and_CPP 板上看到的一篇討論提過的技巧,透過 zero length array 可以動態改變 struct 空間,這邊因為 user 要求的空間不固定但又想包在同個 struct 所以用這種方法。
malloc 出來的 block 會拆分為三個部分:原本放 data 的空間、額外的 block metadata 空間、 footer 空間。
從下面程式碼可以看出 qtest 透過 double linked list 維護 malloc 出來的 block。
除了自己用 double linked list 維護 block 外, test_malloc 還在 block 的 header, footer 加上 MAGICHEADER, MAGICFOOTER 兩個常數,猜測是為了要檢查有無 heap overflow 的發生。
test_free() 對 test_malloc() 出來的 block 做操作,開頭先判斷有無設定 noallocate_mode 和 p 的合法性。
透過找出 block footer 位置並且比對 MAGICFOOTER 有無被修改來確認是否發生 heap overflow ,驗證了上面的猜想。這種設計理念跟 canary 如出一轍。
在 header, footer 的地方放上 MAGICFREE ,在 data 的地方填上 FILLCHAR ,表示該 block 已經被 free 掉。把要 free 的 block 從 double linked list 中 unlink 掉後再將之 free 。
test_free(), test_malloc() 之所以用額外的 double linked list 做串連是為了在測試結束後,透過遍歷 block list 評估 leak memory 的情形。這點在 do_free() 中的 allocation_check() 得到解答。
從 Makefile 的 test 可以發現用來自動評測的方式是透過 python script 實現。
主要的評測 class 為 Tracer ,而評測主要的 method 為 Tracer.run() 。
scoreDict 用字典保存 testcase 和相對應的得分, tid 顧名思義為 testcase 的 id 。
從 self.traceDict[t] 取出 testcase name 輸出測是訊息,接著把從 tidList 取出的 t 傳入 self.runTrace() ,裡面會透過 subprocess 執行:
subprocess 的回傳值再傳回給 Tracer.run ,並將字串格式化輸出檔名、得分、總分。然後把該項得分加總至 score 當作最後的總分。