BatchNormalization論文閱讀與實作

gdb教學一基本操作

本篇文章修改參考這篇文章製作範例和教學,首先我們先寫一個有bug的程式factorial.c

基本操作

編譯程式給GDB

如果要給GDB除錯,一定要加上-g選項

1
gcc -g <any other flags e.g. -Wall> -o <file> <file.c>

開啟GDB session

在終端機輸入gdb和執行檔名稱即可開啟gdb session

1
2
3
4
5
6
7
8
9
gdb <program_name>
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from factorial...
(gdb)

run指令

用run指令執行程式。

1
(gdb) run

如果有程式有接收參數,可以直接放在run後面

1
(gdb) run <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) run < <data>

start指令

用start指令執行程式,並且在main函式的第一行停下來。

1
(gdb) start

如果有程式有接收參數,可以直接放在run後面

1
(gdb) start <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) start < <data>

quit指令

離開GDB可以用quit指令

1
(gdb) quit

範例

以下面程式為範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

用以下指令編譯

1
gcc -Wall -g -o factorial factorial.c

接下來開啟GDB session

1
gdb factorial

在GDB session中執行程式,會發現程式輸出錯誤的值

1
(gdb) run

如果要結束GDB session指令可以用quit。

1
(gdb) quit

中斷點

break指令

如果要放置中斷點在特定檔案的某一行,可以用break <filename>:<line number>指令

1
(gdb) break <filename>:<line number>

你也可以直接指定要放在哪一個function(gdb) break <filename>:<function>

1
(gdb) break <filename>:<function>

列出全部中斷點

要列出全部的中斷點可以用以下指令

1
(gdb) info break

delete指令

如果要刪除中斷點可以先用info break所有中斷點的號碼再刪除

1
(gdb) delete <breakpoint number>

範例

接著前面的例子,我們懷疑程式的第15行有錯誤,因此在GDB session中把中斷點放在第15行並且執行程式

1
2
(gdb) break 15
(gdb) run

我們應該會看到類似以下的輸出,你可以看到GDB輸出我們的第15行程式,代表現在程式停在第15行。
注意!!這時候程式並還沒有執行第15行。GDB告訴我們的是他下一行將要執行的程式。

1
2
3
4
5
6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
The factorial of 5 is 120.

Breakpoint 1, main () at factorial.c:15
15 f = factorial(n);

查看資料

print指令

印出變數

1
(gdb) print <variable_name>

info locals指令

印出當前所有的local variable,除了輸入函式的參數以外。

1
(gdb) info locals

info args指令

印出輸入給函式的參數

1
(gdb) info args

範例

接續前面的範例,們想查看程式運作到中斷點時變數的值。首先查看f和n的值

1
2
3
4
(gdb) print f
$1 = 120
(gdb) print n
$2 = 17

我們也可以用info locals來查看所有區域變數。

1
2
3
(gdb) info locals
n = 17
f = 120

查看程式運作

當程式在中斷點停下後,利用step, next and continue可以控制程式運作以便我們逐行觀察程式運作的情形。

step指令

執行下一行程式,如果下一行程式是呼叫function,gdb就會進到function裡面。

1
(gdb) step

next指令

執行下一行程式,與step不同的是,如果下一行是function,則將function執行完而不進入function。

1
(gdb) next

continue指令

執行程式直到碰到下一個中斷點

1
(gdb) continue

where指令

印出function call stack

1
(gdb) where

list指令

印出目前所在的行數以及前後各兩行。

1
(gdb) list

範例

接續前面的範例。我們想要查看我們的factorial函式如何運作的。因此用step指令進入factorial函式。

1
2
3
(gdb) step
factorial (n=17) at factorial.c:24
24 int f = 1;

接下來我們想一步一步看看factorial函式如和運作的

