# 2018q3 Homework2 (lab0) contributed by <`AlecJY`> >commit message 中,將 "Complete" 改為 "Implement" 更為適當 >[name=課重助教][color=red] >> 好的,謝謝助教的建議 >> [name=AlecJY] ## 實驗環境 * 作業系統:openSUSE Leap 15.0 * gcc:7.3.1 * Cppcheck:1.8 * clang-format:5.0.1 查詢指令及結果如下 ``` $ cat /etc/os-release NAME="openSUSE Leap" VERSION="15.0" ID="opensuse-leap" ID_LIKE="suse opensuse" VERSION_ID="15.0" PRETTY_NAME="openSUSE Leap 15.0" ANSI_COLOR="0;32" CPE_NAME="cpe:/o:opensuse:leap:15.0" BUG_REPORT_URL="https://bugs.opensuse.org" HOME_URL="https://www.opensuse.org/" ``` ``` $ gcc --version gcc (SUSE Linux) 7.3.1 20180323 [gcc-7-branch revision 258812] Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ``` ``` $ cppcheck --version Cppcheck 1.8 ``` ``` $ clang-format --version clang-format version 5.0.1 (tags/RELEASE_501/final 312548) ``` ## 開發紀錄 ### 環境設定 * 主要因為在上這堂課之前就已經安裝 openSUSE ,加上硬碟空間不足,所以沒有另外安裝 Lubuntu ,直接使用原本已經安裝好的 openSUSE * 在 GitHub 上面 fork sysprog21/lab0-c ,然後把 fork 出來的 repo clone 下來 * 之後執行 `make` ,會編譯程式碼並且把 git hook 設定好 * 發現沒有安裝 `cppcheck` 以及 `clang-format`,因為使用 openSUSE , `clang-format` 直接放在 `clang5` 這個 package 裡面,安裝指令如下 ``` $ sudo zypper in cppcheck clang5 ``` * 在提交第一個 commit 之後才發現原始 repo 已經變動,查了一下怎麼把原始 repo 的變動加進去 ``` $ git remote add upstream https://github.com/sysprog21/lab0-c.git $ git pull upstream master ``` ### 程式碼更動 #### q_new() 這邊根據註解的提示加了檢查 `malloc()` 是否回傳 `NULL` 的檢查,如果是 NULL 的話就直接回傳變數 `q` ,避免之後存取到 `NULL` 指標。 #### q_insert_head() 和 q_remove_head() `q_insert_head()` 這部分就是檢查 queue 和字串是否為 `NULL` ,然後利用 `strlen()` 計算字串長度後使用 `malloc()`` ,新增一個 `list_ele_t` 之後加到 queue `q_remove_head()` 這部分也是檢查 queue 和字串是否為 `NULL` ,然後在 `sp` 不是 `NULL` 的情況下把 head 的值複製過去,這邊使用 `strncpy` 複製,然後留下最後一個位置放 null character ,避免字串沒有結尾,之後把 queue 的 head 指到新的 head 後把舊的 head free 在複製字串的部分忘記文件有提到過一定要用 `malloc()` 來分配空間,所以原本想要用 `strdup()` 的,結果在 free 的時候就直接 segmentation fault ,原本還以為誤會了 `strdup()` 的用法,所以仔細檢查了一遍發現好像沒有問題。後來想說不透過 qtest 直接呼叫 function 操作看看,在把測試用程式寫完想要編譯的時候發現出現底下的錯誤 ``` /tmp/ccTDe3Rw.o: In function `q_new': queue.c:(.text+0xe): undefined reference to `test_malloc' /tmp/ccTDe3Rw.o: In function `q_free': queue.c:(.text+0x48): undefined reference to `test_free' /tmp/ccTDe3Rw.o: In function `q_insert_head': queue.c:(.text+0x65): undefined reference to `test_malloc' queue.c:(.text+0x8f): undefined reference to `test_malloc' queue.c:(.text+0xb1): undefined reference to `test_free' /tmp/ccTDe3Rw.o: In function `q_remove_head': queue.c:(.text+0x193): undefined reference to `test_free' queue.c:(.text+0x19f): undefined reference to `test_free' collect2: error: ld returned 1 exit status ``` 去看了其中一個 include 的 header `harness.h` 的時候才發現原來 `malloc()` 和 `free()` 被 Macro 改成了 `test_malloc()` 和 `test_free()` ,所以 `strdup()` 呼叫原本的 `malloc()` 所創建的記憶體空間被*不知道做了什麼事情的* `test_free()` 釋放掉,所以產生錯誤,所以嘗試把 `harness.h` 註解掉之後用自己寫的測試小程式跑發現就沒有產生 Segmentation Fault 了 > 等把程式寫完去看 qtest 程式碼後會再補上 `test_malloc()` 和 `test_free()` 做了什麼 #### q_insert_tail() 大致上步驟跟 `q_insert_head()` 差不多,由於要求時間複雜度為 O(1) ,所以在 `queue_t` 中新增了一個新的欄位 `tail` 來記錄 queue 的結尾,並且同時更改了 `q_insert_head()` 和 `q_remove_head()` 在新增至空 queue 和將 queue 清空的情況變動 tail 中的值 #### q_size() 這部分在 `queue_t` 中新增了一個欄位 `size` 來記錄 queue 的長度,一開始先初始化成0,當有新增或刪除的動作時再將 `size` 的數值做增減 #### q_free() 這邊使用了一個迴圈走訪 queue 中的所有元素並把他們以及他們裡面值得記憶體空間釋放,最後再將 queue 本身釋放 #### q_reverse() 這邊先把 tail 的值設定為反轉前的 head ,再使用一個迴圈走訪 queue 並移動 head 的位置,移動時把 next 改為前一個值的位址,走訪完的時候由於最後一個元素因為條件判斷的關係沒有更改到 next 值,所以在迴圈執行完後單獨處理 ## 程式碼閱讀 ### Git Hooks * Makefile 中在執行 `make` 的時候會檢查 `.git/hooks/applied` 是否存在,如果不存在就執行 `scripts/install-git-hooks` 這個腳本 * `install-git-hooks` 會在 `.git/hooks/` 這個資料夾裡面建立 `pre-commit` 和 `commit-msg` 的 symbolic link,並建立 `.git/hooks/applied` #### `pre-commit.hook` * 這個腳本會檢查有沒有安裝 clang-format 和 cppcheck * 如果有安裝 colordiff 會使用 colordiff ,沒有的話會使用 diff * 會將程式碼送至 clang-format 及 cppcheck 檢查 * 使用 clang-format 檢查是將修改過得檔案送到 clang-format 自動排版一次,再比較有沒有修改過 ### qtest #### `harness.c` / `harness.h` 因為之前被改過的 `malloc()` 和 `free()` 弄到產生 bug ,因此想說先從這兩個檔案開始看 `harness.h` 裡面有一段程式碼 ``` C #ifdef INTERNAL ... #else /* Tested program use our versions of malloc and free */ #define malloc test_malloc #define free test_free #endif ``` 在有定義 `INTERNAL` 的時候就不會將 `malloc()` 和 `free()` 取代成 `test_malloc()` 和 `test_free()` 了,在 `harness.c` 和 `qtest.c` 都有定義 `INTERNAL` 以避免使用 `test_malloc()` 和 `test_free()` 在 `test_malloc()` 裡面會檢查現在是否處於 `noallocate_mode` ,並根據機率隨機決定是否要讓 malloc 強制失敗,之後會建立 `block_ele_t` 的變數 `new_block` ,`test_malloc()` 實際上回傳的記憶體空間是 `block_ele_t` 中的 `payload`,並在 `payload` 的前後塞一些固定的值作為檢查,之後把 `new_block` 和其他之前 allocate 的 block 串起來 在 `test_free()` 裡面會透過 `find_header()` 算出 `block_ele_t` 這個資料結構真正的位置,並且確認是否真的存在,檢查 `magic_header` 等處理,之後檢查 footer 有沒有錯誤,跟檢查有沒有發生 buffer overflow 而放在 stack 裡面的 canary 有點像,最後把 header 和 footer 都設定成 `MAGICFREE` 之後把整塊空間釋放 在 `exception_setup()` 裡面有用到一個 `sigsetjmp()` ,如果回傳 0 的話就代表它是直接 return ,如果不是 0 就代表是使用 `longjump()` 或是 `siglongjump()` 過來這裡的,這邊利用這種方式設定一秒的限制執行時間,之後在 `qtest.c` 裡面設定 `SIGSEGV` 和 `SIGALRM` 的 handler 來處理 Segmentation Fault 和執行超時。 #### `console.c` / `console.h` 這邊使用了一個 list 紀錄指令,利用 `add_cmd()` ,在裡面放入指令名稱、執行指令的函式以及說明,之後在 `interpret_cmda()` 的時候就會去 list 中找到對應的指令並呼叫函式