# 2021q1 Homework1 (lab0) contributed by < `YingMuo` > ###### tags: `linux2021` ## 環境設定 1. 安裝必要工具套件 ```shell $ sudo apt install build-essential git-core valgrind $ sudo apt install cppcheck clang-format aspell colordiff ``` 2. clang-format 設定 - vscode - [x] editor: format on save - C_Cpp: Clang_format_fallback Style 設定的跟.clang-format一樣 3. 引入 git-good-commit ## queue - [x] new - [x] free - [x] insert_head - [x] insert_tail - [x] remove_head - [x] size - [x] reverse - [x] sort ### struct ```cpp /* Queue structure */ typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; int size; } queue_t; ``` ### new/free - 需要檢查 q 不是 null - 就 malloc/free ### insert_head/insert_tail - 需要檢查 1. q 不是 null 2. malloc element 不是 null 3. malloc string 不是 null - malloc 二個空間給 head/tail 和 value - 如果是第一次 insert head/tail 需要補上 tail/head - size++ ### remove_head - 需要檢查 q 不是 null、q->head 不是 null - 如果 sp 有值就用 strncpy 複製 q->head->value 過去 - 要 free head 和 value - size-- ### size - 需要檢查 q 不是 null - 回傳 q->size ### reverse - 需要檢查 q 不是 null、q->head 不是 null - tail 指向 head - 把所有 element 的 link 方向反轉 ### q_sort - 需要檢查 q 不是 null、q->head 不是 null - merge sort ## 找蟲 ### address sanitizer 在編譯時帶入 sanitizer ```shell make SANITIZER=1 ``` 接著執行 `./qtest`並輸入 `help`會看到 ```c ==4977==ERROR: AddressSanitizer: global-buffer-overflow on address 0x560410f423c0 at pc 0x560410f2b7dd bp 0x7ffc0dee4950 sp 0x7ffc0dee4940 READ of size 4 at 0x560410f423c0 thread T0 #0 0x560410f2b7dc in do_help_cmd /home/yingmuo/Desktop/linux2021/lab0-c/console.c:307 #1 0x560410f2b8f0 in interpret_cmda /home/yingmuo/Desktop/linux2021/lab0-c/console.c:221 #2 0x560410f2c0d5 in interpret_cmd /home/yingmuo/Desktop/linux2021/lab0-c/console.c:244 #3 0x560410f2d818 in run_console /home/yingmuo/Desktop/linux2021/lab0-c/console.c:660 #4 0x560410f2a405 in main /home/yingmuo/Desktop/linux2021/lab0-c/qtest.c:780 #5 0x7fe36fa3e0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) #6 0x560410f27bad in _start (/home/yingmuo/Desktop/linux2021/lab0-c/qtest+0x8bad) 0x560410f423c1 is located 0 bytes to the right of global variable 'echo' defined in 'console.c:59:13' (0x560410f423c0) of size 1 SUMMARY: AddressSanitizer: global-buffer-overflow /home/yingmuo/Desktop/linux2021/lab0-c/console.c:307 in do_help_cmd ``` ### 分析 裡面有說到幾個重點: 1. READ of size 4 代表有一個地方有 4 byte 的操作 ( 猜測是 int ) 2. 'echo' defined of size 1 被操作的 echo 只有 1 byte ```c=59 static bool echo = 0; ``` 3. /linux2021/lab0-c/console.c:307 in do_help_cmd 在這地方有被使用到,那就來看這裡的 code ```c=304 param_ptr plist = param_list; report(1, "Options:"); while (plist) { report(1, "\t%s\t%d\t%s", plist->name, *plist->valp, plist->documentation); plist = plist->next; } ``` 看起來是對 plist 的操作,而 plist 則是 param_list,那麼往上追 param_list ```c=23 static param_ptr param_list = NULL; ``` ```c=88 void init_cmd() { cmd_list = NULL; param_list = NULL; err_cnt = 0; quit_flag = false; add_cmd("help", do_help_cmd, " | Show documentation"); add_cmd("option", do_option_cmd, " [name val] | Display or set options"); add_cmd("quit", do_quit_cmd, " | Exit program"); add_cmd("source", do_source_cmd, " file | Read commands from source file"); add_cmd("log", do_log_cmd, " file | Copy output to file"); add_cmd("time", do_time_cmd, " cmd arg ... | Time command execution"); add_cmd("#", do_comment_cmd, " ... | Display comment"); 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); init_in(); init_time(&last_time); first_time = last_time; } ``` 可以找到 param_list 是一個全域變數,並會在 init_cmd 透過 init_cmd() 裡的 add_param() 去對他初始化,會發現裏頭有用到 echo ```c=108 add_param("echo", (int *) &echo, "Do/don't echo commands", NULL); ``` 其中使用 (int \*) 對 echo 進行強制轉型,但是 echo 的型態是 _Bool 只有 1 byte ,所以才會發生 overflow ### 改正 把 echo 改成 int 就可以了 ```c=59 static int echo = 0; ``` ### BUT,又出現新的錯誤了QQ 不過看起來跟 echo 是一樣的問題,所以就不放細節了 ```c=21 bool simulation = false; ``` 一樣改成 int 就沒事了 ## valgrind `make valgrind`會看到 ```c +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head ==11264== 4 bytes in 1 blocks are still reachable in loss record 1 of 3 ==11264== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==11264== by 0x4A4F50E: strdup (strdup.c:42) ==11264== by 0x11010E: linenoiseHistoryAdd (linenoise.c:1236) ==11264== by 0x110CA1: linenoiseHistoryLoad (linenoise.c:1325) ==11264== by 0x10C24C: main (qtest.c:769) ==11264== ==11264== 160 bytes in 1 blocks are still reachable in loss record 2 of 3 ==11264== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==11264== by 0x1100CE: linenoiseHistoryAdd (linenoise.c:1224) ==11264== by 0x110CA1: linenoiseHistoryLoad (linenoise.c:1325) ==11264== by 0x10C24C: main (qtest.c:769) ==11264== ==11264== 164 bytes in 19 blocks are still reachable in loss record 3 of 3 ==11264== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==11264== by 0x4A4F50E: strdup (strdup.c:42) ==11264== by 0x110082: linenoiseHistoryAdd (linenoise.c:1236) ==11264== by 0x110CA1: linenoiseHistoryLoad (linenoise.c:1325) ==11264== by 0x10C24C: main (qtest.c:769) ``` 看起來是有記憶體沒有被 free 掉,在 `linenoise.c` 可以看到要觸發 `freeHistory()` 必須執行到 `linenoise()`,然而要執行到 linenoise() 必須是在有 infile_name 的情況 ```c bool run_console(char *infile_name) { if (!push_file(infile_name)) { report(1, "ERROR: Could not open source file '%s'", infile_name); return false; } if (!has_infile) { char *cmdline; while ((cmdline = linenoise(prompt)) != NULL) { interpret_cmd(cmdline); linenoiseHistoryAdd(cmdline); /* Add to the history. */ linenoiseHistorySave(HISTORY_FILE); /* Save the history on disk. */ linenoiseFree(cmdline); } } else { while (!cmd_done()) cmd_select(0, NULL, NULL, NULL, NULL); } return err_cnt == 0; } ``` 我選擇把 `freeHistory()` 的 static 拿掉,在 qtest.c 最後 call freeHistory(),就可以解決了 ```c ok = ok && run_console(infile_name); ok = ok && finish_cmd(); freeHistory(); ```