owned this note
owned this note
Published
Linked with GitHub
# 2021q1 Homework1 (lab0)
contributed by < `cyrong` >
## 實作過程
為了讓 `q_size` 以及 `q_insert_tail` 時間複雜度為 $O(1)$
修改 `queue.h`
新增 size 以及 tail 兩個元素
```c
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
int size; /* Size of linked list */
list_ele_t *tail; /* Tail of linked list */
} queue_t;
```
### `q_new()`
建立一個 `queue` 如果成功回傳此 `queue` 否則回傳 `NULL`
先 `malloc` 給 `q`
檢查 `malloc` 有無成功
成功就初始 `q` , head : `NULL` 、 tail : `NULL` 、 size : `0`
失敗就回傳 `NULL`
```c
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (q != NULL) {
q->head = NULL;
q->size = 0;
q->tail = NULL;
}
return q;
}
```
### `q_free()`
釋放 `q` 中所有分配的記憶體
檢查 `q` 是否為 `NULL`
釋放 字串 -> 釋放 元素 -> 釋放 `q`
```c
void q_free(queue_t *q)
{
if (q) {
list_ele_t *tmp = q->head;
while (q->head) {
q->head = q->head->next;
free(tmp->value);
free(tmp);
tmp = q->head;
}
} else
return;
free(q);
}
```
### `q_insert_head()`
從頭插入一個元素
檢查 `q` 以及分配的 `newh`, `newh->value` 是否為 `NULL`
再來就是把 `newh` 加進 `q`
若是 `q` 中沒有元素, `q->tail` 也要作設定
最後 `q->size += 1`
```c
bool q_insert_head(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
if (!newh)
return false;
newh->value = malloc(sizeof(char) * strlen(s) + 1);
if (!newh->value) {
free(newh);
return false;
}
strncpy(newh->value, s, strlen(s) + 1);
newh->next = q->head;
q->head = newh;
if (q->size == 0)
q->tail = newh;
q->size += 1;
return true;
}
```
### `q_insert_tail()`
從尾端插入一個元素
整體來說與 `q_insert_head()` 差不多
最後加到 `tail` 的話要把原本 `tail->next` 設定成 `newt`
```c
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(sizeof(char) * strlen(s) + 1);
if (!newt->value) {
free(newt);
return false;
}
strncpy(newt->value, s, strlen(s) + 1);
newt->next = NULL;
if (q->size == 0)
q->head = newt;
else
q->tail->next = newt;
q->tail = newt;
q->size += 1;
return true;
}
```
### `q_remove_head()`
從 `head` 移除一個元素,成功時回傳 `true` 否則回傳 `false`
檢查 `q` 是否是 `NULL`
再來才是檢查 `head` 、 `tail` 為了 `q` 是 `NULL` 時 `dereference` 到 `NULL`
```c
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q)
return false;
if (q->size == 0)
return false;
list_ele_t *tmp = q->head;
if (sp) {
int maxlen = bufsize - 1 > strlen((q->head)->value)
? strlen((q->head)->value)
: bufsize - 1;
strncpy(sp, (q->head)->value, maxlen);
sp[maxlen] = '\0';
}
q->head = tmp->next;
q->size -= 1;
free(tmp->value);
free(tmp);
return true;
}
```
### `q_size()`
回傳 `q` 的元素個數
若 `q` 是 `empty` 或 `NULL` 回傳 `0` 否則回傳元素個數
```c
int q_size(queue_t *q)
{
return q ? q->size : 0;
}
```
### `q_reverse()`
將 `q` 中的元素順序反轉
```c
void q_reverse(queue_t *q)
{
if (!q)
return;
if (q->size <= 1)
return;
list_ele_t *prev = NULL;
list_ele_t *cur = q->head;
list_ele_t *next = NULL;
q->tail = q->head;
while (cur != NULL) {
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
q->head = prev;
}
```
### `q_sort`
利用的 sort 手法是 mergesort
因此新增 `merge` 與 `mergeSortList` 兩個函式
其中 `merge` 使用的是 iterative 版本
使用 recursive 版本會導致自動評分程式複雜度測試 fail
應該是重複呼叫函式的成本過高
```c
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
list_ele_t *result;
list_ele_t **tmp = &result;
while (l1 && l2) {
if (strcmp(l1->value, l2->value) < 0) {
*tmp = l1;
l1 = l1->next;
} else {
*tmp = l2;
l2 = l2->next;
}
tmp = &((*tmp)->next);
}
if (l1)
*tmp = l1;
if (l2)
*tmp = l2;
return result;
}
```
```c
list_ele_t *mergeSortList(list_ele_t *head)
{
if (!head)
return head;
if (!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 = mergeSortList(head);
list_ele_t *l2 = mergeSortList(fast);
return merge(l1, l2);
}
```
[參考文章](https://npes87184.github.io/2015-09-12-linkedListSort/)
## 使用Address Sanitizer
開啟 Address Sanitizer 後
執行 `qtest`
並在 cmd 輸入 help 會得到以下錯誤
```c
cmd> help
Commands:
# ... | Display comment
free | Delete queue
help | Show documentation
ih str [n] | Insert string str at head of queue n times. Generate random string(s) if str equals RAND. (default: n == 1)
it str [n] | Insert string str at tail of queue n times. Generate random string(s) if str equals RAND. (default: n == 1)
log file | Copy output to file
new | Create new queue
option [name val] | Display or set options
quit | Exit program
reverse | Reverse queue
rh [str] | Remove from head of queue. Optionally compare to expected value str
rhq | Remove from head of queue without reporting value.
show | Show queue contents
size [n] | Compute queue size n times (default: n == 1)
sort | Sort queue in ascending order
source file | Read commands from source file
time cmd arg ... | Time command execution
Options:
=================================================================
==19783==ERROR: AddressSanitizer: global-buffer-overflow on address 0x562cb765a3c0 at pc 0x562cb76437bd bp 0x7ffde7d1b7a0 sp 0x7ffde7d1b790
READ of size 4 at 0x562cb765a3c0 thread T0
#0 0x562cb76437bc in do_help_cmd /home/amagi/linux2021/lab0-c/console.c:307
#1 0x562cb76438d0 in interpret_cmda /home/amagi/linux2021/lab0-c/console.c:221
#2 0x562cb76440b5 in interpret_cmd /home/amagi/linux2021/lab0-c/console.c:244
#3 0x562cb76457f8 in run_console /home/amagi/linux2021/lab0-c/console.c:660
#4 0x562cb76423e5 in main /home/amagi/linux2021/lab0-c/qtest.c:780
#5 0x7f3263fd60b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#6 0x562cb763fb8d in _start (/home/amagi/linux2021/lab0-c/qtest+0x8b8d)
0x562cb765a3c1 is located 0 bytes to the right of global variable 'echo' defined in 'console.c:59:13' (0x562cb765a3c0) of size 1
SUMMARY: AddressSanitizer: global-buffer-overflow /home/amagi/linux2021/lab0-c/console.c:307 in do_help_cmd
Shadow bytes around the buggy address:
0x0ac616ec3420: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9
0x0ac616ec3430: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9
0x0ac616ec3440: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0ac616ec3450: 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
0x0ac616ec3460: 00 00 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9
=>0x0ac616ec3470: 01 f9 f9 f9 f9 f9 f9 f9[01]f9 f9 f9 f9 f9 f9 f9
0x0ac616ec3480: 04 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9
0x0ac616ec3490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac616ec34a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac616ec34b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac616ec34c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
Shadow gap: cc
==19783==ABORTING
```
從中可以發現是 `console.c` 有問題
看到 summary 的地方發現是 global variable '`echo`' 發生了錯誤
進入 `console.c` 觀察 `echo`
```c
/* Parameters */
static int err_limit = 5;
static int err_cnt = 0;
static bool echo = 0;
```
`echo` 型態是 `bool`
接下來繼續尋找 `echo` 的足跡
在函式 `init_cmd()` 中
```c
add_param("echo", (int *) &echo, "Do/don't echo commands", NULL);
```
函式 `set_echo` 中
```c
void set_echo(bool on)
{
echo = on ? 1 : 0;
}
```
其餘出現部份都是在 if 中因此不會改變到 `echo` 的值
其中 `set_echo()` 看起來沒什麼問題
因此聚焦在 `add_param`
```c
void add_param(char *name,
int *valp,
char *documentation,
setter_function setter)
{
param_ptr next_param = param_list;
param_ptr *last_loc = ¶m_list;
while (next_param && strcmp(name, next_param->name) > 0) {
last_loc = &next_param->next;
next_param = next_param->next;
}
param_ptr ele = (param_ptr) malloc_or_fail(sizeof(param_ele), "add_param");
ele->name = name;
ele->valp = valp;
ele->documentation = documentation;
ele->setter = setter;
ele->next = next_param;
*last_loc = ele;
}
```
功能是最終建立一個 element 並指派到 `last_loc`
type 是 `param_ptr`
因此前去觀察 `param_ptr`
在 `console.h` 中
```c
/* Integer-valued parameters */
typedef struct PELE param_ele, *param_ptr;
struct PELE {
char *name;
int *valp;
char *documentation;
/* Function that gets called whenever parameter changes */
setter_function setter;
param_ptr next;
};
```
從中觀察到對 `echo` 進行數值指派之處在 `valp` ,type 是 `int *`
但是傳入 `add_param` 的 `echo` 是被強制轉變成 `int *` 的 `bool`
導致後續使用到 `last_loc` 的地方
看到一開始標出的錯誤位置
```c
#0 0x562cb76437bc in do_help_cmd /home/amagi/linux2021/lab0-c/console.c:307
```
```c
static bool do_help_cmd(int argc, char *argv[])
{
cmd_ptr clist = cmd_list;
report(1, "Commands:", argv[0]);
while (clist) {
report(1, "\t%s\t%s", clist->name, clist->documentation);
clist = clist->next;
}
param_ptr plist = param_list;
report(1, "Options:");
while (plist) {
report(1, "\t%s\t%d\t%s", plist->name, *plist->valp, //<-- here 307
plist->documentation);
plist = plist->next;
}
return true;
}
```
`*plist->valp` 的部份出錯了
因此初步判斷是 `echo` 的 type 問題
因為 type `bool` 占的空間和 `int` 不一樣 (`bool` 1 byte `int` 4 bytes)
`%d` 會去存取的大小會是 `int` 的大小 所以存取身為 `bool` 的 `echo` 會超出記憶體範圍
修改成以下的樣子
```c
static int echo = 0;
```
而執行 `make test` 時也出現相同狀況
```c
amagi@amagi-MacBookAir:~/linux2021/lab0-c$ make test
scripts/driver.py -c
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, size, and sort
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head, reverse, size, and sort
--- trace-05-ops 5/5
+++ TESTING trace trace-06-string:
# Test of truncated strings
--- trace-06-string 6/6
+++ TESTING trace trace-07-robust:
# Test operations on NULL queue
--- trace-07-robust 6/6
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 6/6
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 6/6
+++ TESTING trace trace-10-malloc:
# Test of malloc failure on new
--- trace-10-malloc 6/6
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
--- trace-11-malloc 6/6
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
--- trace-12-malloc 6/6
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 6/6
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, reverse, and sort
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if q_insert_tail and q_size is constant time complexity
=================================================================
==19755==ERROR: AddressSanitizer: global-buffer-overflow on address 0x56197ae895e0 at pc 0x56197ae71ae8 bp 0x7ffe3caddf50 sp 0x7ffe3caddf40
READ of size 4 at 0x56197ae895e0 thread T0
#0 0x56197ae71ae7 in do_option_cmd /home/amagi/linux2021/lab0-c/console.c:369
#1 0x56197ae708d0 in interpret_cmda /home/amagi/linux2021/lab0-c/console.c:221
#2 0x56197ae710b5 in interpret_cmd /home/amagi/linux2021/lab0-c/console.c:244
#3 0x56197ae722e1 in cmd_select /home/amagi/linux2021/lab0-c/console.c:594
#4 0x56197ae7285b in run_console /home/amagi/linux2021/lab0-c/console.c:667
#5 0x56197ae6f3e5 in main /home/amagi/linux2021/lab0-c/qtest.c:780
#6 0x7f45471c70b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#7 0x56197ae6cb8d in _start (/home/amagi/linux2021/lab0-c/qtest+0x8b8d)
0x56197ae895e1 is located 0 bytes to the right of global variable 'simulation' defined in 'console.c:21:6' (0x56197ae895e0) of size 1
'simulation' is ascii string ''
SUMMARY: AddressSanitizer: global-buffer-overflow /home/amagi/linux2021/lab0-c/console.c:369 in do_option_cmd
Shadow bytes around the buggy address:
0x0ac3af5c9260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac3af5c9270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac3af5c9280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac3af5c9290: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9
0x0ac3af5c92a0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9
=>0x0ac3af5c92b0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9[01]f9 f9 f9
0x0ac3af5c92c0: f9 f9 f9 f9 00 00 00 00 01 f9 f9 f9 f9 f9 f9 f9
0x0ac3af5c92d0: 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
0x0ac3af5c92e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac3af5c92f0: 00 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9
0x0ac3af5c9300: 01 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 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
Shadow gap: cc
==19755==ABORTING
--- trace-17-complexity 0/5
--- TOTAL 95/100
```
觀察 `simulation`
```c
/* Some global values */
bool simulation = 0;
```
在 `init_cmd`
```c
add_param("simulation", (int *) &simulation, "Start/Stop simulation mode",
NULL);
```
型態 `bool` 並被強制轉換 `int *`
看起來與 `echo` 是相同問題
因此修改 `console.c` 以及 `console.h`
`console.c` :
```c
/* Some global values */
int simulation = 0;
```
`console.h` :
原
```c
/* Simulation flag of console option */
extern bool simulation;
```
修改後
```c
/* Simulation flag of console option */
extern int simulation;
```
最終執行結果:
```c
./qtest
cmd> help
Commands:
# ... | Display comment
free | Delete queue
help | Show documentation
ih str [n] | Insert string str at head of queue n times. Generate random string(s) if str equals RAND. (default: n == 1)
it str [n] | Insert string str at tail of queue n times. Generate random string(s) if str equals RAND. (default: n == 1)
log file | Copy output to file
new | Create new queue
option [name val] | Display or set options
quit | Exit program
reverse | Reverse queue
rh [str] | Remove from head of queue. Optionally compare to expected value str
rhq | Remove from head of queue without reporting value.
show | Show queue contents
size [n] | Compute queue size n times (default: n == 1)
sort | Sort queue in ascending order
source file | Read commands from source file
time cmd arg ... | Time command execution
Options:
echo 1 Do/don't echo commands
error 5 Number of errors until exit
fail 30 Number of times allow queue operations to return false
length 1024 Maximum length of displayed string
malloc 0 Malloc failure probability percent
simulation 0 Start/Stop simulation mode
verbose 4 Verbosity level
cmd> quit
Freeing queue
```
```c
make test
scripts/driver.py -c
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, size, and sort
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head, reverse, size, and sort
--- trace-05-ops 5/5
+++ TESTING trace trace-06-string:
# Test of truncated strings
--- trace-06-string 6/6
+++ TESTING trace trace-07-robust:
# Test operations on NULL queue
--- trace-07-robust 6/6
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 6/6
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 6/6
+++ TESTING trace trace-10-malloc:
# Test of malloc failure on new
--- trace-10-malloc 6/6
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
--- trace-11-malloc 6/6
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
--- trace-12-malloc 6/6
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 6/6
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, reverse, and sort
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if q_insert_tail and q_size is constant time complexity
Probably constant time
Probably constant time
--- trace-17-complexity 5/5
--- TOTAL 100/100
```
## 使用[Valgrind](https://valgrind.org/)
執行以下命令
```shell
make valgrind
```
會得到以下訊息
```c
# Explicitly disable sanitizer(s)
make clean SANITIZER=0 qtest
make[1]: Entering directory 'lab0-c'
rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o linenoise.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .linenoise.o.d *~ qtest /tmp/qtest.*
rm -rf .dudect
rm -rf *.dSYM
(cd traces; rm -f *~)
CC qtest.o
CC report.o
CC console.o
CC harness.o
CC queue.o
CC random.o
CC dudect/constant.o
CC dudect/fixture.o
CC dudect/ttest.o
CC linenoise.o
LD qtest
make[1]: Leaving directory 'lab0-c'
cp qtest /tmp/qtest.BK0T2K
chmod u+x /tmp/qtest.BK0T2K
sed -i "s/alarm/isnan/g" /tmp/qtest.BK0T2K
scripts/driver.py -p /tmp/qtest.BK0T2K --valgrind
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
==21222== 5 bytes in 1 blocks are still reachable in loss record 1 of 3
==21222== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==21222== by 0x4A4C50E: strdup (strdup.c:42)
==21222== by 0x11009A: linenoiseHistoryAdd (linenoise.c:1236)
==21222== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==21222== by 0x10C22C: main (qtest.c:769)
==21222==
==21222== 95 bytes in 19 blocks are still reachable in loss record 2 of 3
==21222== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==21222== by 0x4A4C50E: strdup (strdup.c:42)
==21222== by 0x11000E: linenoiseHistoryAdd (linenoise.c:1236)
==21222== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==21222== by 0x10C22C: main (qtest.c:769)
==21222==
==21222== 160 bytes in 1 blocks are still reachable in loss record 3 of 3
==21222== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==21222== by 0x11005A: linenoiseHistoryAdd (linenoise.c:1224)
==21222== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==21222== by 0x10C22C: main (qtest.c:769)
==21222==
--- trace-01-ops 6/6
...
```
以下還有更多訊息 不過大多數合 trace-01 一樣的問題
可以關注這幾段
```c
==21222== by 0x4A4C50E: strdup (strdup.c:42)
==21222== by 0x11009A: linenoiseHistoryAdd (linenoise.c:1236)
==21222== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==21222== by 0x10C22C: main (qtest.c:769)
```
追蹤出現問題的地方
`linenoiseHistory`
```c
int linenoiseHistoryAdd(const char *line)
{
char *linecopy;
if (history_max_len == 0)
return 0;
/* Initialization on first call. */
if (history == NULL) {
history = malloc(sizeof(char *) * history_max_len);
if (history == NULL)
return 0;
memset(history, 0, (sizeof(char *) * history_max_len));
}
/* Don't add duplicated lines. */
if (history_len && !strcmp(history[history_len - 1], line))
return 0;
/* Add an heap allocated copy of the line in the history.
* If we reached the max length, remove the older line. */
linecopy = strdup(line); //<-here 1236
if (!linecopy)
return 0;
if (history_len == history_max_len) {
free(history[0]);
memmove(history, history + 1, sizeof(char *) * (history_max_len - 1));
history_len--;
}
history[history_len] = linecopy;
history_len++;
return 1;
}
```
`linenoiseHistoryLoad`
```c
int linenoiseHistoryLoad(const char *filename)
{
FILE *fp = fopen(filename, "r");
char buf[LINENOISE_MAX_LINE];
if (fp == NULL)
return -1;
while (fgets(buf, LINENOISE_MAX_LINE, fp) != NULL) {
char *p;
p = strchr(buf, '\r');
if (!p)
p = strchr(buf, '\n');
if (p)
*p = '\0';
linenoiseHistoryAdd(buf); //<-here 1325
}
fclose(fp);
return 0;
}
```
`main`
```c
/* Trigger call back function(auto completion) */
linenoiseSetCompletionCallback(completion);
linenoiseHistorySetMaxLen(HISTORY_LEN);
linenoiseHistoryLoad(HISTORY_FILE);// <-here 769
```
而 `strdup` 在 `string.h`
still reachable 代表的是有東西還沒 free
主要原因是輸入方式
手動輸入命令和自命令歷史記錄檔案 `history` 讀取的輸入會不一樣
輸入命令會把命令寫進 `history`
在 `run_console` 中可以發現這一點
```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;
}
```
其中的 `has_infile` 條件判斷裡可以發現這一點
`linenoiseFree` 也是在沒有檔案的情況下作
因此推測此 memory error 與是否有讀檔有關
作以下實驗
自行輸入 `trace-eg.cmd` 中的命令 和 讀檔
讀檔
```shell
$ valgrind --leak-check=yes ./qtest -v 3 -f traces/trace-eg.cmd
cmd>
cmd> # Demonstration of queue testing framework
cmd> # Use help command to see list of commands and options
cmd> # Initial queue is NULL.
cmd> show
q = NULL
cmd> # Create empty queue
cmd> new
q = []
cmd> # Fill it with some values. First at the head
cmd> ih dolphin
q = [dolphin]
cmd> ih bear
q = [bear dolphin]
cmd> ih gerbil
q = [gerbil bear dolphin]
cmd> # Now at the tail
cmd> it meerkat
q = [gerbil bear dolphin meerkat]
cmd> it bear
q = [gerbil bear dolphin meerkat bear]
cmd> # Reverse it
cmd> reverse
q = [bear meerkat dolphin bear gerbil]
cmd> # See how long it is
cmd> size
Queue size = 5
q = [bear meerkat dolphin bear gerbil]
cmd> # Delete queue. Goes back to a NULL queue.
cmd> free
q = NULL
cmd> # Exit program
cmd> quit
Freeing queue
==29205== 5 bytes in 1 blocks are still reachable in loss record 1 of 3
==29205== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==29205== by 0x4A4C50E: strdup (strdup.c:42)
==29205== by 0x11009A: linenoiseHistoryAdd (linenoise.c:1236)
==29205== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==29205== by 0x10C22C: main (qtest.c:770)
==29205==
==29205== 91 bytes in 19 blocks are still reachable in loss record 2 of 3
==29205== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==29205== by 0x4A4C50E: strdup (strdup.c:42)
==29205== by 0x11000E: linenoiseHistoryAdd (linenoise.c:1236)
==29205== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==29205== by 0x10C22C: main (qtest.c:770)
==29205==
==29205== 160 bytes in 1 blocks are still reachable in loss record 3 of 3
==29205== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==29205== by 0x11005A: linenoiseHistoryAdd (linenoise.c:1224)
==29205== by 0x110C2D: linenoiseHistoryLoad (linenoise.c:1325)
==29205== by 0x10C22C: main (qtest.c:770)
==29205==
```
自行輸入
```shell
$ valgrind --leak-check=yes ./qtest
cmd>
cmd> show
q = NULL
cmd> new
q = []
cmd> ih dolphin
q = [dolphin]
cmd> ih bear
q = [bear dolphin]
cmd> ih gerbil
q = [gerbil bear dolphin]
cmd> it meerkat
q = [gerbil bear dolphin meerkat]
cmd> it bear
q = [gerbil bear dolphin meerkat bear]
cmd> reverse
q = [bear meerkat dolphin bear gerbil]
cmd> size
Queue size = 5
q = [bear meerkat dolphin bear gerbil]
cmd> free
q = NULL
cmd> quit
Freeing queue
```
因此在 `main` 中發生錯誤的地方加上條件判斷
```c
/* Trigger call back function(auto completion) */
if (!infile_name) {
linenoiseSetCompletionCallback(completion);
linenoiseHistorySetMaxLen(HISTORY_LEN);
linenoiseHistoryLoad(HISTORY_FILE); /* Load the history at startup */
}
```
重新 make
```c
$ make valgrind
# Explicitly disable sanitizer(s)
make clean SANITIZER=0 qtest
make[1]: Entering directory '/home/amagi/linux2021/lab0-c'
rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o linenoise.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .linenoise.o.d *~ qtest /tmp/qtest.*
rm -rf .dudect
rm -rf *.dSYM
(cd traces; rm -f *~)
CC qtest.o
CC report.o
CC console.o
CC harness.o
CC queue.o
CC random.o
CC dudect/constant.o
CC dudect/fixture.o
CC dudect/ttest.o
CC linenoise.o
LD qtest
make[1]: Leaving directory '/home/amagi/linux2021/lab0-c'
cp qtest /tmp/qtest.4RAvVj
chmod u+x /tmp/qtest.4RAvVj
sed -i "s/alarm/isnan/g" /tmp/qtest.4RAvVj
scripts/driver.py -p /tmp/qtest.4RAvVj --valgrind
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, size, and sort
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head, reverse, size, and sort
--- trace-05-ops 5/5
+++ TESTING trace trace-06-string:
# Test of truncated strings
--- trace-06-string 6/6
+++ TESTING trace trace-07-robust:
# Test operations on NULL queue
--- trace-07-robust 6/6
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 6/6
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 6/6
+++ TESTING trace trace-10-malloc:
# Test of malloc failure on new
--- trace-10-malloc 6/6
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
--- trace-11-malloc 6/6
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
--- trace-12-malloc 6/6
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 6/6
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, reverse, and sort
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if q_insert_tail and q_size is constant time complexity
Probably constant time
Probably constant time
--- trace-17-complexity 5/5
--- TOTAL 100/100
Test with specific case by running command:
scripts/driver.py -p /tmp/qtest.4RAvVj --valgrind -t <tid>
```