--- tags: linux2021-hw --- # 2021q1 Homework1 (lab0) contributed by < `YoLinTsai` > ## :penguin: 作業要求 - [x] 在 GitHub 上 fork [lab0-c](https://github.com/sysprog21/lab0-c) - [x] 參閱 [Git 教學和 GitHub 設定指引](https://hackmd.io/@sysprog/git-with-github) ^附教學影片^ - [x] ==詳細閱讀 [C Programming Lab](https://www.cs.cmu.edu/afs/cs/academic/class/15213-s20/www/labs/cprogramminglab.pdf)== ,依據指示著手修改 `queue.[ch]` 和連帶的檔案,測試後用 Git 管理各項修改,記得也該實作 `q_sort` 函式。 - [x] 在提交程式變更前,務必詳閱 [如何寫好 Git Commit Message](https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/) - [x] 不用理會 [Autolab](http://www.autolabproject.com/) 和檔案下載的描述,這兩者都是 CMU 專屬 - [x] 除了修改程式,也要編輯「[作業區](https://hackmd.io/@sysprog/linux2021-homework1)」,增添開發紀錄和 GitHub 連結,除了提及你如何逐步達到自動評分程式的要求外,共筆也要涵蓋以下: - [x] 開啟 [Address Sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer),修正 `qtest` 執行過程中的錯誤 - [x] 先執行 `qtest` 再於命令提示列輸入 `help` 命令,會使 開啟 [Address Sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) 觸發錯誤,應予以排除 - [ ] 運用 Valgrind 排除 `qtest` 實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的實驗 - [ ] 在 `qtest` 中實作 [coroutine](https://en.wikipedia.org/wiki/Coroutine),並提供新的命令 `web`,提供 web 伺服器功能,注意: web 伺服器運作過程中,`qtest` 仍可接受其他命令 - [ ] 可嘗試整合 [tiny-web-server](https://github.com/7890/tiny-web-server),將 `FORK_COUNT` 變更為 `0`,並以 [coroutine](https://en.wikipedia.org/wiki/Coroutine) 取代原本的 fork 系統呼叫 - [ ] 解釋 [select](http://man7.org/linux/man-pages/man2/select.2.html) 系統呼叫在本程式的使用方式,並分析 [console.c](https://github.com/sysprog21/lab0-c/blob/master/console.c) 的實作,說明其中運用 CS:APP [RIO 套件](http://csapp.cs.cmu.edu/2e/ch10-preview.pdf) 的原理和考量點。可對照參考 [CS:APP 第 10 章重點提示](https://hackmd.io/@sysprog/H1TtmVTTz) :arrow_forward: 為避免「舉燭」,請比照 `qtest` 實作,撰寫獨立的終端機命令解譯器 (可建立新的 GitHub repository) - [ ] 說明 [antirez/linenoise](https://github.com/antirez/linenoise) 的運作原理,注意到 [termios](http://man7.org/linux/man-pages/man3/termios.3.html) 的運用 - [ ] 研讀論文 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf),解釋本程式的 "simulation" 模式是如何透過以實驗而非理論分析,達到驗證時間複雜度,需要解釋 [Student's t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) 及程式實作的原理。注意:現有實作存在若干致命缺陷,請討論並提出解決方案; - [ ] 指出現有程式的缺陷 (提示: 和 [RIO 套件](http://csapp.cs.cmu.edu/2e/ch10-preview.pdf) 有關),嘗試強化並提交 pull request * 截止日期: * Mar 7, 2021 (含) 之前 * 越早在 GitHub 上有動態、越早接受 code review,評分越高 ## 修改 queue.c * **q_new** * 回傳q之前先檢查malloc的回傳值 ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (!q) { return NULL; } q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` * **q_free** * 先檢查q是否合法,依序 free linked list 中的 elements ```cpp= void q_free(queue_t *q) { if (q) { list_ele_t *newh; while (q->head) { newh = q->head->next; free(q->head->value); free(q->head); q->head = newh; } free(q); } return; } ``` * **q_insert_head** * malloc 一個新的 element 放到 q 的最前面,即: * ```new_e->next = q->head;``` * 把 q 中的變數妥當 maintain (size, head, tail) ```cpp= bool q_insert_head(queue_t *q, char *s) { list_ele_t *newh; if (!q) { return false; } newh = malloc(sizeof(list_ele_t)); if (!newh) { return false; } newh->value = malloc((strlen(s) + 1) * sizeof(char)); if (!newh->value) { free(newh); return false; } memset(newh->value, 0, strlen(s) + 1); snprintf(newh->value, strlen(s) + 1, "%s", s); newh->next = q->head; q->head = newh; q->size++; if (q->size == 1) { q->tail = newh; } return true; } ``` * **q_insert_tail** * 和 inset_head 概念大同小異,在 q 最後面再加一個element: * ```q->tail->next = new_e;``` * 特別處理 ```q->size = 0``` 的 case ```cpp= bool q_insert_tail(queue_t *q, char *s) { if (!q) { return false; } list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (!newt) { return false; } newt->value = malloc((strlen(s) + 1) * sizeof(char)); if (!newt->value) { free(newt); return false; } memset(newt->value, 0, strlen(s) + 1); snprintf(newt->value, strlen(s) + 1, "%s", s); newt->next = NULL; q->size++; if (q->size == 1) { q->tail = newt; q->head = q->tail; } else { q->tail->next = newt; q->tail = newt; } return true; } ``` * **q_remove_head** * 先判斷 q 是否可以再拔 element (```q->size```是否為0) * strncpy? ```cpp= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (!q || q->size == 0) { return false; } list_ele_t *rmh = q->head; q->head = q->head->next; bool ret = false; if (sp) { strncpy(sp, rmh->value, bufsize - 1); sp[bufsize - 1] = '\0'; ret = true; } free(rmh->value); free(rmh); q->size--; return ret; } ``` * **q_size** * 檢查 q 是否為 NULL 後回傳 ```q->size``` ```cpp= int q_size(queue_t *q) { if (!q) { return 0; } return q->size; } ``` * **q_reverse** * 當 q 中的 element 數量小於等於1時不用反轉後沒有差別 * 我的想法是把每個 element 的 next 指向前一個 element,總共使用三個變數 cur_e、 next_e 和 tmp <br> * 這是一開始的狀態 (將 tail->next 指到 head) ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; a:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false]; b:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false]; head -> a; tail -> c; } ``` * reverse a->next ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; cur [label="{ <data> cur}"]; NULL [label="{ <data> NULL }"]; a:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false, color=grey]; b:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false]; head -> a; tail -> c; cur->a; a:ref:c -> NULL:data [arrowtail=dot, dir=both, tailclip=false, color=red]; } ``` * reverse b->next ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; cur [label="{ <data> cur}"]; NULL [label="{ <data> NULL }"]; head -> a; tail -> c; cur->b; a:ref:c -> NULL:data [arrowtail=dot, dir=both, tailclip=false]; b:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false, color=grey]; b:ref:c -> a:data [arrowtail=dot, dir=both, tailclip=false, color=red]; } ``` * reverse c->next ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; cur [label="{ <data> cur}"]; NULL [label="{ <data> NULL }"]; head -> a; tail -> c; cur->c; a:ref:c -> NULL:data [arrowtail=dot, dir=both, tailclip=false]; b:ref:c -> a:data [arrowtail=dot, dir=both, tailclip=false]; c:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false, color=red]; } ``` * maintain attribute (head, tail) ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; c [label="{ <data> c | <ref> }"]; b [label="{ <data> b | <ref> }"]; a [label="{ <data> a | <ref> }"]; NULL [label="{ <data> NULL }"]; a:ref:c -> NULL:data [arrowtail=dot, dir=both, tailclip=false]; b:ref:c -> a:data [arrowtail=dot, dir=both, tailclip=false]; c:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false]; head -> c[color=red]; tail -> a[color=red]; } ``` ```cpp= void q_reverse(queue_t *q) { if (!q || !q->head || q->size <= 1 ) { return; } list_ele_t *tmp = q->head->next; q->tail->next = q->head; while (tmp != q->tail) { q->head->next = tmp->next; tmp->next = q->tail->next; q->tail->next = tmp; tmp = q->head->next; } q->tail = q->head; q->tail->next = NULL; q->head = tmp; return; } ``` * **q_sort** ```cpp= list_ele_t *merge(list_ele_t *l1, list_ele_t *l2) { if (!l1) { return l2; } if (!l2) { return l1; } list_ele_t *head = NULL; list_ele_t *tmp = NULL; if (strcmp(l1->value, l2->value) <= 0) { head = l1; l1 = l1->next; } else { head = l2; l2 = l2->next; } tmp = head; while (l1 && l2) { if (strcmp(l1->value, l2->value) <= 0) { tmp->next = l1; l1 = l1->next; } else { tmp->next = l2; l2 = l2->next; } tmp = tmp->next; } while (l1 || l2) { if (l1) { tmp->next = l1; l1 = l1->next; } else { tmp->next = l2; l2 = l2->next; } tmp = tmp->next; } return head; } list_ele_t *merge_sort(list_ele_t *head) { if (!head || !head->next) { return head; } list_ele_t *fast = head->next; list_ele_t *slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } fast = slow->next; slow->next = NULL; list_ele_t *l1 = merge_sort(fast); list_ele_t *l2 = merge_sort(head); return merge(l1, l2); } void q_sort(queue_t *q) { if (!q || !q->head || !q->head->next) { return; } q->head = merge_sort(q->head); while (q->tail->next) { q->tail = q->tail->next; } } ``` ## Address Sanitizer lab0-c 支援透過 ```SANITIZER=1``` 開起 Address Sanitizer 強化記憶體檢測, command 如下: ```shell= $ make SANITIZER=1 $ make test ``` 實際執行後會產生這個錯誤 messenge: ``` ================================================================= ==7886==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558be8287620 at pc 0x558be826fae8 bp 0x7ffe9a15fbd0 sp 0x7ffe9a15fbc0 READ of size 4 at 0x558be8287620 thread T0 #0 0x558be826fae7 in do_option_cmd /home/alan/linux2021/lab0-c/console.c:369 #1 0x558be826e8d0 in interpret_cmda /home/alan/linux2021/lab0-c/console.c:221 #2 0x558be826f0b5 in interpret_cmd /home/alan/linux2021/lab0-c/console.c:244 #3 0x558be82702e1 in cmd_select /home/alan/linux2021/lab0-c/console.c:594 #4 0x558be827085b in run_console /home/alan/linux2021/lab0-c/console.c:667 #5 0x558be826d3e5 in main /home/alan/linux2021/lab0-c/qtest.c:780 #6 0x7f21aaac50b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) #7 0x558be826ab8d in _start (/home/alan/linux2021/lab0-c/qtest+0x8b8d) 0x558be8287621 is located 0 bytes to the right of global variable 'simulation' defined in 'console.c:21:6' (0x558be8287620) of size 1 'simulation' is ascii string '' ``` trace 一下會發現錯誤發生在 ```console.c``` 中的 369 行: ```cpp=367 while (!found && plist) { if (strcmp(plist->name, name) == 0) { int oldval = *plist->valp; *plist->valp = value; if (plist->setter) plist->setter(oldval); found = true; } else plist = plist->next; } ``` 369行乍看沒有錯,但 ```plist->valp``` 指向的位置是```bool```,換句話說我們希望用```int```取值時會超過原本```bool```規劃的範圍,而取到**違法**的記憶體區塊。 ```cpp= //global variable simulation int simulation = false; //init_cmd() call add_param() using simulation add_param("simulation", (int *) &simulation, "Start/Stop simulation mode", NULL); //in the function we treat vlap as a pointer to int void add_param(char *name, int *valp, char *documentation, setter_function setter) { ... ele->valp = valp; ... return; } ``` 這個問題也會發生在執行 ```qtest``` 的指令 ```help``` 的時候,錯誤的原因和上述相同,這個問題我認為有兩個解決方式: 1. 暴力的把所有 ```vlap``` 統一用 ```int``` 去宣告 2. 用 ```void*``` 去 handle 各種不同的變數型態 目前我粗糙的用第一種方式讓 Address Sanitizer 能通過,第二種方法還會牽涉到變數長度問題。 ## Valgrind ## Dudect [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf) 是一篇發表於2016年的論文,根據我閱讀此論文後的理解如下。本篇論文的目的是要確認程式的某些運行時間是否是常數時間,他採用了統計的理論去**估計是否運行時間為常數的假設為真**,他的步驟如下: 1. Measure execution time: * 用兩個類別 ( fix vs. random ) 當作 testing input data 2. Apply post-processing: * 論文提到 Cropping 和 High-order preprocessing 的方法,後者尚未細讀過,目前的理解是本篇的實驗是建立在 Cropping 上討論 3. Apply statistical test: * 用 Welch’s t-test 來檢驗「兩組數據的運行時間分佈相同」這個假說 (hypothesis) **第一步**我認為不難理解,意義就是做實驗組跟對照組看看時間分佈是否相同。 **第二步**意外的容易影響實驗結果,論文中的論點是因為程式運行的當下可能被 OS 打斷等等因素,導致一些運行時間讓分佈失真,論文中的實驗結果也可以看出**不同的 cropping 參數確實會影響到結果的判讀**。 ### Welch’s t-test 今天我們要判斷兩個分佈是否相同, Welch’s t-test 用 **兩個分佈平均是否相同** 當作依據去判斷,也就是: $$ H_0 : \bar{X_0} - \bar{X_1} = 0$$ 所以我是這樣看待 t value 的:他和平均相同這個假設到底差多少 $$ t = \frac{(\bar{X_0} - \bar{X_1})-0}{\sqrt{\frac{s^2_1}{N_1} + \frac{s^2_2}{N_2}}} $$