# 2021q1 Homework1 (lab0) contributed by < `hapion` > ## 作業要求 完成以下程式 - q_new: 建立新的「空」佇列; - q_free: 釋放佇列所佔用的記憶體; - q_insert_head: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則); - q_insert_tail: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則); - q_remove_head: 在佇列開頭 (head) 移去 (remove) 給定的元素。 - q_size: 計算佇列中的元素數量。 - q_reverse: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素; - q_sort ## 實作 ### Queue structure - 為了使 insert_tail 跟 size 可以在$O(1)$ 實現,增加 tail 跟 size ``` c= typedef struct { list_ele_t *head; list_ele_t *tail; int size; } queue_t; ``` ### q_new - 產生一個新的 queue,先配置記憶體空間,記得初始化剛剛增加的 tail 跟 size ``` c= queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (q == NULL) return NULL; q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` ### q_free - 釋放的時候要先檢查 queue 是否為 NULL,透過 while 迴圈,先釋放 queue 上 element 所佔空間,最後在釋放 queue 本身 ``` c= void q_free(queue_t *q) { if (q == NULL) return; while (q->head != NULL) { list_ele_t *tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); } free(q); } ``` ### q_insert_head/tail - head 跟 tail 在新增節點的方式一樣,差別在於後面 head 與 tail 的處理 - 使用 strncpy 來處理字串,要記得加 \0 - 一開始測試 q_insert_tail 時,沒注意到要將新節點指向 NULL ,出現segmantation fault ,導致 memory leak ``` c= bool q_insert_head(queue_t *q, char *s) { if (q == NULL) return false; list_ele_t *newh; newh = malloc(sizeof(list_ele_t)); if (newh == NULL) return false; newh->value = malloc(sizeof(char) * (strlen(s) + 1)); if (newh->value == NULL) { free(newh); return false; } memset(newh->value, '\0', (strlen(s) + 1)); strncpy(newh->value, s, strlen(s)); newh->next = q->head; q->head = newh; if (q->size == 0) q->tail = newh; q->size++; return true; } bool q_insert_tail(queue_t *q, char *s) { if (q == NULL) return false; list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (newt == NULL) return false; newt->value = malloc(sizeof(char) * (strlen(s) + 1)); if (newt->value == NULL) { free(newt); return false; } memset(newt->value, '\0', (strlen(s) + 1)); strncpy(newt->value, s, strlen(s)); newt->next = NULL; if (q->head == NULL) { q->head = newt; } else { q->tail->next = newt; } q->tail = newt; q->size++; return true; } ``` ### q_remove_head - 釋放 element 本身前,要記得先釋放 element 中存放的 char pointer ``` c= { if (q == NULL || q->head == NULL || sp == NULL) return false; memset(sp, '\0', bufsize); strncpy(sp, q->head->value, bufsize - 1); list_ele_t *tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); q->size--; return true; } ``` ### q_size ``` c = int q_size(queue_t *q) { if (q == NULL || q->head == NULL) return 0; return q->size; } ``` ### q_reverse ``` c= void q_reverse(queue_t *q) { if (q == NULL || q->head == NULL) return; q->tail = q->head; list_ele_t *cur = q->head; list_ele_t *next = NULL; list_ele_t *prev = NULL; while (cur != NULL) { next = cur->next; cur->next = prev; prev = cur; cur = next; } q->head = prev; } ``` - 圖例 Declare ```graphviz digraph graphname{ NULL[label=NULL,shape=plaintext] node[shape=box] head[shape=plaintext,fontcolor=red] tail[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=blue] next[shape=plaintext,fontcolor=blue] prev[shape=plaintext,fontcolor=blue] { rankdir = LR rank="same" A[label=A] B[label=B] C[label=C] NULL1[label=NULL,shape=plaintext] } { head->A tail->C } A->B->C->NULL1 cur->A next->NULL prev->NULL } ``` while loop ```cpp next = cur->next; ``` ```graphviz digraph graphname{ NULL[label=NULL,shape=plaintext] node[shape=box] head[shape=plaintext,fontcolor=red] tail[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=blue] next[shape=plaintext,fontcolor=blue] prev[shape=plaintext,fontcolor=blue] { rank="same" A[label=A] B[label=B] C[label=C] NULL1[label=NULL,shape=plaintext] } { head->A tail->C } A->B->C->NULL1 cur->A next->B prev->NULL } ``` ``` c cur->next = prev; ``` ```graphviz digraph graphname{ head[shape=plaintext,fontcolor=red] tail[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=blue] next[shape=plaintext,fontcolor=blue] prev[shape=plaintext,fontcolor=blue] { rank="same" NULL[label=NULL,shape=plaintext] node[shape=box] A[label=A] B[label=B] C[label=C] NULL1[label=NULL,shape=plaintext] } { head->A NULL->A [dir=back]; tail->C } B->C->NULL1 cur->A next->B prev->NULL } ``` ``` c prev = cur; ``` ```graphviz digraph graphname{ head[shape=plaintext,fontcolor=red] tail[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=blue] next[shape=plaintext,fontcolor=blue] pre[shape=plaintext,fontcolor=blue] { rank="same" NULL[label=NULL,shape=plaintext] node[shape=box] A[label=A] B[label=B] C[label=C] NULL1[label=NULL,shape=plaintext] } { head->A NULL->A [dir=back]; tail->C } B->C->NULL1 cur->A next->B pre->A } ``` ``` c cur = next; ``` ```graphviz digraph graphname{ head[shape=plaintext,fontcolor=red] tail[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=blue] next[shape=plaintext,fontcolor=blue] pre[shape=plaintext,fontcolor=blue] { rank="same" NULL[label=NULL,shape=plaintext] node[shape=box] A[label=A] B[label=B] C[label=C] NULL1[label=NULL,shape=plaintext] } { head->A NULL->A [dir=back]; tail->C } B->C->NULL1 cur->B next->B pre->A } ``` ### q_sort - 主要是參考 [Merge Sort for Linked Lists](https://www.geeksforgeeks.org/merge-sort-for-linked-list/) 來實作 Merge sort ### Address Sanitizer 在開啟 Address Sanitizer 後,出現錯誤 ``` c= ==13804==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55b29afcf5e0 at pc 0x55b29afb7ae8 bp 0x7ffddadb7e50 sp 0x7ffddadb7e40 READ of size 4 at 0x55b29afcf5e0 thread T0 #0 0x55b29afb7ae7 in do_option_cmd /home/hapion/linux2021/lab0-c/console.c:369 #1 0x55b29afb68d0 in interpret_cmda /home/hapion/linux2021/lab0-c/console.c:221 #2 0x55b29afb70b5 in interpret_cmd /home/hapion/linux2021/lab0-c/console.c:244 #3 0x55b29afb82e1 in cmd_select /home/hapion/linux2021/lab0-c/console.c:594 #4 0x55b29afb885b in run_console /home/hapion/linux2021/lab0-c/console.c:667 #5 0x55b29afb53e5 in main /home/hapion/linux2021/lab0-c/qtest.c:780 #6 0x7f157e5070b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) #7 0x55b29afb2b8d in _start (/home/hapion/linux2021/lab0-c/qtest+0x8b8d) 0x55b29afcf5e1 is located 0 bytes to the right of global variable 'simulation' defined in 'console.c:21:6' (0x55b29afcf5e0) of size 1 'simulation' is ascii string '' SUMMARY: AddressSanitizer: global-buffer-overflow /home/hapion/linux2021/lab0-c/console.c:369 in do_option_cmd Shadow bytes around the buggy address: 0x0ab6d35f1e60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab6d35f1e70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab6d35f1e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab6d35f1e90: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 0x0ab6d35f1ea0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 =>0x0ab6d35f1eb0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9[01]f9 f9 f9 0x0ab6d35f1ec0: f9 f9 f9 f9 00 00 00 00 01 f9 f9 f9 f9 f9 f9 f9 0x0ab6d35f1ed0: 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00 0x0ab6d35f1ee0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab6d35f1ef0: 00 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9 0x0ab6d35f1f00: 01 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 ``` 發生 global over flow,錯誤偵測點在`369`行,對應到`do_option_cmd`函式中的 `int oldval = *plist->valp;` 判定是在參數傳遞出現問題,因此往前追蹤`plist`的來源,其來自於`param_ptr param_list`且在`add_param`中賦予值 接著繼續往前尋找`add_param`的呼叫點,是在`init_cmd`函式底下 ```c= add_param("simulation", (int *) &simulation, "Start/Stop simulation mode", NULL); add_param("verbose", &verblevel, "Verbosity level", NULL); add_param("error", &err_limit, "Number of errors until exit", NULL); add_param("echo", (int *) &echo, "Do/don't echo commands", NULL); ``` 發現問題源於`simulation`、`echo`兩個全域變數 `simulation`、`echo` 最初是宣告成 `bool` 佔一個 bytes, 上面這段程式將其轉成 `int` 因此產生 `global-buffer- overflow` (存取到超出 `bool` 本身一個 byte 大小的空間造成錯誤) - 可以將所有 bool 都改成 int,但會需要連帶修改其他東西,不好維護,且需要使用額外 3 byte 來表示一個 bool 參考 [RinHizakura](https://hackmd.io/@RinHizakura/ByuY_q6AU#Address-Sanitizer) 發現可以利用判斷資料型別大小來解決 調整方法 - 先將 PELE 中`valp`修改為`void *`,新增一個 type_size 來記錄原始 type 所用的 bytes ```c= typedef struct PELE param_ele, *param_ptr; struct PELE { char *name; void *valp; int type_size; char *documentation; /* Function that gets called whenever parameter changes */ setter_function setter; param_ptr next; }; ``` - function`add_param`也要修改 - 設計一個函式`valp_deref`來判斷型別並回傳,此 function 在`do_option_cmd`、`report`會使用到 ``` c= int valp_deref(param_ptr plist) { if (plist->type_size == sizeof(bool)) return *(bool *) plist->valp == true ? 1 : 0; else return *(int *) plist->valp; } ``` - 修改`do_option_cmd`來讓輸入符合需求,利用`memcpy`來 assign value ```c= * Find parameter in list */ param_ptr plist = param_list; while (!found && plist) { if (strcmp(plist->name, name) == 0) { int oldval = valp_deref(plist); if (plist->type_size == sizeof(bool)) memcpy(plist->valp, &value, sizeof(bool)); else memcpy(plist->valp, &value, sizeof(int)); if (plist->setter) plist->setter(oldval); found = true; } else plist = plist->next; }