1
2
3
4
5
6
7
8
(gdb) next
25 int i = 1;
(gdb) next
26 while (i <= n) {
(gdb) next
27 f = f * i;
(gdb) next
28 i++;

除了不斷輸入重複的指令,你也可以直接按Enter,GDB會重複你上一個指令。
接下來我們預期執行到這裡,if應該要等於1

1
2
3
4
(gdb) print i
$2 = 1
(gdb) print f
$3 = 1

如果我們想查看目前在程式哪一行,可以用where指令來印出call stack

1
2
3
(gdb) where
#0 factorial (n=17) at factorial.c:28
#1 0x0000555555555196 in main () at factorial.c:15

如果要印出目前行數前後的程式可以用list

1
2
3
4
5
6
7
8
(gdb) list
23 int factorial(int n) {
24 int f = 1;
25 int i = 1;
26 while (i <= n) {
27 f = f * i;
28 i++;
29 }

我們也可以用continue指令和中斷點來加速除錯,首先先下一個中斷點在第28行。

1
2
(gdb) break 28
Breakpoint 2 at 0x5555555551e1: file factorial.c, line 28.

接下來用continue指令直接跳到這個中斷點

1
2
3
4
5
(gdb) continue
Continuing.

Breakpoint 2, factorial (n=17) at factorial.c:28
28 i++;

然後依次印出所有區域變數

1
info locals

我們不斷重複這個動作,可以發現前面都還運作正常,直到i=13時,答案開始出出錯了,如果繼續執行會發現答案越來越小,甚至變成負的。這個錯誤原因是int這個資料型態無法儲存這麼大的值,我們必須使用更大的資料型態才能儲存。

Call Stack

call Stack是由stack frames所組成。stack frames是用來儲存呼叫函式的時候函式的區域變數。如果函式內又呼叫另一個函式,新的一個stack frames會被放當前函式的stack frames的上面。當一個函式完成後,就會移除一個stack frames。

where指令

印出call stack並且包含檔名和行數。

up指令

往上移動stack一層frame

1
2
(gdb) up
(gdb) up <n_frames>

down指令

往下移動stack一層frame

1
2
(gdb) down
(gdb) down <n_frames>

frame指令

移動到指定的frame

1
(gdb) frame <frame_number>

範例

如果錯誤出現在函式庫的程式碼,這時候用call stack來debug就會很有用,我們可以利用call stack來尋找我們的程式在什麼地方出錯。用下面範例corrupted_linked_list.c來講解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
struct node *list1 = create_list(7);
print_list(list1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list){
struct node *curr = list;

while (curr != NULL) {
printf("%d->", curr->data);

curr == curr->next;
}
printf("X\n");
}

{:file=’corrupted_linked_list.c’}

首先我們編譯並且執行程式,我們會發現程式進入無窮迴圈,於是我們強制程式停下來。

1
2
3
4
$ gcc -g -o corrupted_linked_list corrupted_linked_list.c
./corrupted_linked_list
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->
**ctrl + c**

我們預期程式的輸出應該像下面這樣

1
2
./corrupted_linked_list
0->1->2->3->4->5->6->X

為了要了解程式到底錯在哪裡,我們在GDB session裡面執行程式。並且中斷程式

1
2
3
4
5
6
7
$ gdb corrupted_linked_list
(gdb) run
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0
**ctrl + c**
Program received signal SIGINT, Interrupt.
0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.

中斷後我們可以用where指令看一下目前所在的位置,輸出會類似如下

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) where
#0 0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1 0x00007fffff0a8bff in _IO_new_file_write (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=0x6020f0, n=512) at fileops.c:1263
#2 0x00007fffff0aa409 in new_do_write (to_do=512,
data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., fp=0x7fffff3f5620 <_IO_2_1_stdout_>) at fileops.c:518
#3 _IO_new_do_write (fp=0x7fffff3f5620 <_IO_2_1_stdout_>,
data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., to_do=512) at fileops.c:494
#4 0x00007fffff0a947d in _IO_new_file_xsputn (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=<optimised out>, n=2) at fileops.c:1331
#5 0x00007fffff07d92d in _IO_vfprintf_internal (s=0x7fffff3f5620 <_IO_2_1_stdout_>, format=<optimised out>, ap=ap@entry=0x7ffffffedf08) at vfprintf.c:1663
#6 0x00007fffff085899 in __printf (format=<optimised out>) at printf.c:33
#7 0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
#8 0x0000000000400628 in main () at corrupted_linked_list.c:17

可以看到程式被中斷在標準函式庫的程式,不過我們想看一看輸入函式庫的參數是什麼。因此我們可以用up指令從frame 0 移動到 frame 1。或者我們直接用frame指令移動到我們要的地方

1
2
3
(gdb) frame 7
#7 0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
50 printf("%d->", curr->data);

首先我們先看一下區域變數

1
2
(gdb) info locals
curr = 0x602010

可以用ptype指令查看curr的型態,可以發現他是一個node struct的指針。

1
2
3
4
5
(gdb) ptype curr
type = struct node {
int data;
struct node *next;
} *

我們dereference查看一下內容

1
2
(gdb) print *curr
$1 = {data = 0, next = 0x602030}

也可以查看其他的內容

1
2
3
4
5
(gdb) print *(curr->next)
$2 = {data = 1, next = 0x602050}
(gdb) print *(curr->next->next)
$3 = {data = 2, next = 0x602070}
(gdb)

Core Dumps

程式當機當下程式的狀態對於除錯十分有幫助,我們可以利用core dump檔來記錄這些狀態。對於一些不定時發生的錯誤,這些除錯資訊就十分珍貴了。

Core Dumps設定

首先查看Core Dumps記錄功能有沒有被開啟,如果回傳0代表沒有打開Core Dumps記錄功能

1
ulimit -c

用以下指令設定打開Core Dumps記錄功能

1
ulimit -c unlimited

通常Core Dumps檔產生的位置會記路在/proc/sys/kernel/core_pattern這個設定,用以下指令查看Core Dumps檔的位置。

產生Core Dump

當Core Dumps紀錄功能打開後,如果程式遇到Segmentation fault的錯誤,就會產生Core Dump檔。

用GDB查看Core Dump檔

要查看Core Dump檔可以用以下指令。注意當我們利用Core Dump檔來除錯的時候,程式實際上並沒有在運作,所以step, nextcontinue 這些指令這時候是沒有功能的。

1
gdb <binary-file> <core-dump-file>

範例

利用下面範例broken_linked_list.c將說明如何使用Core Dump除錯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list, int length);

