# 2020q3 Homework1 (lab0)
contributed by < `dalaoqi` >
## Outline
[TOC]
## 環境
```shell
$ uname -a
Linux tsunghan-VirtualBox 5.4.0-47-generic #51~18.04.1-Ubuntu SMP Sat Sep 5 14:35:50 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
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.
$ lscpu
架構: x86_64![](https://i.imgur.com/rvGASYs.jpg)
![](https://i.imgur.com/WoL9wfX.jpg)
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
每核心執行緒數: 1
每通訊端核心數: 1
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 158
Model name: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
製程: 10
CPU MHz: 3696.002
BogoMIPS: 7392.00
Hypervisor 供應商: KVM
虛擬型態: 全部
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 12288K
NUMA node0 CPU(s): 0
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq monitor ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase avx2 invpcid rdseed clflushopt md_clear flush_l1d
```
## 使用工具
* Git pre-commit hook
* Valgrind: 動態程式分析工具
* AddressSanitizer: 記憶體使用分析
## 作業要求
於 `queue.[c/h]` 中完成下列功能
* `q.new`
* `q.free`
* `q.insert head`
* `q.insert tail`
* `q.remove head`
* `q.size`
* `q.reverse`
* 功能需要在 $O(1)$ 時間內執行
## 開發過程
### `queue.h`
作業要求有一項是要在 queue 的尾端新增元素,而且限制要在 $O(1)$ 時間內執行完畢。若每次都從頭開始iterate到尾巴,這樣的時間複雜度為 $O(N)$ ,不符合要求,因此需要在 `queue.h` 新增 `list_ele_t *tail` 到 `struct queue_t` 。`list_ele_t *tail` 的目的是紀錄 queue 的尾端,如此一來當執行 `q.insert tail` 就能夠在 $O(1)$ 時間內執行完畢。
```cpp
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
int size;
} queue_t;
```
### `queue.c`
#### `q_size`
因需要滿足 $O(1)$ 時間複雜度,因此在 `queue.h` 新增 `int size` 到 `struct queue_t`。如此,呼叫此函式可以直接回傳大小。
```cpp
int q_size(queue_t *q)
{
if (!q) {
return 0;
}
return q->size;
}
```
#### `q_new`
根據上述的結果, `q_new` 就會長成這樣,目的為當新的 queue 分配到新的記憶體空間後,需要做初始化的動作。
```cpp
/*
* Create empty queue.
* Return NULL if could not allocate space.
*/
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`
釋放 queue 中所有節點以及節點內所使用到的記憶體。
原本一開始提交的 code 是長得像下面這樣:
```cpp
/* Free all storage used by queue */
void q_free(queue_t *q)
{
if (!q) {
return;
}
list_ele_t *tmp;
while (q->head) {
tmp = q->head;
tmp->next = NULL;
q->head = q->head->next;
free(tmp->next);
free(tmp);
}
free(q);
}
```
結果在 `$ git commit` 的時候出現下列的錯誤:
```shell
Following files need to be cleaned up:
queue.c
queue.c:30:17: style: The scope of the variable 'tmp' can be reduced. [variableScope]
list_ele_t *tmp;
^
Fail to pass static analysis.
```
這個錯誤就是說, `*tmp` 在宣告的這個 scope 中沒有被使用到,所以把 `list_ele_t *tmp;` 移動到 while 迴圈中就可以解決,如下所示。
```cpp
/* Free all storage used by queue */
void q_free(queue_t *q)
{
if (!q) {
return;
}
while (q->head) {
list_ele_t *tmp = q->head;
tmp->next = NULL;
q->head = q->head->next;
free(tmp->next);
free(tmp);
}
free(q);
}
```
:::info
在差不多完成 `q_sort()` 後,預期的 test 分數應該要往上衝才對,但是很遺憾的只有42/100,出現的錯誤大多是
```shell
ERROR: Freed queue, but xxx blocks are still allocated
```
直覺是 `q_free` 那裡有問題,果然在針對 value 釋放記憶體的時候誤打成 next 了...
修改完後,得到71/100,還有下列錯誤需要處理。
```shell
Segmentation fault occurred. You dereferenced a NULL or invalid pointer
```
:::
#### `q_insert_head`
從 queue 的 head 插入一個新的 node ,若 queue 為 NULL 或是在分配空間時沒有成功分配的話,需要 return false 且釋放新分配的記憶體空間。在 `q->tail` 為 NULL 時也要將它指向新的節點。
在作業的說明中有提到
>依據 CERN [Common vulnerabilities guide for C programmers
](https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml) 建議而移除 gets / sprintf / strcpy 等不安全的函式;
將原本要使用的 `strcpy` 改為 CERN 所提供替代方案 `strlcpy` ,不過不是 BSD system ,所以又更改為 `snprintf` 函式進行操作。
```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;
}
size_t length = strlen(s) + 1;
newh->value = (char *) malloc(length * sizeof(char));
if (!newh->value) {
free(newh);
return false;
}
snprintf(newh->value, length, "%s", s);
newh->next = q->head;
q->head = newh;
if (!q->tail) {
q->tail = newh;
}
q->size++;
return true;
}
```
#### `q_remove_head`
將 queue 中的第一個 node (head) 移除,若成功移除則回傳 true ,若 queue 為空或 NULL ,則回傳 false 。如果 `sp` 不是 NULL 且節點移除成功,需要將被移除的節點中的值存放在 `sp` 中, `sp` 的最後一位為 null terminator。
```cpp
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->head) {
return false;
}
list_ele_t *tmp = q->head;
if (sp) {
snprintf(sp, bufsize, "%s", tmp->value);
}
q->head = q->head->next;
free(tmp->value);
free(tmp);
q->size--;
return true;
}
```
#### `q_reverse`
原本 `q_reverse` 是以下方的方式執行:
```cpp
void q_reverse(queue_t *q)
{
if (!q || !q->head) {
return;
}
list_ele_t *tmp = NULL;
q->tail->next = q->head;
while (tmp != q->tail) {
tmp = q->head->next;
q->head->next = tmp->next;
tmp->next = q->tail->next;
q->tail->next = tmp;
}
tmp->next = q->tail->next;
q->tail = q->head;
q->tail->next = NULL;
q->head = tmp;
return;
}
```
但在做 test 時在 trace-03 發生超時錯誤
```shell
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Removed value dolphin != expected value squirrel
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Removed value vulture != expected value gerbil
Error limit exceeded. Stopping command execution
```
經過調整測試修改 while 迴圈的條件可以減少程式執行的時間
```cpp
void q_reverse(queue_t *q)
{
if (!q || !q->head) {
return;
}
list_ele_t *tmp = NULL;
q->tail->next = q->head;
while (q->head->next != q->tail) {
tmp = q->head->next;
q->head->next = tmp->next;
tmp->next = q->tail->next;
q->tail->next = tmp;
}
q->tail = q->head;
q->tail->next = NULL;
q->head = tmp;
return;
}
```
但執行 test 時 trace-03 還是發生錯誤,推斷 `q_reverse` 還是沒有寫好。
```shell
ERROR: Removed value bear != expected value squirrel
ERROR: Removed value meerkat != expected value gerbil
ERROR: Removed value gerbil != expected value bear
ERROR: Removed value vulture != expected value meerkat
ERROR: Removal from queue failed (1 failures total)
Error limit exceeded. Stopping command execution
Segmentation fault occurred. You dereferenced a NULL or invalid pointer
```
為了驗證上述的推論,我試著執行下面的指令,運行的結果是正確的
```shell
new
ih dolphin
ih bear
ih gerbil
rh gerbil
rh bear
rh dolphin
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
```
回去看 `q_reverse` 函式,發現在 tail 的那個節點會被遺漏,因此再修改一次 while 迴圈的條件,也順便將 Segmentation fault 修好( tmp 在宣告的時候給值)。
```cpp
void q_reverse(queue_t *q)
{
if (!q || !q->head) {
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`
使用 mergeSort 當作排序的演算法,參考 [npes87184](https://npes87184.github.io/2015-09-12-linkedListSort/) 方式,修改成可作為字串比較的函式。
* `merge` 函式
```cpp
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
if (!l1) {
return l2;
}
if (!l2) {
return l1;
}
size_t len_l1 = strlen(l1->value), len_l2 = strlen(l2->value);
if (len_l1 < len_l2) {
len_l1 = len_l2;
}
if (strncmp(l1->value, l2->value, len_l1) <= 0) {
l1->next = merge(l1->next, l2);
return l1;
} else {
l2->next = merge(l1->next, l1);
return l2;
}
}
```
* `mergeSort` 主程式
```cpp
/*
* mergeSort for q_sort()
*/
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) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
list_ele_t *l1 = merge_sort(head);
list_ele_t *l2 = merge_sort(fast);
return merge(l1, l2);
}
```
* `q_sort`
```cpp
/*
* Sort elements of queue in ascending order
* No effect if q is NULL or empty. In addition, if q has only one
* element, do nothing.
*/
void q_sort(queue_t *q)
{
if (!q || !q->head) {
return;
}
q->head = merge_sort(q->head);
while (q->tail->next) {
q->tail = q->tail->next;
}
}
```
以上函式完成應當在 test 的時候會全部 pass ,結果如下
```shell
$ make test
```
分數結果如下:
<font color=Green>trace-01-ops 6/6
trace-02-ops 6/6
trace-03-ops 6/6</font>
<font color=Red>trace-04-ops 0/6
trace-05-ops 0/5
trace-06-string 0/6</font>
<font color=Green>trace-07-robust 6/6
trace-08-robust 6/6
trace-09-robust 6/6
trace-10-malloc 6/6
trace-11-malloc 6/6
trace-12-malloc 6/6
trace-13-per 6/6
trace-14-per 6/6</font>
<font color=Red>trace-15-per 0/6
trace-16-per 0/6</font>
<font color=Green>trace-17-complexity 5/5</font>
<font color=Red>TOTAL 71/100</font>
於是,我去看 trace-04-ops 指令並用 `$ valgrind ./qtest` 做測試:
```
cmd> new
cmd> new
q = []
cmd> ih gerbil
cmd> ih gerbil
q = [gerbil]
cmd> ih bear
cmd> ih bear
q = [bear gerbil]
cmd> ih dolphin
cmd> ih dolphin
q = [dolphin bear gerbil]
cmd> it meerkat
cmd> it meerkat
q = [dolphin bear gerbil meerkat]
cmd> it bear
cmd> it bear
q = [dolphin bear gerbil meerkat bear]
cmd> it gerbil
cmd> it gerbil
q = [dolphin bear gerbil meerkat bear gerbil]
cmd> sort
```
在 sort 時結果出現錯誤:
```shell
==17829== Invalid read of size 8
==17829== at 0x109D22: do_sort (qtest.c:561)
==17829== by 0x10BA22: interpret_cmda (console.c:220)
==17829== by 0x10BF96: interpret_cmd (console.c:243)
==17829== by 0x10C705: cmd_select (console.c:607)
==17829== by 0x10C7AC: run_console (console.c:628)
==17829== by 0x10AED1: main (qtest.c:772)
==17829== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==17829==
Segmentation fault occurred. You dereferenced a NULL or invalid pointer
==17829== 5 bytes in 1 blocks are still reachable in loss record 1 of 32
==17829== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17829== by 0x10B642: strsave_or_fail (report.c:230)
==17829== by 0x10BF56: parse_args (console.c:190)
==17829== by 0x10BF56: interpret_cmd (console.c:242)
==17829== by 0x10C705: cmd_select (console.c:607)
==17829== by 0x10C7AC: run_console (console.c:628)
==17829== by 0x10AED1: main (qtest.c:772)
==17829==
==17829== 8 bytes in 1 blocks are still reachable in loss record 2 of 32
==17829== at 0x4C31B25: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17829== by 0x10B59F: calloc_or_fail (report.c:208)
==17829== by 0x10BF27: parse_args (console.c:187)
==17829== by 0x10BF27: interpret_cmd (console.c:242)
==17829== by 0x10C705: cmd_select (console.c:607)
==17829== by 0x10C7AC: run_console (console.c:628)
==17829== by 0x10AED1: main (qtest.c:772)
...
```
猜測是在執行 `q_sort` 時發生問題,進一步檢查發現在 merge 時候有誤
```cpp
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
if (!l1) {
return l2;
}
if (!l2) {
return l1;
}
size_t len_l1 = strlen(l1->value), len_l2 = strlen(l2->value);
if (len_l1 < len_l2) {
len_l1 = len_l2;
}
if (strncmp(l1->value, l2->value, len_l1) <= 0) {
l1->next = merge(l1->next, l2);
return l1;
} else {
//這行
l2->next = merge(l1, l2->next);
return l2;
}
}
```
除完蟲之後再執行一次 `$ make test`
<font color=Green> trace-01-ops 6/6
trace-02-ops 6/6
trace-03-ops 6/6
trace-04-ops 6/6
trace-05-ops 5/5</font>
<font color=Red> trace-06-string 0/6 </font>
<font color=Green> trace-07-robust 6/6
trace-08-robust 6/6
trace-09-robust 6/6
trace-10-malloc 6/6
trace-11-malloc 6/6
trace-12-malloc 6/6
trace-13-per 6/6
trace-14-per 6/6 </font>
<font color=Red> trace-15-per 0/6 </font>
<font color=Green> trace-16-per 6/6
trace-17-complexity 5/5 </font>
<font color=Red>TOTAL 88/100 </font>
我去看 trace-06-string 指令並用 `valgrind ./qtest` 做測試,發現說如果 queue 在只有一個元素的狀況下做 `q_reverse` 會有下列錯誤:
```shell
==18020== Invalid read of size 8
==18020== at 0x10CE68: q_reverse (queue.c:159)
==18020== by 0x109EA2: do_reverse (qtest.c:463)
==18020== by 0x10BA22: interpret_cmda (console.c:220)
==18020== by 0x10BF96: interpret_cmd (console.c:243)
==18020== by 0x10C705: cmd_select (console.c:607)
==18020== by 0x10C7AC: run_console (console.c:628)
==18020== by 0x10AED1: main (qtest.c:772)
==18020== Address 0x8 is not stack'd, malloc'd or (recently) free'd
==18020==
Segmentation fault occurred. You dereferenced a NULL or invalid pointer
==18020== 8 bytes in 1 blocks are still reachable in loss record 1 of 30
==18020== at 0x4C31B25: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18020== by 0x10B59F: calloc_or_fail (report.c:208)
==18020== by 0x10BF27: parse_args (console.c:187)
==18020== by 0x10BF27: interpret_cmd (console.c:242)
==18020== by 0x10C705: cmd_select (console.c:607)
==18020== by 0x10C7AC: run_console (console.c:628)
==18020== by 0x10AED1: main (qtest.c:772)
==18020==
==18020== 8 bytes in 1 blocks are still reachable in loss record 2 of 30
==18020== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18020== by 0x10B642: strsave_or_fail (report.c:230)
==18020== by 0x10BF56: parse_args (console.c:190)
==18020== by 0x10BF56: interpret_cmd (console.c:242)
==18020== by 0x10C705: cmd_select (console.c:607)
==18020== by 0x10C7AC: run_console (console.c:628)
==18020== by 0x10AED1: main (qtest.c:772)
...
```
所以再去修改 `q_reverse`,在一開始的判斷式內新增 `q->size == 1` 的情況。修改完後重新執行 `make test`
<font color=Green> trace-01-ops 6/6
trace-02-ops 6/6
trace-03-ops 6/6
trace-04-ops 6/6
trace-05-ops 5/5
trace-06-string 6/6
trace-07-robust 6/6
trace-08-robust 6/6
trace-09-robust 6/6
trace-10-malloc 6/6
trace-11-malloc 6/6
trace-12-malloc 6/6
trace-13-perf 6/6
trace-14-perf 6/6 </font>
<font color=Red> trace-15-perf 0/6 </font>
<font color=Green> trace-16-perf 6/6
trace-17-complexity 5/5 </font>
<font color=Red>TOTAL 94/100 </font>
使用 AddressSanitizer 找問題,執行
```
$ make SANITIZER=1
$ make test
```
發現下列錯誤:
```
==20647==ERROR: AddressSanitizer: stack-overflow on address 0x7ffdd6d22fc8 (pc 0x7f1678355576 bp 0x7ffdd6d23850 sp 0x7ffdd6d22fd0 T0)
#0 0x7f31776f8e6e (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x59e6e)
#1 0x562475d82e44 in merge /home/tsunghan/linux2020/lab0-c/queue.c:178
#2 0x562475d82ebd in merge /home/tsunghan/linux2020/lab0-c/queue.c:179
#3 0x562475d82ebd in merge /home/tsunghan/linux2020/lab0-c/queue.c:179
#4 0x562475d82ebd in merge /home/tsunghan/linux2020/lab0-c/queue.c:179
```
結果是堆疊溢位,需要把 `q_sort` 修改成不需要遞迴的做法。參考 [Method 2 (Using Local References) ](https://www.geeksforgeeks.org/merge-two-sorted-linked-lists/) 方式做修改再重新執行指令。
<font color=Green> trace-01-ops 6/6
trace-02-ops 6/6
trace-03-ops 6/6
trace-04-ops 6/6
trace-05-ops 5/5
trace-06-string 6/6
trace-07-robust 6/6
trace-08-robust 6/6
trace-09-robust 6/6
trace-10-malloc 6/6
trace-11-malloc 6/6
trace-12-malloc 6/6
trace-13-perf 6/6
trace-14-perf 6/6 </font>
<font color=Red> trace-15-perf 0/6 </font>
<font color=Green> trace-16-perf 6/6
trace-17-complexity 5/5 </font>
<font color=Red>TOTAL 94/100 </font>
```
==8384==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x56075bc7c4f5 bp 0x7ffd69807750 sp 0x7ffd69807740 T0)
==8384==The signal is caused by a READ memory access.
==8384==Hint: address points to the zero page.
#0 0x56075bc7c4f4 in do_sort /home/tsunghan/linux2020/lab0-c/qtest.c:561
#1 0x56075bc7f68a in interpret_cmda /home/tsunghan/linux2020/lab0-c/console.c:220
```
錯誤是來自於嘗試去存取一些不合法的記憶體位址,如未被分配出的空間。
在 `merge` 函式中的比較字串大小的地方使用的是 `strncmp` ,兩個字串的比較長度以最長的那個為準,所以在存取較短的字串時就有可能存取到不合法的空間。將之改成 `strcmp`,就可以解決,因為他是比兩字串的字串直到碰到結尾 '\0' 。
```shell
$ make test
```
<font color=Green> trace-01-ops 6/6
trace-02-ops 6/6
trace-03-ops 6/6
trace-04-ops 6/6
trace-05-ops 5/5
trace-06-string 6/6
trace-07-robust 6/6
trace-08-robust 6/6
trace-09-robust 6/6
trace-10-malloc 6/6
trace-11-malloc 6/6
trace-12-malloc 6/6
trace-13-perf 6/6
trace-14-perf 6/6
trace-15-perf 6/6
trace-16-perf 6/6
trace-17-complexity 5/5
TOTAL 100/100 </font>
```shell
$ make valgrind
```
<font color=Green> trace-01-ops 6/6
trace-02-ops 6/6
trace-03-ops 6/6
trace-04-ops 6/6
trace-05-ops 5/5
trace-06-string 6/6
trace-07-robust 6/6
trace-08-robust 6/6
trace-09-robust 6/6
trace-10-malloc 6/6
trace-11-malloc 6/6
trace-12-malloc 6/6
trace-13-perf 6/6
trace-14-perf 6/6
trace-15-perf 6/6
trace-16-perf 6/6
trace-17-complexity 5/5
TOTAL 100/100 </font>
## 程式優化&除錯
紀錄在實作過程中,遇到的非個人實作部份的問題。
如下方在執行 `./qtest -f traces/trace-17-complexity.cmd
` 時在執行到 `option simulation 1` 會發生 `global-buffer-overflow` 錯誤,存取到非法的記憶體空間。
```
==17371==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55d74f604a00 at pc 0x55d74f3f591e bp 0x7fff3a083930 sp 0x7fff3a083920
READ of size 4 at 0x55d74f604a00 thread T0
#0 0x55d74f3f591d in do_option_cmd /home/tsunghan/linux2020/lab0-c/console.c:368
#1 0x55d74f3f453a in interpret_cmda /home/tsunghan/linux2020/lab0-c/console.c:220
#2 0x55d74f3f4f3e in interpret_cmd /home/tsunghan/linux2020/lab0-c/console.c:243
#3 0x55d74f3f5c27 in cmd_select /home/tsunghan/linux2020/lab0-c/console.c:569
#4 0x55d74f3f6044 in run_console /home/tsunghan/linux2020/lab0-c/console.c:628
#5 0x55d74f3f315d in main /home/tsunghan/linux2020/lab0-c/qtest.c:772
#6 0x7fb2cc42cb96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#7 0x55d74f3f08b9 in _start (/home/tsunghan/linux2020/lab0-c/qtest+0x68b9)
0x55d74f604a01 is located 0 bytes to the right of global variable 'simulation' defined in 'console.c:20:6' (0x55d74f604a00) of size 1
'simulation' is ascii string ''
SUMMARY: AddressSanitizer: global-buffer-overflow /home/tsunghan/linux2020/lab0-c/console.c:368 in do_option_cmd
Shadow bytes around the buggy address:
0x0abb69eb88f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0abb69eb8900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0abb69eb8910: 00 00 00 00 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x0abb69eb8920: 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x0abb69eb8930: 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
=>0x0abb69eb8940:[01]f9 f9 f9 f9 f9 f9 f9 00 00 00 00 01 f9 f9 f9
0x0abb69eb8950: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0abb69eb8960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0abb69eb8970: 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9
0x0abb69eb8980: f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9
0x0abb69eb8990: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==17371==ABORTING
--- trace-17-complexity 0/5
--- TOTAL 95/100
```
為了排除這個錯誤,去 `console.c` 中找到宣告為 bool 型態 simulation 變數。 但在 `init_cmd()` 函式初始化參數中,是以 `int` 型態去讀取 simulation 的記憶體位址。
```cpp
add_param("simulation", (int *) &simulation, "Start/Stop simulation mode", NULL);
```
原因是因為當初 simulation 時是以 bool 型態來做宣告 (1 byte),但在 `init_cmd()` 中,為配合 `add_param()` 函式,在讀 simulation 時強制轉型為 int* 型態 (4 bytes),造成在讀取的時候超出可讀取的範圍。所以將 simulation 型態改成 int 型態就可以解決這項錯誤。
## Valgrind Massif 實驗
練習使用 Valgrind 搭配 Massif,參考 [massif官方說明](https://valgrind.org/docs/manual/ms-manual.html) 操作。
massif 為 heap profiler,可以分析並記錄我們程式的 heap 使用量。
操作方式為指定工具 massif
```shell
$ valgrind --tool=massif 執行程式
```
執行完後,當前資料夾底下會出現 massif.out.<執行程式 PID>, 透過 `ms_print` 顯示分析結果
```shell
$ ms_print massif.out.<執行程式 PID>
```
以 trace-16-perf.cmd 為範例:
```shell
--------------------------------------------------------------------------------
Command: ./qtest -f ./traces/trace-16-perf.cmd
Massif arguments: (none)
ms_print arguments: massif.out.4265
--------------------------------------------------------------------------------
MB
12.83^ #############
| # ::::::::::
| ::# :: :
| :::# :: :
| :::# :: ::
| :::::# :: ::
| ::::::# :: ::
| @::::::# :: ::
| :@::::::# :: ::
| @:@::::::# :: ::
| @@@@@@@::::: :@:@::::::# :: ::
| :@ : : ::@:@::::::# :: ::
| :@ : : :::@:@::::::# :: :::
| :::@ : : ::::@:@::::::# :: :::
| :::@ : : @::::@:@::::::# :: ::@
| :::::@ : : :@::::@:@::::::# :: ::@
| :::::@ : : :::@::::@:@::::::# :: ::@
| :::::::@ : : ::::@::::@:@::::::# :: ::@
| @: ::::::::@ : : ::::@::::@:@::::::# :: ::@
| @: :::::::::@ : :: :::::@::::@:@::::::# :: ::@
0 +----------------------------------------------------------------------->Mi
0 495.3
Number of snapshots: 61
Detailed snapshots: [2, 18, 29, 35, 38, 47 (peak), 57]
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
0 0 0 0 0 0
1 7,457,859 643,248 499,627 143,621 0
2 15,767,905 1,354,744 1,050,291 304,453 0
77.53% (1,050,291B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->76.78% (1,040,189B) 0x10C7CE: test_malloc (harness.c:137)
| ->41.34% (560,000B) 0x10CC2B: q_insert_head (queue.c:52)
| | ->41.34% (560,000B) 0x10A864: do_insert_head (qtest.c:212)
| | ->41.34% (560,000B) 0x10B9CD: interpret_cmda (console.c:220)
| | ->41.34% (560,000B) 0x10BF41: interpret_cmd (console.c:243)
| | ->41.34% (560,000B) 0x10C50F: cmd_select (console.c:569)
| | ->41.34% (560,000B) 0x10C757: run_console (console.c:628)
| | ->41.34% (560,000B) 0x10AE7C: main (qtest.c:772)
| |
| ->35.44% (480,125B) 0x10CC52: q_insert_head (queue.c:57)
| | ->35.44% (480,125B) 0x10A864: do_insert_head (qtest.c:212)
| | ->35.44% (480,125B) 0x10B9CD: interpret_cmda (console.c:220)
| | ->35.44% (480,125B) 0x10BF41: interpret_cmd (console.c:243)
| | ->35.44% (480,125B) 0x10C50F: cmd_select (console.c:569)
| | ->35.44% (480,125B) 0x10C757: run_console (console.c:628)
| | ->35.44% (480,125B) 0x10AE7C: main (qtest.c:772)
| |
| ->00.00% (64B) in 1+ places, all below ms_print's threshold (01.00%)
|
->00.75% (10,102B) in 1+ places, all below ms_print's threshold (01.00%)
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
3 23,453,176 1,354,656 1,050,261 304,395 0
4 28,845,644 1,354,656 1,050,261 304,395 0
5 36,050,846 493,792 384,107 109,685 0
6 40,597,339 893,616 693,605 200,011 0
7 47,999,774 1,544,176 1,196,899 347,277 0
8 53,713,837 2,047,696 1,585,950 461,746 0
9 58,710,600 2,487,960 1,926,667 561,293 0
10 65,134,400 3,052,248 2,363,619 688,629 0
11 70,488,913 3,523,224 2,728,127 795,097 0
12 74,773,885 3,900,288 3,019,744 880,544 0
13 80,844,693 4,433,200 3,432,074 1,001,126 0
14 85,484,153 4,841,568 3,748,130 1,093,438 0
15 91,194,395 5,343,664 4,137,005 1,206,659 0
16 94,765,012 5,658,096 4,380,328 1,277,768 0
17 100,832,364 6,192,128 4,793,704 1,398,424 0
18 107,970,808 6,730,104 5,210,146 1,519,958 0
77.42% (5,210,146B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->77.27% (5,200,044B) 0x10C7CE: test_malloc (harness.c:137)
| ->41.60% (2,800,000B) 0x10CC2B: q_insert_head (queue.c:52)
(略)
```
massif 會在程式執行期間製作多個 snapshots,顯示程式 heap memory 執行時期的使用量。其中:
* :, Normal snapshot
* @, Detail snapshot
* #, Peak snapshot
若程式有正確釋放記憶體,會看到最終記憶體使用量會回到0。
透過 massif ,我們可以分析程式 heap 的用量,並透過 stack trace,優化程式。
透過 massif-visulizer 視覺化的結果:
![](https://i.imgur.com/4YOuJjO.jpg)
把 `q_free` 拿掉,發現直到程式結束前記憶體用量都沒有下降,造成 memory leak。
![](https://i.imgur.com/dp7LuCn.jpg)