My code works, I don’t know why.

國王的耳朵是驢耳朵

Makefile 自動將產生的檔案指定到特定目錄

| Comments

Mar/13/2014已更正錯誤:原本錯誤分析請看這邊

之前分享了Makefile header file dependency問題解決方式之一,裏面提到把.o 和.d 指定產生到特定目錄,而不是散落各處的改進方向。過了幾天網路上看到別人的解法,手癢自己也來玩一下。

範例程式細節在這邊,不想看code只要知道每個檔案都有參考到某個自訂的header file就好了。

這次加碼一下,把檔案散落在不同目錄如下

tree view
1
2
3
4
5
6
7
8
9
10
$ tree
.
├── include
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── liba.c
│   └── libb.c
├── Makefile
└── test.c

Makefile更改如下

Makefile
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
CFLAGS=-g -MMD -I ./include

# binaries
LIB_SRC=$(shell ls libs/*.c)
SRCS= $(LIB_SRC) test.c

OBJS = $(patsubst %.c, %.o, $(SRCS))

# Output
OUT_DIR = build
OUT_OBJS=$(addprefix $(OUT_DIR)/, $(OBJS))
DEPS = $(patsubst %.o, %.d, $(OUT_OBJS))

# Build Rules
TARGET=test

$(OUT_DIR)/$(TARGET): $(OUT_OBJS)
  $(CC) -o $@ $^

$(OUT_DIR)/%.o:%.c
  mkdir -p $(dir $@)
  $(CC) $(CFLAGS) -c $< -o $@


clean:
  rm -rf $(OUT_DIR)

-include $(DEPS)

整個Makefile主要是

  1. 找出所有要編譯的c檔案並放在變數SRCS
  2. 把變數SRC中的.c換成.o並存到變數OBJS
  3. 再把變數OBJS裏面的字串每一個都加上build/的prefix並存到變數OUT_OBJS
  4. 再把OUT_OBJS中的.o換成.d並存到變數DEP
  5. 編譯.c到.o的時候,先產生對應於C code的目錄並將.o .d存到裏面

參數說明如下

  • -MMD請參考這邊
  • shell請參考這邊
  • patsubst請參考這邊
  • addprefix,照字面就是加上prefix
  • dir,取得檔案的dir
  • $@
    • Makefile的內建巨集,代表target
  • $<
    • Makefile的內建巨集,代表第一個prerequisite
  • - *告訴make 忽略失敗
  • %.o:%.c
    • Pattern Rules
    • %表示萬用字元,而prerequisite的%會和target的% match到的相同。所以就是說所有.o結尾的檔案depends on .c檔案時要做下面的事
    • 舉例來說$(OUT_DIR)/%.o:%.c,表示build/xxx/file1.o 會依賴對應到的xxx/file1.c
  • 其實重點是mkdir -p $(dir $@)會自動在build產生對應的目錄存放.d和.o

跑起來結果如下

result
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
$ make
mkdir -p build/libs/
cc -g -MMD -I ./include -c libs/liba.c -o build/libs/liba.o
mkdir -p build/libs/
cc -g -MMD -I ./include -c libs/libb.c -o build/libs/libb.o
mkdir -p build/
cc -g -MMD -I ./include -c test.c -o build/test.o
cc -o build/test build/libs/liba.o build/libs/libb.o build/test.o

$ tree
.
├── build
│   ├── libs
│   │   ├── liba.d
│   │   ├── liba.o
│   │   ├── libb.d
│   │   └── libb.o
│   ├── test
│   ├── test.d
│   └── test.o
├── include
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── liba.c
│   └── libb.c
├── Makefile
└── test.c

參考資料

Makefile Header File Dependency問題

| Comments

自幹Makefile的時候常常忘記把header files加到Prerequisite然後編譯的時候就發生非預期的錯誤。之前整理GNU: The C Preprocess導讀有看到gcc/cpp(C Preprocessor)的-MMD參數,但是還不清楚怎麼使用。直到看到Using g++ with -MMD in makefile to automatically generate dependencies才知道怎麼玩。手癢自己也來弄一個驗證一下。

測試檔案及程式碼如下 不想看code只要知道每個檔案都有參考到某個自訂的header file就好了。

liba.h
1
2
3
4
5
#ifndef LIBA_H_2013
#define LIBA_H_2013
void test_liba(void);
void from_liba(void);
#endif
liba.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "libb.h"
#include <stdio.h>

void test_liba(void)
{
    from_libb();
}

void from_liba()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}
libb.h
1
2
3
4
5
#ifndef LIBB_H_2013
#define LIBB_H_2013
void test_libb(void);
void from_libb(void);
#endif
libb.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "liba.h"
#include <stdio.h>

void test_libb(void)
{
    from_liba();
}

void from_libb()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}
test.c
1
2
3
4
5
6
7
8
9
10
#include "libb.h"
#include "liba.h"
#include <stdlib.h>
int main(void)
{
    test_liba();
    test_libb();

    return 0;
}

先來複習一下-MMD的效果

執行-MMD 結果
1
2
3
4
5
$ gcc -MMD -c test.c
$ ls test.*
test.c  test.d  test.o
$ cat test.d
test.o: test.c libb.h liba.h

剩下就是想辦法讓Makefile吃到*.d的內容,因此Makefile長的就像這樣

Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CFLAGS=-g -MMD

SRCS= test.c liba.c libb.c
OBJS = $(patsubst %.c, %.o, $(SRCS))
DEPS = $(patsubst %.o, %.d, $(OBJS))

TARGET=test

$(TARGET): $(OBJS)
  gcc -g -o $@ $^

clean:
  rm -rf *.o *.a *~ $(TARGET) $(OBJS) $(DEPS)

-include $(DEPS)

Makefile說明:

  • patsubst
    • Makefile 提供的函數,用在pattern 替換,所以上面做了下列的代換
    • .c轉成.o
    • .o轉成.d
  • $@
    • Makefile的內建巨集,代表target
  • $^
    • Makefile的內建巨集,代表prerequisite
  • -
    • 告訴make 忽略失敗,trick就在這邊

Trick就是上面提到的-,第一次make時一定.d檔案不存在,因此不使用-就一定編不過。第一次編過之後因為.d已經產生,所以header file的dependecy也被處理好了。

其實上面的Makefile可以做更多改進,當project檔案數量大到某種程度就可以感覺改進的效果,例如

  • .o 和.d 指定產生到特定目錄,而不是散落各處
  • 加上distclean,只有在這種情況下才去刪除*.d的檔案

筆記:Priority Inversion on Mars

| Comments

  • Priority Inversion on Mars投影片

  • 筆記

    • Readers–writers problem
    • Synchronization: Basic Rules
      • Process/thread的lock絕對不可互相等待
      • 當lock的順序無法確定時,使用 try_lock or try_unlock 以及對應的恢復方法
      • mutex是critical section專用,除了critical section外process/thread要等待其他的process做完才做事這樣的行為請用semaphore
    • Live lock
      • Wiki範例:兩個有禮貌的人在長廊中互相讓路,讓來讓入大家都卡在長廊裡面。
    • Priority inversion現象之一是在waiting queue中高優先權的thread/process等待低優先權的process/thread裡面的mutex release的現象
    • ASI: Atmospheric Structure Investigation
    • MET: Meteorology
    • Bounded priority inversion
      • 高優先權的process/thread等待進入critical section,該critical section目前由低優先權的process/thread佔用中。因此只要低優先權的process/thread離開該critical section後高優先權的process/thread便可繼續執行
    • Unbounded priority inversion
      • 高優先權的process/thread等待進入critical section,該critical section目前由低優先權的process/thread佔用中
      • 不幸的是,當低優先權process/thread還在critical section執行的時候,被切換到中優先權的process/thread由於高優先權的process/thread被block,而低優先權的process/thread一定會被中優先權的process/thread搶走執行權。最壞的狀況就是之後就只剩中優先權的process/thread被執行
    • 解法?
      • Priority inheritance
        • 當高優先權的process/thread要進入critical section發現該section以被低優先權的process/thread佔用時,系統暫時將該低優先權的process/thread調整到高優先權直到該低優先權的process/thread離開critical section
          • 看來可以解Unbounded priority inversion,bounded priority inversion應該還是本質無法解掉?
    • 除錯觀點
      • 火星探測車錯誤發生的地方是在select的系統實作上有用到mutex,而低優先權的priority使用該API
      • 直接從火星打開偵錯訊息傳回地球太過不切實際。後來確認在地球可以複製錯誤後全力追蹤context switch, 中斷和sync的部份

筆記:from Source to Binary: How GNU Toolchain Works

| Comments

出處: from Source to Binary: How GNU Toolchain Works

筆記

  • 微言大意:一個單純的描述背後隱含大量的細節和操作
    • printf放在哪裡,怎麼被系統呼叫?
    • main()函數怎麼被呼叫?
    • #include到底做了什麼事?
  • 傳統程式碼從Compile到執行 階段每段的最佳化都和上下階段無關,上面的資訊下面的無法存取
    • Compiler 知道原始碼的結構,但是沒有變數和函數怎麼放置的資訊
    • Linker 和C ompiler相反;知道變數和函數怎麼放置,但是沒有原始碼的結構的資訊
  • 一個source code從編譯到產生binary使用到的GNU toolchain
    • cpp: 處理巨集產生*.i檔案
    • cc1: 產生*.s (組合語言)檔案
    • as: 產生*.o 檔案
    • collect2: 沒放在PATH內,Ubuntu 12.04 放在/lib/gcc/x86_64-linux-gnu/4.6/collect2。Wrap linker and generate constructor code if needed.
    • ld: 吃link script,library, C runtime以及*.o,產生binary *
  • gcc
    • GNU compiler collection
    • compiler driver
    • compile時幫使用者呼叫cpp, cc1, as ….等程式並處理對應的參數
  • Intermediate Representation在gcc也有不同的leve
    • High Level : GENERIC (Syntax Tree Style IR)
    • Middle Level : Gimple (Tree Style IR, SSA form)
    • Low Level : RTL (List Style IR, Register Based)
  • SSA: Static Single Assignment
    • 每次的assign expression將變數加上版本號碼
      • a = b + 1; a++ ; b++; 變成
      • a1 = b1 + 1; a2++; b2++;
    • 如果變數assign expression是條件式的結果,使用Φ函數從集合中選擇
      • if (cond) { a = 1} else { a = 2 }變成
      • if (cond1) { a1 = 1} else { a2 = 2 } => a3 = Φ (a1, a2)
    • SSA的優點 (pass,每個應該是大哉問)
  • GCC最佳化
  • as的任務
    • 產生obj 檔案
    • 讓linker resolve symbol: ex: test.c 呼叫了printf(“hello\n”);,printf在哪裡?
      • 產生symbol table
      • 產生relocation table
  • symbol,用來協助linker找到或連結變數或是函數正確的位址
    • 先標記檔案內的變數或函數是internal的還是external的
  • binary分類
    • obj file
      • 不需要處理symbol和relocation問題
    • 執行檔: linker要把需要的function或變數從*.o檔案和在一起,或是動態使用symbol。
      • static link
        • 要處理symbol和relocation問題
      • dynamic link
        • 要處理symbol和relocation問題
        • 要處理動態呼叫library的問題
    • shared library
      • 要處理symbol和relocation問題
      • 要處理動態呼叫library的問題
      • 使用Position independent code
        • 載入時才確認address
        • global variable使用base + offset方式
        • 會有overhead
  • dynamic linker
    • 優點
      • 不同process可共用dynamic library 可共用的部份如程式碼(應該要thread safe?),想像不同process呼叫printf,而printf程式碼只需要一份放在shared library-> 節省記憶體
      • 系統只放一份shared library -> 節省空間
      • 更新library不需要重邊執行檔
      • Load on demand
    • 執行檔dynamic Link 步驟
      • Compile time
        • ld -> 從shared linker取得relocation和symbol資訊,並將資訊放到binary執行檔內
      • runtime:
        • 使用者執行程式
        • 系統從程式中取得dynamic linker路徑。
        • dynamic linker會把shared library相關的檔案如libc.so的.data和.text relocate到記憶體中
        • resolve相關的symbol
        • 開始執行程式
    • Unix世界中,dynamic linker屬於C library
Ubuntu 12.04 64-bit dynamic linker
1
2
3
4
5
$ ls /lib64/ld-linux-x86-64.so.2  -l
ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.15.so*

$ file /lib/x86_64-linux-gnu/ld-2.15.so
/lib/x86_64-linux-gnu/ld-2.15.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x930bb48366d22fbd8e002ef1c09f3061a506b43e, stripped

F9-Kernel初探 - Build System

| Comments

MACRO

Platform module

  • -D __CHIP__=xxx
  • -D'INC_PLAT(x)=<platform/__CHIP__/x>'
  • #include INC_PLAT(gpio.h)
1
2
$ find |grep gpio.h
./include/platform/stm32f4/gpio.h

compiler front end options

  • -std=gnu99
  • -isystem
  • -nostdlib
  • -ffreestanding
  • -mcpu=cortex-m4
  • -mthumb
  • -mthumb-interwork
    • Specify that the code has been generated with interworking between Thumb and ARM code in mind.
  • -Xassembler -mimplicit-it=thumb -mno-sched-prolog -mno-unaligned-access
  • -Wdouble-promotion
  • -fsingle-precision-constant
  • -fshort-double
  • -fno-toplevel-reorder
  • -fdata-sections
  • -ffunction-sections
  • -g3
  • -Wall
  • -Werror
  • -Wundef
  • -Wstrict-prototypes
  • -Wno-trigraphs
  • -fno-strict-aliasing
  • -fno-common
  • -Werror-implicit-function-declaration
  • -Wno-format-security
  • -fno-delete-null-pointer-checks
  • -Wdeclaration-after-statement
  • -Wno-pointer-sign
  • -fno-strict-overflow
  • -fconserve-stack
  • -MMD
  • -MF

Build directory order

  1. platform/stm32f4/
  2. board/discoveryf4/
  3. platform/
  4. kernel/lib/
  5. kernel/
  6. user/
  7. user/lib/l4/platform/
  8. user/lib/io/
  9. user/apps/

.config

  • Platform related
  • Limitation
  • Kernel timer
  • Flexiable page
  • Thread
  • KIP
  • Kernel debug

Build binaries

Root Makefile

  • Options
    • BOARD
    • PROJECT
  • Decide which files to compiled and linked

Implicit Rules

  • CC
    • mk/generic.mk
  • Loader
    • mk/loader.mk

Questions

  • Makefile
    • eval command?

Makefile結合Command Line輸出結果範例

| Comments

測試大檔案支援順手玩了一下Makefile撈command line 結果,整理如下

  • $(shell command)
    • 跑command,回傳command 輸出畫面,類似command line的$(command)或是`command`
  • $(warning)
    • 顯示警告訊息,這邊是單純拿來顯示
  • ifeq字串比較記得不要亂加"

範例如下

Makefile example
1
2
3
4
5
6
7
8
# Warning! Not sure if there is any compatilbe problem, and surely 
# not worked on cross compilation!
IS_64=$(shell file /bin/ls|grep -o 64-bit)

ifneq ($(IS_64),64-bit)
$(warning "Set -D_FILE_OFFSET_BITS=64")
CFLAGS+=-D_FILE_OFFSET_BITS=64
endif

Linux 檔案的hole

| Comments

大綱

檔案的hole檔案中連續的null 字元。

當lseek指定的offset (假設叫o_lseek)超過目前檔案最後的offset(o_end)時,多出來的delta系統並不會寫入到實體空件,而是有人要讀取這段空間時會讀到連續的null字元。也就是說會有檔案大小遠大於實際佔用的儲存空間的特性。

一般來說,hole會應在sparse的檔案,最典型的的例子就是Virtual machine的image檔案。

範例

測試程式行為描述

  1. 讀入任意字串
  2. 將字串寫入檔案,每個字元間隔1G的空間

執行結果

請注意檔案大小和目錄大小的差異

result
1
2
3
4
5
6
7
8
9
10
$ ./create_hole_file
Enter any text: test111
Get test111
, used to create test hole file

$ ls -lh file_in_the_hole
-rw------- 1 nobody nobody 7.1G Dec 31 14:24 file_in_the_hole

$ du -sh .
128K  .

Makefile

Makefile
1
2
3
4
5
6
7
8
9
10
11
12
CFLAGS=-g -Wall -pedantic
LDFLAGS=

TARGET=create_hole_file
HOLE_FILE_NAME=file_in_the_hole

all: $(TARGET)
create_hole_file: create_hole_file.c
  gcc -o $@ $(CFLAGS) $^ -DHOLE_FILE_NAME="\"$(HOLE_FILE_NAME)\""

clean:
  rm -rf *.o *~ $(TARGET) $(HOLE_FILE_NAME)

程式

create_hole_file.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
57
58
59
60
61
62
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#ifndef HOLE_FILE_NAME
#error You need to define HOLE_FILE_NAME
#endif

#define MAX_CHAR_LINE  (256)
#define HOLE_GAP       (1024*1024*1024) /* 1G */
int main(void)
{
    char line[MAX_CHAR_LINE] = {0};
    int rval = 0;
    int fd   = 0;
    int i    = 0;

    off_t   lseek_offset = 0;
    ssize_t write_size   = 0;

    /* Open file */
    fd = open(HOLE_FILE_NAME, O_CREAT | O_WRONLY, S_IRUSR  | S_IWUSR);
    if (fd == -1) {
        fprintf(stderr, "%d: %s\n", __LINE__, strerror(errno));
        return -1;
    }

    /* Get string from stdin */
    fprintf(stderr, "Enter any text: ");
    fgets(line, MAX_CHAR_LINE, stdin);

    fprintf(stderr, "Get %s, used to create test hole file\n", line);

    /* Implent hole */
    for (i = 0; i < strlen(line); i++) {
        write_size = write(fd, &line[i], sizeof(char));
        if (write_size == -1) {
            fprintf(stderr, "%d: %s\n", __LINE__, strerror(errno));
            return -1;
        }

        /* file in the hole! */
        lseek_offset = lseek(fd, HOLE_GAP, SEEK_CUR);
        if (lseek_offset == -1) {
            fprintf(stderr, "%d: %s\n", __LINE__, strerror(errno));
            return -1;
        }

    }

    /* Close */
    rval = close(fd);
    if (rval == -1) {
        fprintf(stderr, "%d: %s\n", __LINE__, strerror(errno));
        return -1;
    }
    return 0;
}

參考資料

  • man lseek
  • The Linux Programming Interface: Chapter 4

Linux下面的C語言使用getopt Parse Command Line參數

| Comments

在Linux下面的C語言可以透過getopt去parse從command line傳過來的參數

使用方式基本上就是重複讀取getopt回傳option字元直到回傳-1。

請注意:這只是蓋略說明,很多細節仍然需要參考manpage,例如哪些是Gnu的特別功能還有一些細節的參數操作等。

進一步細節如下:

大綱

參數以及變數說明

  • 參數
    • int argc: main的argument count
    • char **argv: main的argument vector
    • const char optstring: 你的程式要支援的option及格式
      • option以-開頭,後面為單一個字元,可接參數
      • 格式
        • 單一字元
        • 單一字元後接:
          • 該option 支援參數
        • 單一字元後接::
          • 該option 可接參數也可不接
          • ex: option -a, 參數arg1,下命令會是./cmd -aarg1
      • 範例: 支援-a, -b, -c 參數, -d 參數-e optional 參數
        • "abc:d:e::"
  • 變數 (僅列舉)
    • optopt
      • 如果遇到不支援的option或是不合語法的option操作,getopt會回傳?,然後把出問題的option放在optopt這個變數內
    • optind
      • option index,表示getopt下一個要處理的參數。當getopt回傳-1後記得比較optindargc看看還有沒有非option的參數要處理
        • 範例 ./cmd -a -b 123 456,跑完getopt後還有123 456要處理。

範例程式

test_getopt.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
57
58
59
60
61
62
63
#include <unistd.h>  /* getopt */
#include <stdio.h>

int main(int argc, char **argv)
{
    int cmd_opt = 0;

    fprintf(stderr, "argc:%d\n", argc);
    while(1) {
        fprintf(stderr, "proces index:%d\n", optind);
        cmd_opt = getopt(argc, argv, "abc:d:e::");

        /* End condition always first */
        if (cmd_opt == -1) {
            break;
        }

        /* Print option when it is valid */
        if (cmd_opt != '?') {
            fprintf(stderr, "option:-%c\n", cmd_opt);
        }

        /* Lets parse */
        switch (cmd_opt) {
            /* No args */
            case 'a':
            case 'b':
                break;

            /* Single arg */
            case 'c':
            case 'd':
                fprintf(stderr, "option arg:%s\n", optarg);
                break;

            /* Optional args */
            case 'e':
                if (optarg) {
                    fprintf(stderr, "option arg:%s\n", optarg);
                }
                break;


            /* Error handle: Mainly missing arg or illegal option */
            case '?':
                fprintf(stderr, "Illegal option:-%c\n", isprint(optopt)?optopt:'#');
                break;
            default:
                fprintf(stderr, "Not supported option\n");
                break;
        }
    }

    /* Do we have args? */
    if (argc > optind) {
        int i = 0;
        for (i = optind; i < argc; i++) {
            fprintf(stderr, "argv[%d] = %s\n", i, argv[i]);
        }
    }

    return 0;
}

執行結果

result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ./test_getopt -a -emy_args -c dddd  123 456 7890 -b -f -d
argc:11
proces index:1
option:-a
proces index:2
option:-e
option arg:my_args
proces index:3
option:-c
option arg:dddd
proces index:5
option:-b
proces index:9
./test_getopt: invalid option -- 'f'
Illegal option:-f
proces index:10
./test_getopt: option requires an argument -- 'd'
Illegal option:-d
proces index:11
argv[8] = 123
argv[9] = 456
argv[10] = 7890

參考資料

  • man 3 getopt
  • The Linux Programming Interface: Appendix B

C語言archive 明明有symbol卻link時出現unsolved Symbol錯誤

| Comments

說明

GNU ld 手冊提到,archive (可能是.a或是.o檔)在link時預設只會reference一次,如果沒有注意的話會發現明明有symbol卻link時出現unsolved symbol錯誤


測試

下面是測試程式碼liba.a包含的程式碼liba.a包含的程式碼,可以看到liba.a的function去呼叫libb的function,反之亦然。

test.c
1
2
3
4
5
6
7
8
9
10
#include "libb.h"
#include "liba.h"

int main(void)
{
    test_liba();
    test_libb();

    return 0;
}
liba.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "libb.h"
#include <stdio.h>

void test_liba(void)
{
    from_libb();
}

void from_liba()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}
libb.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "liba.h"
#include <stdio.h>

