My code works, I don’t know why.

國王的耳朵是驢耳朵

Libtool初探

| Comments

之前討論的autotools中,偷懶沒去看libtool。後來想到手上參考的資料沒有使用autotool產生shared library的方式。想要補充後發現和libtool有關。看來出來混還是要還的。

目錄


概論

autotool想要處理不同平台之間的移植性問題。而shared library部份的問題是:

  • 不同平台會有不同格式,舉例來說,在Linux下面的shared library的副檔名是so;而在Windows平台上面會是dll的副檔名。
  • 因為平台不同,shared library可能功能相同但是API稍有不同的
  • Header file可能名稱不同

處理這些問題,應用程式可能會寫了一堆很噁心的#ifdef來呼叫不同平台處理不同的shared library的API。這樣一來不但損害程式碼可讀性,也提高的程式碼的維護成本。所以GNU提出了libtool來解決這樣的問題。

這邊可以看到,GNU libtool使用下面的方式來處理平台移植性的中shared library的問題。

  • 用libtool這個script去封裝平台相依性以及提供存取介面
  • 使用者透過libtool這個script存取封裝的資料

因為libtool提供了和平台無關的share library統一介面。Shared library的使用者可以透過autotools安心使用shared library;而Shared library開發者可以安心的使用libtool產生不同平台使用的shared library。

libtool --help 可以看到有--mode參數。他的合法選項為

  • clean
    • 從build目錄刪除檔案
  • compile
    • 把原始碼編譯成libtool object
  • execute
    • 自動設定library path 並執行特定檔案
  • finish
    • 結束libtool library 安裝
  • install
    • 安裝library或是執行檔
  • link
    • 產生library或是執行檔
  • uninstall
    • 反安裝library或是執行檔

範例

  • 測試環境: Ubuntu 12.04.4

測試程式碼

範例程式細節在這邊,檔案各別重新分配到src, include, libs這三個目錄。不想看code只要知道每個檔案都有參考到某個自訂的header file就好了。重點在Makefile如何使用libtool的部份。

測試程式樹狀架構
1
2
3
4
5
6
7
8
9
10
11
├── include
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── liba.c
│   ├── libb.c
│   └── Makefile
├── Makefile
└── src
    ├── Makefile
    └── test.c

測試Makefile

主要demo libtool的部份在這邊。 ./Makefile 主要只是按照指定目錄進入編譯或清除

./Makefile
1
2
3
4
5
6
7
8
9
10
COMPILE_DIRS = libs src
INC_DIR=$(shell pwd)/include

.PHONY: all

all:
  for i in $(COMPILE_DIRS); do make -C $$i INC_DIR=$(INC_DIR); done

clean:
  for i in $(COMPILE_DIRS); do make -C $$i INC_DIR=$(INC_DIR) clean; done

使用libtool編譯函式庫

libs/Makefile大概描述如下

  • 產生兩個libraries, liba.a(靜態函式庫)和libb.so(動態函式庫)
  • 編譯時使用–mode=compile作為參數
  • 連結時使用–mode=link作為參數,都是指定gcc產生*.la檔案
  • 靜態和動態的參數差別為是否有指定runtime elf搜尋路徑-rpath
    • -rpath加入後libtools會產生 動態和靜態兩種函式庫 ,不加看起來只有產生靜態函式庫
libs/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
LIBTOOL=libtool
SRCS = liba.c libb.c
OBJS = $(patsubst %.c, %.o, $(SRCS))
CFLAGS = -I$(INC_DIR)
CC = gcc

TARGET=liba.la libb.la

all: $(OBJS) $(TARGET)

