2019q1 Homework1 (lab0)
contributed by <f26401004
>
尚未完成:
- trace harness.h / harness.c
- qtest 機制分析
目標
- 實作簡單的 queue data structure,並提供 insert head / insert tail / remove head 等操作
- 題目中特別限制部份程式碼須在特定的時間複雜度下
- 練習 github 以及 git hook
程式碼
queue_t *q_new()
- 該 function 建立一個 queue
- 需要小心的是當 malloc 不成功時需要 return null 以及 malloc 成功後須重設 queue 的 member
void q_free(queue_t *q)
- 在 free 一個 queue 時需要注意:因為每個 entry 的 value 當初也是被 malloc 的,因此也需要特別去 free 掉,否則會產生 dangling pointer (最一開始在寫的時候忘記 free 掉,因此在 make test 時會產生有 block 仍然存在的 error)
- 更多閱讀:dangling pointer
bool q_insert_head(queue_t *q, char *s)
- 在 insert head 時需要注意因為本次的 malloc 了兩次記憶體,因此不論在哪個階段 malloc 都需要檢查該 malloc 是否成功,不成功則需要將上一個階段的 malloc 成功的記憶體 free 掉並且 return false
- 若 insert head 時發現 head 和 tail 皆為 null 時則初始將 head 和 tail 皆設為新的 entry
bool q_insert_tail(queue_t *q, char *s)
- 因為 insert tail 也要求時間複雜度只能為 O(1),故在 queue 的 data structure 新增 tail pointer link,每次 insert tail 時同時更新最新的 tail pointer link。有了 tail pointer link 便可以和 insert head 相同使用 O(1) 時間複雜度插入
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
- 題目要求當 sp 不為 NULL 時便將正要移除的 entry value 複製到 sp 中
- 然而一開始使用 strncpy 時根據原始碼的實作會發現若 from 的長度大於 to 時會自動 expend to 的長度,在某些情況會造成效率低落。(當要複製的 string 長度遠小於指定的長度時)
- 此外strncpy 不能保證 to 存在 \0,因此必須手動填上 \0 在 to 的尾巴確保存在 null character
- 另一種的作法可以使用 strlcpy,它可以確保 to 的結尾存在 null character,查閱 C89/C99/C11 pdf 後發現並不存在 strlcpy,我的電腦系統為 Unbuntu 18.04LTS 需要額外安裝相關 package 才可以使用。
$ sudo apt-get install libbsd-dev
- 引用
#include <bsd/string.h>
- 或者使用 memcpy 直接複製指定的記憶體區段,但同樣的他也不能保證 null character,因此也需要額外填上 \0 在 to 的尾巴。
- [更多閱讀]:strncpy source code (linux v5.0-rc7)
- [更多閱讀]:memcpy source code (linux v5.0-rc7)
避免說「此函式似乎沒有被納入 standard ANSI C」這種話,工程人員的論述要清晰,要指出 C89/C99/C11 是否存在 strlcpy,以及 POSIX 相關規範 (包含 4BSD) 是否有 conformance 描述
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
jserv
int q_size(queue_t *q)
void q_reverse(queue_t *q)
- 利用 3 個 pointer 來 traversal 整個 queue,過程如下:
- 初始化 next / current 為 head,prev 為 null
- 只要 current 不為 null 則繼續進行下面的步驟
- next 移往下一個 entry
- 將 current->next 設置為 prev
- 設置 prev 為 current、current = next
- 回到第 2 步驟
- 需要特別注意的是別忘記回來重設 head 和 tail pointer
討論
1. 自動評分系統運作原理
首先可以先查看 makefile 中的 test,其對應行為為執行了一段 python 程式碼
若有安裝 python 環境也可以在我們 repo 檔案路徑下執行下面程式碼驅動
而裡面定義了一個 Tracer class ,其中在這個 class 讀入了每個 trace 的檔案以及會 output trace 過程
而最主要執行每次驗證的結果是在 Tracer 中的 runTrace:
由上面程式碼的第 9 行可以看到他會產生 subprocess 去執行 traces 資料夾底下的 cmd,在裡面定義了許多執行 qtest 的指令,再將執行的結果抓出來計算分數。
而值得注意的是,因為跑 subprocess,因此實際上如果真的 malloc 失敗或 free 失敗,如果程式寫的嚴謹可以跑完 process,程式寫的不嚴謹可能會造成程式錯誤而令 process 中止,但是不論哪一種因為在 c 裡面沒有類似 c++ 的 try catch 語法,因此實際上我們的 malloc 以及 free 的 function 都經過改寫,塞入了錯誤訊息的紀錄程式碼,如此一來我們便可以測試自己的程式邏輯是否正確。(not sure)
malloc 與 free 的改寫被定義在 harness.h / harness.c 中,利用 define 將 test_malloc 定義為 malloc、 test_free 定義為 free
harness.c / harness.h
在 harness.c / harness.h 中實際上自行定義了一個 double linked list 來完成我們在 qtest 的操作
其中可以發現每個 entry 的 data structure 存在一個 magic_header field ,他的作用是在於每次 insert 新的 entry 時會同時紀錄當前 queue 的 magic_header,以下程式碼節錄自 harness.c 的 test_malloc(size_t size)
如此一來在之後進行 free operation 時便可以檢查是否 free 到 unallocated block
而 payload_size 則紀錄了要 malloc 記憶體的大小;payload 則是模擬實際獲得的記憶體空間,他會在 test_malloc 時順便全部被初始化為 0x55
不過我發現 MAGICHEADER 都會被初始化為 0xdeadbeef,不知道是不是故意的XD
後來我去查了一下發現有一種專有名詞 Hexspeak 就是只這種將 16 進位表達成英文單詞,不同處理器、作業系統會有不同的意義的樣子
TODO: 解讀這裡 magic number 存在的必要,以及找出類似追蹤記憶體使用而出現的 magie number 設計
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
jserv
接著為了確保 queue 的執行,在 harness 中也特別定義
allocated 會紀錄最近一次 malloc 的 block_ele_t pointer,而 allocated_count 則會紀錄目前 allocated block_ele_t 數
而在 harness.c 中透過下面這兩個函式檢查每次 free 一個 pointer 是否合法
- 因為 test_malloc 會回傳的是 payload 的指標,因此當使用者需要 free 一個空間的時候也會把 payload 的指標作為參數丟給 test_free
- find_head 中首先直接用數學運算得到該 payload 的 block_ele_t pointer,接著自 allocated 開始尋找是否存在相同的 block_ele_t pointer,如果不存在即紀錄錯誤訊息並報錯
- 而 find_footer 則可以用來檢查每次 test_malloc 會特別註記在每個 block_ele_t 尾巴的 MAGICFOOTER
2. qtest 的行為以及裡面的技巧
簡單繪製了一下各個 .c / .h 檔被引用的狀況
[額外筆記] 為什麼 strlcpy 被視為不安全的操作?
像是 strlcpy 以及 strlcat,兩者皆沒有被納入標準的 ANSI C 中,根據 ANSI C maintainer Ulrich Drepper 認為這兩個 function 是不安全的,一旦使用者使用了此兩個 function 很可能會造成一些 truncation error 被忽視,導致未來可能會產生更多的 bug 需要被處理。
Ulrich Drepper 建議使用 mempcpy,利用回傳得到的最後一個 character pointer 直接寫入該 pointer value 為 \0
*((char *) mempcpy (dst, src, n)) = '\0';
(雖然 mempcpy 也沒有被納入標準 ANSI C 中)
另外還有一個議題是因為沒有被納入標準的 ANSI C,因此實際上不同的 OS 的 implementation 也不相同。因此可能會導致在不同作業系統編譯出來的執行檔執行結果錯誤。
參考資料
- bootlin Elixir Cross Referencer
- C Programming/C Reference/nonstandard/strlcpy