int main(void){
int length1 = 7;
struct node *list1 = create_list(length1);
print_list(list1, length1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list, int length){
struct node *curr = list;
int i = 0;
while (i <= length) {
printf("%d->", curr->data);
curr = curr->next;
i++;
}
printf("X\n");
}

{: file=’broken_linked_list.c’}
編譯並且執行程式,可以看到程式出現Segmentation fault (core dumped)錯誤

1
2
3
$ gcc -g -o broken_linked_list broken_linked_list.c
$ ./broken_linked_list
Segmentation fault (core dumped)

接下來讀取Core Dump檔

1
gdb broken_linked_list core

GDB將會顯示程式出錯的位置

1
2
3
4
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055be9593e283 in print_list (list=0x55be96c20260, length=7)
at broken_linked_list.c:51
51 printf("%d->", curr->data);

從這裡可以知道我們在第51行正在嘗試存取一個非法的記憶體位置。因此我們可以推測可能是curr的位址不正確或者是data是不可以讀取的位址。於是先嘗試印出curr的值

1
2
(gdb) print curr
$1 = (struct node *) 0x0

可以看到curr是一個NULL址針。接下來我們在印出當前狀況的區域變數

1
2
3
(gdb) info locals
curr = 0x0
i = 7

可以看到i是7,也就是現在是第8次執行for迴圈,但是我們的Linking list只有7個節點,而且依照我們的建立Linking list的方式,第8個節點會是NULL址針,所以程式會出錯。
我們在檢查一下Linking list本身是否有問題。

1
2
(gdb) print *list
$2 = {data = 0, next = 0x55be96c20280}

可以看到Linking list的位址沒問題,因此可以更加確定問題就是迴圈多執行了一次。我們可以用list指令看一下程式現在所在的位置。

1
2
3
4
5
6
7
8
9
10
11
(gdb) list
46
47 void print_list(struct node *list, int length){
48 struct node *curr = list;
49 int i = 0;
50 while (i <= length) {
51 printf("%d->", curr->data);
52 curr = curr->next;
53 i++;
54 }
55 printf("X\n");

GDB Init File

如果單純使用GDB的指令,有些變數就會變得難以查看,例如如果想要查看linked list的所有成員就會變得很麻煩。而GDB提供讓使用者自定義指令讓我們可以容易議處想要的結果。

GDB User Initialization File

GEB啟動時會載入User Initialization File所記錄的指令,你可以建立一個新的User Initialization File,他的檔名是.gdbinit,放置的位置是home directory

1
~/.gdbinit

建立之後在檔案內加入下面指令,如此一來就可以在每一個專案下各自建立專屬的initialization file .gdbinit

GDB

專案自己的initialization file位在專案的跟目錄下,使用者可以自訂義指令或是GDB啟動的行為。

1
~/<file_path>/.gdbinit

基礎用法

例如你的專案想要每次使用GDB的時候都會放一個breakpoint在某一個function,你就可以在.gdbinit寫入下面這行。

1
break <function_name>

GDB腳本語法

定義命令

你可以用以下命令定義一個自訂義命令

1
2
3
define <command>
<code>
end

增加命令說明

你可以用以下命令為自訂義命令增加註解

1
2
3
document <command>
<information about the command>
end

如果要查看命令說明可以用以下命令

1
2
(gdb) help <command>
<information about the command>

命令參數

如果需要傳入參數給自訂義命令可以用以下方式

1
(gdb) <command> <arg0> <arg1> <arg2> ...

如果要在指令內使用參數可以用以下方式

1
2
3
4
5
$argc
$arg0
$arg1
$arg2
...

方便的內建變數

可以用以下方式定義

1
$<variable_name>

設定變數

用以下指令設定變數

1
set $<variable_name> = <value_or_expression>

if 聲明

用以下方式可以做if聲明

1
2
3
4
5
if <condition>
<code>
else
<code>
end

while loop

1
2
3
while <condition>
<code>
end

Printing

GDB的print語法跟c十分相似

1
printf "<format string>", <arg0>, <arg1>, ...

使用自訂義命令

使用者自訂義命令的用法跟內建指令的用法相同。

1
(gdb) <command> <arg0> <arg1> <arg2> ...

範例:除錯Linked lists

下面將以除錯Linked list作為範例linked_list.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
struct node *list1 = create_list(7);
print_list(list1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list){
struct node *curr = list;

while (curr != NULL) {
printf("%d->", curr->data);
curr = curr->next;
}
printf("X\n");
}

{:file=’linked_list.c’}

首先先把下面這行加入~/.gdbinit

1
set auto-load safe-path /

接下來撰寫自訂義命令,在專案根目錄也建立一個.gdbinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
define p_generic_list
set var $n = $arg0
while $n
print *($n)
set var $n = $n->next
end
end

document p_generic_list
p_generic_list LIST_HEAD_POINTER
Print all the fields of the nodes in the linked list pointed to by LIST_HEAD_POINTER. Assumes there is a next field in the struct.
end



define indentby
printf "\n"
set $i_$arg0 = $arg0
while $i_$arg0 > 10
set $i_$arg0 = $i_$arg0 - 1
printf "%c", ' '
end
end

{:file=’.gdbinit’}

在命令裡面,我們建立一個變數來儲存第一個參數($arg0),也就是linked list的指針

1
set var $n = $arg0

接下來印出 linked list的內容。

1
print *($n)

接下來把把linked list的指針指向下一個元素

1
set var $n = $n->next

運行結果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ gcc -g -o linked_list linked_list.c
$ gdb -q ./linked_list
Reading symbols from ./linked_list...
(gdb) br 18
Breakpoint 1 at 0x11c3: file linked_list.c, line 18.
(gdb) r
Starting program: /home/steven/tmp/gcc_practice/linked_list
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at linked_list.c:18
18 print_list(list1);
(gdb) p_generic_list list1
$1 = {data = 0, next = 0x5555555592c0}
$2 = {data = 1, next = 0x5555555592e0}
$3 = {data = 2, next = 0x555555559300}
$4 = {data = 3, next = 0x555555559320}
$5 = {data = 4, next = 0x555555559340}
$6 = {data = 5, next = 0x555555559360}
$7 = {data = 6, next = 0x0}
(gdb)

watch and display

watch可以監看一個變數,每當這個變數數值改變的時候就暫停程式。display 則是每當程式停止的時候顯示變數。

watch

使用以下指令設定想要監看的變數

1
(gdb) watch <variable_name>

查看Watchpoints

查看Watchpoints的方式跟查看breakpoints的方式一樣

1
(gdb) info breakpoints

移除Watchpoints

使用以下指令移除Watchpoints

1
(gdb) disable <watchpoint_number>

display

使用以下指令設定display

1
(gdb) display expression

查看所有display

查看所有display

1
(gdb) info display

移除display

用以下指令移除display

1
(gdb) delete display <display_number>

範例

以下將以計算階乘的程式factorial.c來做示範。程式在計算階層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

接著編譯程式並且用gdb讀取

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

首先先設定中斷點,可以看到n=5的時候晟是正常運作,讓嘗試繼續執行讓n=17

1
2
3
4
5
6
7
8
9
10
11
(gdb) br factorial
Breakpoint 1 at 0x11a5: file factorial.c, line 24.
(gdb) r
Starting program: ~/factorial
Breakpoint 1, factorial (n=5) at factorial.c:24
24 int f = 1;
(gdb) c
Continuing.
The factorial of 5 is 120.
Breakpoint 1, factorial (n=17) at factorial.c:24
24 int f = 1;

接下來設定watch和display,我們希望i初始化之後再設定watch和display,

1
2
3
4
(gdb) n
25 int i = 1;
(gdb) n
26 while (i <= n) {

然後設定watch和display

1
2
3
4
(gdb) watch f
Hardware watchpoint 2: f
(gdb) display i
1: i = 1

然後我們就可以觀察程式計算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 1
New value = 2
factorial (n=17) at factorial.c:28
28 i++;
1: i = 2
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 2
New value = 6
factorial (n=17) at factorial.c:28
28 i++;
1: i = 3
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 6
New value = 24
factorial (n=17) at factorial.c:28
28 i++;
1: i = 4
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 24
New value = 120
factorial (n=17) at factorial.c:28
28 i++;
1: i = 5
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 120
New value = 720
factorial (n=17) at factorial.c:28
28 i++;
1: i = 6
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 720
New value = 5040
factorial (n=17) at factorial.c:28
28 i++;
1: i = 7
(gdb) c

Continuing.

Hardware watchpoint 2: f

Old value = 5040
New value = 40320
factorial (n=17) at factorial.c:28
28 i++;
1: i = 8
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 40320
New value = 362880
factorial (n=17) at factorial.c:28
28 i++;
1: i = 9
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 362880
New value = 3628800
factorial (n=17) at factorial.c:28
28 i++;
1: i = 10
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 3628800
New value = 39916800
factorial (n=17) at factorial.c:28
28 i++;
1: i = 11
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 39916800
New value = 479001600
factorial (n=17) at factorial.c:28
28 i++;
1: i = 12
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 479001600
New value = 1932053504
factorial (n=17) at factorial.c:28
28 i++;
1: i = 13

我們可以觀察到當n=13的時候程式就開始出錯。

條件式Breakpoints

利用conditional breakpoints可以讓程式達到特定條件的時候才停下來。

設定條件式Breakpoints的方法

  1. 建立一個中斷點
    如同前面所教的方法先建立一個中斷點。

    1
    2
    (gdb) break <file_name> : <line_number>
    (gdb) break <function_name>
  2. 查看所有中斷點
    下面指令可以查看目前已經設定過的中斷點。

    1
    (gdb) info breakpoints
  3. 設定條件
    首先我們必須要知道中斷點的編號,並且用以下指令設定條件

    1
    (gdb) condition <breakpoint_number> condition
  4. 移除中斷點的停止條件
    如果想要移除中斷點的停止條件,可以用以下指令

    1
    (gdb) condition <breakpoint_number>

範例

下面將繼續沿用階層計算程式factorial.c來示範conditional breakpoints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

{:file=’factorial.c’}

編譯程式並啟動GDB

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

設定conditional breakpoints

我們已經知道程式在i <= 5 之前都正常運作,所以我們不必在確認i <= 5 之前的輸出結果。因此我們設定的條件是 i > 5。

1
2
3
4
5
$ gdb factorial
Reading symbols from factorial...done.
(gdb) br 28
Breakpoint 1 at 0x11bf: file factorial.c, line 28.
(gdb) condition 1 i > 5

開始除錯

接下來就可以執行程式並且觀察不同i之下的輸出變化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
(gdb) r
Starting program: ~/factorial
The factorial of 5 is 120.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 720
i = 6
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 5040
i = 7
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 40320
i = 8
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 362880
i = 9
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 3628800
i = 10
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 39916800
i = 11
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 479001600
i = 12
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 1932053504
i = 13
(gdb)

我們可以發現i=13之後數值就開始出現異常了。

除錯shared library

gcc印出所使用到的shared library

1
gcc -Wl,-t your_program.c -o your_program > ld_output.txt

gdb查看被載入的shared library

1
info share

每當有新的shared library被載入的時候就暫停

set stop-on-solib-events 1

https://jasonblog.github.io/note/gdb/li_yong_gdb_jin_xing_shared_library_de_chu_cuo.html

參考:
https://www.cse.unsw.edu.au/~learn/debugging/modules/gdb_basic_use/

實作yolov2(YOLO9000)

yolov2 詳細說明

https://senliuy.gitbook.io/advanced-deep-learning/chapter1/yolo9000-better-faster-stronger

Darknet-19與YOLOv2

目前Github上的YOLOv2已經被修改過,和論文上的描述並不一樣,必須參考最原始的cfg,而且必須要是voc的版本。
https://github.com/pjreddie/darknet/blob/c6afc7ff1499fbbe64069e1843d7929bd7ae2eaa/cfg/yolo_voc.cfg
{: .prompt-tip }

論文的第三章Faster提到Darknet-19是一個classification model,他是YOLOv2的基礎。
在Training for detection提到YOLOv2是刪掉Darknet-19的最後一個conv layers(以及他後面的其他layer),並且用3個3 x 3 x 1024 conv layer 最後搭配 1 x 1 x 類別數量的conv layer。

We modify this network for detection by removing the last convolutional layer and instead adding on three 3 × 3 convolutional layers with 1024 filters each followed by a final 1 × 1 convolutional layer with the number of outputs we need for detection.

Darknet源碼註解

region layer的biases就是anchors的值

實作Conv2dBatchLeaky

Conv

activation為linear時沒有做任何事 https://github.com/AlexeyAB/darknet/blob/0faed3e60e52f742bbef43b83f6be51dd30f373e/src/gemm.c#L2337

BatchNormal

問題:
在pytorch有momentum,在darknet有沒有?

Leaky

region layer

Pytorch實作

下面以圖片作為輸入來舉例

  • CONV2D: 輸入的tensor每一個維度所對應的是$$(N,C_{in},H,W)$$
    • $$N$$: 照片張數,一次一張照片為1
    • $$C_{in}$$:照片channel,彩色照片為3
    • $$H$$: 照片高度
    • $$W$$: 照片寬度

https://kikaben.com/yolo-v2-yolo9000/

參考:
yolov2架構講解(架構圖1x1 conv的地方有錯)
https://kikaben.com/yolo-v2-yolo9000/

完整yolov2實作(高參考價值)
https://github.com/Tencent/ObjectDetection-OneStageDet
https://zhuanlan.zhihu.com/p/45039781

http://yuenshome.space/timeline/2018-11/yolov2-region-source-code/
https://github.com/tztztztztz/yolov2.pytorch
https://github.com/gwinndr/YOLOv4-Pytorch/tree/master/model/layers

https://github.com/ruiminshen/yolo2-pytorch/blob/master/model/yolo2.py

韓文Darknet介紹
https://jjeamin.github.io/darknet_book/part3_src/batchnorm_layer.html

實作yolov1

計算layer輸出

image info{: w=”700” h=”200” }

  • 首先可以看到輸入的圖片是448 x 448,然後經過7 x 7 x 64 stripe 2 的conv layer,以及2 x 2 stripe 2 的Maxpool layer。
    如果直接計算輸出的dimension,會發現計算有問題!!因為$${ 輸入寬度 - kernel寬度 \over stripe} \ne 224$$。查詢後發現如果去看yolov1.cfg,第一層的padding=1,也就是這層conv有paddind。
    首先先看到darknet/src/parser.cparse_convolutional可以發現if(pad) padding = size/2;,也就是如果cfg的padding=1,padding的大小就是$$\lfloor {kernel \ size \over 2} \rfloor$$ 取整數。所以第一層的padding是$$\lfloor {7 \over 2} \rfloor$$取整數3。
    再去看darknet/src/convolutional_layer.cmake_convolutional_layer呼叫的convolutional_out_width,就可以看到詳細計算。conv的輸出尺寸是

$$\lfloor{ {輸入長(寬) + 2 \times padding - kernel \ 長(寬)} \over stride}\rfloor + 1 = \lfloor{447 \over 2}\rfloor + 1 = 224$$

  • 接下來是2 x 2 Maxpool layer,輸出尺寸為112,輸出channel是64!!不過如果直接對照圖看,會發現圖上寫112 x 112 x 192,這很可能是論文的圖寫錯了,因為如果用darknet幫你計算每一層的輸出的話應該是如下圖。
    image info{: w=”700” h=”200” }

  • 1 x 1 的conv稱為reduction layers

Pytorch實作

下面以圖片作為輸入來舉例

  • CONV2D: 輸入的tensor每一個維度所對應的是$$(N,C_{in},H,W)$$
    • $$N$$: 照片張數,一次一張照片為1
    • $$C_{in}$$:照片channel,彩色照片為3
    • $$H$$: 照片高度
    • $$W$$: 照片寬度

darknet的conv layer程式碼疑問:

1
for(i = 0; i < l.nweights; ++i) l.weights[i] = scale*rand_normal(); #209

其他紀錄

計算總共有多少個weights

layer定義

h:輸入高
w:輸入寬
c:輸入channel
n:輸出channel
size:kernel size
nweights = (c / groups) * n * size * size

darknet載入權重

src -> parser.c -> load_convolutional_weights

  1. 讀取biases,每一個conv的filter都有一個,如yolov1第一層有64個
  2. 讀取batch_normalize
    1. 讀取scales,數量等於輸出channel
    2. 讀取rolling_mean,數量等於輸出channel
    3. 讀取rolling_variance,數量等於輸出channel
  3. 讀取weights

計算weight數量

史丹佛cs231n這張圖解釋得非常清楚,權重的總數為
輸入channel x kernel 寬 x kernel 高 x 輸出channel

local layer

參考:

Dive into Deep learning
https://d2l.ai/chapter_convolutional-neural-networks/padding-and-strides.html#stride

CS231
https://cs231n.github.io/convolutional-networks/#comp

Deep learning
http://neuralnetworksanddeeplearning.com

caculate weights number : Convolution Demo
https://cs231n.github.io/convolutional-networks/

Batch-Normalization
https://towardsdatascience.com/batch-normalization-in-3-levels-of-understanding-14c2da90a338

Locally Connected Layers說明
https://www.cs.toronto.edu/~lczhang/aps360_20191/lec/w03/convnet.html

Understanding Convolutions
http://colah.github.io/posts/2014-07-Understanding-Convolutions/

local connected layer
http://sintesis.ugto.mx/WintemplaWeb/01Neural%20Lab/09Convolutional%20NN/10Locally%20Connected/index.htm

local connected layer 實作
https://github.com/pytorch/pytorch/pull/1583/files

local connected layer 實作
https://github.com/nsoul97/yolov1_pytorch/blob/main/code/model.py

local connected layer 實作
https://github.com/pytorch/pytorch/issues/499

GDB
https://cotonne.github.io/gdb/2019/07/14/gdb-beginner-guide.html

GDB https://condor.depaul.edu/glancast/373class/docs/gdb.html

Math Syntax
https://www.rapidtables.com/math/symbols/Basic_Math_Symbols.html

GDB指令:
p sizeof(array)/sizeof(*array) ptype array 印出array長度
印出指標array的長度
p *l.biases@10 印出array的前10個元素

CMake教學系列三建立CMake專案

下載範例

下面指令將會下載本文所需的範例

1
2
git clone https://gitlab.com/CLIUtils/modern-cmake.git
cd modern-cmake/examples/simple-project/

CMakeLists.txt解說

  • 在第29行add_library(MyLibExample simple_lib.cpp simple_lib.hpp)增加了一個名為MyLibExampletarget並且將會編譯一個MyLibExamplelibrary
  • 第34行增加了一個add_executable(MyExample simple_example.cpp)增加了一個名為MyExampletarget並且將會編譯一個MyExample的執行檔
  • 第38行target_link_libraries(MyExample PRIVATE MyLibExample)連接了MyLibExampleMyExample這兩個target,注意你必須先製作target才能夠連接他們

建置專案、編譯

接下來你可以用以下指令建置專案並且編譯。CMake會產生一個build資料夾,在裡面你應該會看到libMyLibExample.alibrary和MyExample執行檔。你可以查看CMakeCache.txt看有哪些cache variable,接下來用./build/MyExample執行MyExample執行檔

1
2
cmake -S . -B build
cmake --build build -j 8

常見錯誤

路徑中有空白

1
set(VAR a b v)

這個寫法會讓VAR變成一個list,並且擁有a b c三個元素,因此如果你的路徑像這樣,你的路徑就會被拆成兩段。

1
2
set(MY_DIR "/path/with spaces/")
target_include_directories(target PRIVATE ${MY_DIR})

解決方法就是在${MY_DIR}外面再包一個引號

1
2
set(MY_DIR "/path/with spaces/")
target_include_directories(target PRIVATE "${MY_DIR}")

除錯

印出變數

雖然message指令也可以印出變數,不過你有更好的選擇,cmake_print_propertiescmake_print_variables,記得要先include(CMakePrintHelpers)。如此一來你可以更加容易的印出target的屬性

1
2
3
4
5
6
include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
cmake_print_properties(
TARGETS my_target
PROPERTIES POSITION_INDEPENDENT_CODE
)

–trace-source 和 –trace-expand

--trace-source讓你可以指定只要看你想看的CMakeLists.txt--trace-expand變數全部展開,例如原本是

1
add_library(simple_lib ${SOURCES} ${HEADERS} )

加了--trace-expand變成

1
add_library(simple_lib simple_lib.c simple_lib.h )

下載這個範例,並且試看看cmake -S . -B build --trace-source=CMakeLists.txtcmake -S . -B build --trace-source=CMakeLists.txt --trace-expand有什麼不一樣

1
2
git clone git@github.com:hsf-training/hsf-training-cmake-webpage.git
cd hsf-training-cmake-webpage/code/04-debug

除錯CMake的find_…模組

沿用上面的範例,在這個範例你可以看到第16行有一個find_library(MATH_LIBRARY m)

1
2
git clone git@github.com:hsf-training/hsf-training-cmake-webpage.git
cd hsf-training-cmake-webpage/code/04-debug

試看看cmake -S . -B build --debug-find。記得要先清除build資料夾否會出現debug訊息。你也可以用CMAKE_FIND_DEBUG_MODE來針對你想要debug的find_…模組來除錯

1
2
3
set(CMAKE_FIND_DEBUG_MODE TRUE)
find_program(...)
set(CMAKE_FIND_DEBUG_MODE FALSE)

設定build types

如果你想要執行C++ debugger,你會需要設定很多flag,CMake提供四種build types來幫你設定這些flag。

  • CMAKE_BUILD_TYPE=Debug : 輸出所有除錯訊息
  • CMAKE_BUILD_TYPE=RelWithDebInfo : release build 不過有一些額外的除錯訊息
  • CMAKE_BUILD_TYPE=Release : 最佳化release build
  • CMAKE_BUILD_TYPE=MinSizeRel : minimum size release
    下面範例示範如何用CMake的Debug模式編譯,並且用GDB除錯
    1
    2
    3
    4
    cd hsf-training-cmake-webpage/code/04-debug
    cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
    cmake --build build-debug
    gdb build-debug/simple_example
    GDB指令
    1
    2
    3
    4
    5
    # GDB                
    break my_sin
    r
    watch sign
    c

尋找套件

1
find_package(MyPackage 1.2)

這個命令會首先尋找CMAKE_MODULE_PATH這份路徑清單,在這些路徑底下尋找FindMyPackage.cmake這個檔案,注意他是直接把find_package第一個參數MyPackage產生出FindMyPackage.cmake這個搜尋目標,所以如果我們寫成find_package(OpenCV 3),那搜尋目標就是FindOpenCV.cmake
如果找不到FindMyPackage.cmake他就會接著尋找MyPackageConfig.cmake,如果MyPackage_DIR存在的話也會搜尋這個路徑。
在CMake3.12+之後,如果你的套件不是安裝在系統預設路徑,你可以設定環境變數<PackageName>_ROOT讓CMake搜尋。以下以Bash命令設定環境變數為例

1
export HDF5_ROOT=$HOME/software/hdf5-1.12.0

或者是設定CMAKE環境變數,以下以Bash命令設定環境變數為例

1
export CMAKE_PREFIX_PATH=$HOME/software/hdf5-1.12.0:$HOME/software/boost-1.74.0:$CMAKE_PREFIX_PATH

FindPackage.cmake

這是舊方法(MODULE方法),這裡有CMake提供的FindPackage清單

PackageConfig

這是由package開發者所提供,簡而言之如果你是package開發者,你應該提供<package>Config.cmake並且自行維護。

CMake結合PkgConfig

參考:
https://hsf-training.github.io/hsf-training-cmake-webpage/08-debugging/index.html

https://hsf-training.github.io/hsf-training-cmake-webpage/09-findingpackages/index.html

現代CMake觀念
https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/

CMake結合PkgConfig
https://stackoverflow.com/a/74038236

CMake教學系列二 CMake的觀念

在第一篇我們已經學會如何編譯單一檔案的C++程式,接下來將介紹一些CMake的觀念

關於Targets

在CMake有兩種方式建立targettarget的名稱必須是唯一的

  • add_executable(myexample simple.cpp) : 建立了一個名為myexample的target,並且命名輸出的執行檔為myexample
  • add_library(mylibrary simplelib.cpp) : 建立了一個名為myexample的target,並且命名輸出的函式庫為myexample
    target很像程式裡面物件的概念,他擁有許多屬性,例如SOURCES在這個範例他會擁有simple.cpp這個檔案,target所有的屬性可以在這裡查到

特別的targets

有些C++函式庫只有標頭檔(例如函式庫Eigen),這種情況下你沒辦法真正的輸出一個library檔,但是他又被製作成target讓其他target使用。這種函式庫我們叫做interface libraries而再CMake我們可以用下面指令把他製作成target。注意到跟前面不同的是他只需要設為INTERFACE屬性而且他不需要輸入*.cpp檔。

1
add_library(some_header_only_lib INTERFACE)

另一種狀況是你要直接使用預先編譯好的pre-built library,這種情況你也不會有*.cpp可以給CMake編譯。這種library在CMake裡我們稱為imported library,我們可以用關鍵字IMPORTED來告訴CMake。

1
add_library(some_library STATIC IMPORTED)

連接

一旦你有了target就可以用target_link_libraries連接所需要的東西,連接的時候有PUBLIC, PRIVATE, 和 INTERFACE三種屬性可以選擇,他有點像程式語言中的存取控制。如果TargetB引用TargetA,則TargetA的PUBLIC屬性都會傳遞給TargetB。

範例1: Include directories

  • target_include_directories(TargetA PRIVATE mydir)連接TargetA和mydir資料夾屬性為PRIVATE,這時候TargetA的INCLUDE_DIRECTORIES屬性就會包含mydir資料夾
  • target_include_directories(TargetA INTERFACE mydir)連接TargetA和mydir資料夾屬性為INTERFACE,這時候TargetA的INTERFACE_INCLUDE_DIRECTORIES屬性就會包含mydir資料夾
  • target_include_directories(TargetA PUBLIC mydir)連接TargetA和mydir資料夾屬性為PUBLIC,則INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES都會包含mydir資料夾

CMake變數

為了方便起見,接下來的範例會直接執行example.cmake檔,而不是建立一個CMakeLists.txt。首先你需要先建立一個example.cmake,如果要執行example.cmake可以利用CMake的 -p 選項。這樣可以節省去許多編譯設定,也比較容易實驗

1
2
# Assuming you have a file called example.cmake:
cmake -P example.cmake

Local variables

example.cmake輸入以下指令,然後執行cmake -P example.cmake就可以看到終端機輸出你的變數。在這裡set指令設定變數,message指令印出變數,這裡我們使用的是STATUSmessage,還有其他的狀態你可以到官網查看

1
2
set(MY_VARIABLE "I am a variable")
message(STATUS "${MY_VARIABLE}")

{: file=’example.cmake’}

Cached variables

cached variables在CMake十分重要,通常我們會用CMake圖形介面或是CMake命令介面設定許多cached variables,這些變數都會被寫到CMakeCache.txt檔案裡面。當你執行CMake的時候CMake會先讀取這些Cached variables。下面這個範例會設定一個Cached variables,不過因為我們用-P執行*.cmake,所以不會真的輸出一個CMakeCache.txt,你可以參考上一篇的範例觀察CMake如何產生CMakeCache.txt

1
2
set(MY_VARIABLE "I am a variable")
message(STATUS "${MY_VARIABLE}")

{: file=’example.cmake’}
而因為Cached variables幾乎都是可以讓使用者設定的選項,所以有一個更方便的指令option

1
option(MY_OPTION "On or off" OFF)

Other variables

  • 你可以藉由$ENV{my_env}來取得環境變數my_env的值,你也可以利用if(DEFINED ENV{my_env})來檢查my_env這個環境變數是不是有被設定(注意這個指令沒有$)
  • target的屬性其實也是一種變數,你可以利用get_propertyset_property,或者是get_target_propertiesset_target_properties來查看和設定target屬性,除此之外還有CMake本身的屬性,可以從cmake-properties這份清單查詢。

Target properties and variables

你已經知道target的屬性可以控制target的行為,如果你仔細觀察會發現,有許多target屬性(例如CXX_EXTENSIONS)會有一個對應的CMake變數,並且以CMAKE_為開頭(例如CMAKE_CXX_EXTENSIONS),這些CMake變數是用來初始化對應的target屬性用的。因此你可以先設定這些CMake變數,這樣就可以快速設定target對應屬性的初始值。

搜尋工具

CMake有一些方便的glob指令可以用來處理string

1
file(GLOB OUTPUT_VAR *.cxx)

在這裡要提到一個很重要的flagCONFIGURE_DEPENDS(CMake 3.12+),如果沒有這個flag則在重新執行建置的時候,cmake不會再次用glob去搜尋,如此一來如果這些被搜尋的資料夾放入的新的檔案也不會被CMake發現。因此如果你想要在每一次建置專案的時候都重新搜尋,記得加上這個flag

1
file(GLOB OUTPUT_VAR *.cxx CONFIGURE_DEPENDS)

參考:
CMake Fundamentals Part 4
https://jeremimucha.com/2021/02/cmake-fundamentals-part4/

Modern CMake is like inheritance
https://kubasejdak.com/modern-cmake-is-like-inheritance

CMake doc : Importing Libraries
https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html#importing-libraries

https://cliutils.gitlab.io/modern-cmake/chapters/basics.html

https://hsf-training.github.io/hsf-training-cmake-webpage/04-targets/index.html

https://hsf-training.github.io/hsf-training-cmake-webpage/05-variables/index.html

安裝Vagrant製作測試環境

安裝

Ubuntu22.04安裝Vagrant

1
2
3
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vagrant

安裝VirtualBox
/etc/apt/sources.list最下面加上這一行

1
deb [arch=amd64 signed-by=/usr/share/keyrings/oracle-virtualbox-2016.gpg] https://download.virtualbox.org/virtualbox/debian jammy contrib

然後下載和註冊public key

1
wget -O- https://www.virtualbox.org/download/oracle_vbox_2016.asc | sudo gpg --dearmor --yes --output /usr/share/keyrings/oracle-virtualbox-2016.gpg

顯示fingerprint,應該要為B9F8 D658 297A F3EF C18D 5CDF A2F6 83C5 2980 AECF

1
gpg --show-keys --with-fingerprint /usr/share/keyrings/oracle-virtualbox-2016.gpg

最後安裝

1
2
sudo apt-get update
sudo apt-get install virtualbox-6.1

建立一個Ubuntu20.04 box

1
2
3
vagrant init ubuntu/focal64
vagrant up
vagrant ssh

x11 forwarding

https://jcook0017.medium.com/how-to-enable-x11-forwarding-in-windows-10-on-a-vagrant-virtual-box-running-ubuntu-d5a7b34363f

共享資料夾

https://developer.hashicorp.com/vagrant/tutorials/getting-started/getting-started-synced-folders

參考:
安裝VirtualBox
https://www.virtualbox.org/wiki/Linux_Downloads

顯示gpg fingerprint
https://superuser.com/a/1747762