liba.la: liba.lo
  $(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $@ $^

libb.la: libb.lo
  $(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $@ $^ -rpath /usr/local/lib

%.o:%.c
  $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c $^

clean:
  rm -rf $(OBJS) .libs *.lo $(TARGET) *.a

先來看編譯程式碼的輸出

libs/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
libtool --mode=compile gcc -I../include -c liba.c
libtool: compile:  gcc -I../include -c liba.c  -fPIC -DPIC -o .libs/liba.o
libtool: compile:  gcc -I../include -c liba.c -o liba.o >/dev/null 2>&1
libtool --mode=compile gcc -I../include -c libb.c
libtool: compile:  gcc -I../include -c libb.c  -fPIC -DPIC -o .libs/libb.o
libtool: compile:  gcc -I../include -c libb.c -o libb.o >/dev/null 2>&1
libtool --mode=link gcc -I../include -o liba.la liba.lo
libtool: link: ar cru .libs/liba.a .libs/liba.o
libtool: link: ranlib .libs/liba.a
libtool: link: ( cd ".libs" && rm -f "liba.la" && ln -s "../liba.la" "liba.la" )
libtool --mode=link gcc -I../include -o libb.la libb.lo -rpath /usr/local/lib
libtool: link: gcc -shared  -fPIC -DPIC  .libs/libb.o      -Wl,-soname -Wl,libb.so.0 -o .libs/libb.so.0.0.0
libtool: link: (cd ".libs" && rm -f "libb.so.0" && ln -s "libb.so.0.0.0" "libb.so.0")
libtool: link: (cd ".libs" && rm -f "libb.so" && ln -s "libb.so.0.0.0" "libb.so")
libtool: link: ar cru .libs/libb.a  libb.o
libtool: link: ranlib .libs/libb.a
libtool: link: ( cd ".libs" && rm -f "libb.la" && ln -s "../libb.la" "libb.la" )

如果把gcc換成cc會發現不會有FPIC參數跑出來,詳細原因不明。

可以看到

  • libtool產生兩種object檔
    • PIC版本放在libs/.libs
      • 一般的就放在工作目錄下

現在libs下面檔案除了object檔案外,還多了.lo和.la檔案。

libs/Makefile
1
2
$ ls libs
liba.c  liba.la  liba.lo  liba.o  libb.c  libb.la  libb.lo  libb.o  Makefile

我們先看lo檔,可以看到lo檔存放PIC和一般的object檔案位置資訊。

libs/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
$ cat liba.lo
# liba.lo - a libtool object file
# Generated by libtool (GNU libtool) 2.4.2 Debian-2.4.2-1ubuntu1
#
# Please DO NOT delete this file!
# It is necessary for linking the library.

# Name of the PIC object.
pic_object='.libs/liba.o'

# Name of the non-PIC object
non_pic_object='liba.o'

而la檔案則是存放library資訊,我們可以看到靜態和動態主要的差別是有沒有指定so檔名稱以及存放路徑

libs/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
29
30
31
$ diff libs/libb.la  libs/liba.la
1c1
< # libb.la - a libtool library file
---
> # liba.la - a libtool library file
8c8
< dlname='libb.so.0'
---
> dlname=''
11c11
< library_names='libb.so.0.0.0 libb.so.0 libb.so'
---
> library_names=''
14c14
< old_library='libb.a'
---
> old_library='liba.a'
25,28c25,28
< # Version information for libb.
< current=0
< age=0
< revision=0
---
> # Version information for liba.
> current=
> age=
> revision=
41c41
< libdir='/usr/local/lib'
---
> libdir=''

使用libtool編譯執行檔

src/Makefile中主要就是連結的時候除了指定object檔案外,指定連結la檔案而不是.a或是.so檔案。libtool會幫你從la檔案中找到對的library binary。

src/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
LIBTOOL=libtool
SRCS = test.c
OBJS = $(patsubst %.c, %.o, $(SRCS)) ../libs/libb.la ../libs/liba.la
CFLAGS = -I$(INC_DIR)
CC = gcc

TARGET=test

$(TARGET): $(OBJS)
  $(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $@ $(OBJS)

%.o:%.c
  $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c $^

clean:
  rm -rf $(OBJS) .libs *.lo $(TARGET)

安裝

  • 如果--mode=link有加-rpath資訊,libtool會支援安裝功能:
    • libtool –mode=install cp libs/libb.la /usr/local/lib
    • 另外值得一提的是安裝時la檔案是有被更動的。比對如下
src/Makefile
1
2
3
4
5
$ diff libs/libb.la  /usr/local/lib/libb.la
31c31
< installed=no
---
> installed=yes
  • 執行檔:

    • libtool –mode=install install -c src/.libs/my_test /usr/local/bin
  • 靜態函式庫部份目前還沒試出來,不確定是libtool只支援安裝動態函式庫還是沒有找到正確的方式。


應該有人會問怎麼沒看到autotool怎麼用,這當作下次題目吧。


參考資料及資源

Comments