void test_libb(void)
{
    from_liba();
}

void from_libb()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}

而測試的Makefile如下,變數說明如下

  • $^: prerequisites
  • $@: target
  • $>: 第一個prerequisite
Makefile
1
2
3
4
5
6
7
8
9
10
11
all: liba.a libb.a test.o
  gcc -o test $^

liba.a: liba.o liba.h
  ar crv $@ $<

libb.a: libb.o libb.h
  ar crv $@ $<

clean:
  rm -rf *.o *.a *~

編譯輸出如下,正如前面的預想

Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ make
cc    -c -o liba.o liba.c
ar crv liba.a liba.o
a - liba.o
cc    -c -o libb.o libb.c
ar crv libb.a libb.o
a - libb.o
cc    -c -o test.o test.c
gcc -o test liba.a libb.a test.o
test.o: In function `main':
test.c:(.text+0x5): undefined reference to `test_liba'
test.c:(.text+0xa): undefined reference to `test_libb'
collect2: ld returned 1 exit status
make: *** [all] Error 1

解法

解法一:調整link順序

  • 將Makefile的all: liba.a libb.a test.o順序改成all: test.o liba.a libb.a可以正常編譯
    • 延伸測試
      • 將Makefile的all: liba.a libb.a test.o順序改成all: liba.a test.o libb.a會發生下面的錯誤
  • 關於link怎麼resovle symbol部份就不在本篇中討論,抱歉。
Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ make
cc    -c -o liba.o liba.c
ar crv liba.a liba.o
a - liba.o
cc    -c -o test.o test.c
cc    -c -o libb.o libb.c
ar crv libb.a libb.o
a - libb.o
gcc -o test liba.a test.o libb.a
test.o: In function `main':
test.c:(.text+0x5): undefined reference to `test_liba'
libb.a(libb.o): In function `test_libb':
libb.c:(.text+0x5): undefined reference to `from_liba'
collect2: ld returned 1 exit status
make: *** [all] Error 1

解法二:使用--start-group--end-group參數

  • 將Makefile 的gcc -o test $^改成gcc -o test -Wl,--start-group $^ -Wl,--end-group
  • 參數說明如下

    • -Wl: 告訴gcc傳參數給linker
    • --start-group--end-group
      • 告訴linker在中間的...重複找尋symbol
      • 這樣的行為有效能代價,請謹慎使用
  • 編譯並執行結果如下

Result
1
2
3
4
5
6
7
8
9
10
11
12
13
$ make
cc    -c -o liba.o liba.c
ar crv liba.a liba.o
a - liba.o
cc    -c -o libb.o libb.c
ar crv libb.a libb.o
a - libb.o
cc    -c -o test.o test.c
gcc -o test -Wl,--start-group liba.a libb.a test.o -Wl,--end-group

$ ./test
from_libb
from_liba

版本資訊

  • GNU ld 2.2

參考資料

  • man ld
    • 搜尋start-group

C語言在Linux下組裝經驗分享

| Comments

整理組裝經驗如下,全部是Command line的文字模式


非Runtime

  • 找出static library symbols
    • find | grep *\\.a$ | xargs nm -A | grep 要找的symbol

  • 找出library搜尋路徑
    • ld --verbose | grep SEARCH | tr "; " "\n\r"

  • 列出CC 預設define和相關資訊
    • echo "" | gcc -E -xc - -dM -v
  • grep 加regular expression萬歲

Runtime

  • dump console 輸出文字

    • script
      • script 文字輸出檔檔名
      • 操作
      • ctrl+d結束dump
    • 執行檔 2>&1 | tee 輸出檔名
      • 變形
        • 執行檔 2>&1 | grep 想找的文字 | tee 輸出檔名
  • 當dynamic library 不在/無法放到linker搜尋路徑造成檔案無法執行

    • $LD_LIBRARY_PATH=$LD_LIBRARY_PATH:你的library路徑 執行檔

  • 看程式system call的行為
    • strace 執行檔
    • strace -e open 執行檔
      • 看open system call,常用,因為有時候程式爛掉是因為開檔案失敗,用這個找很快
    • strace -f 執行檔
      • 包含fork的process

  • Linker/Loader 相關
    • ld.so會和路徑以及平台相關,以Ubuntu LTS 12.04 X86有
      • /lib64/ld-linux-x86-64.so.2
        • 64-bit
      • /lib/ld-linux.so.2
        • 32-bit
    • 透過dynamic linker/loader 看檔案link的library
      • LD_TRACE_LOADED_OBJECTS=1 ld.so 執行檔
    • 觀察dynamic linker/loader行為
      • LD_DEBUG=help /bin/ls
        • 顯示選項
      • LD_DEBUG=all 執行檔
        • 全部都顯示
  • 合體
    • LD_DEBUG=all strace -f ld.so strace -f 執行檔

參考資料