彙編庫的使用(結)

彙編語言和C一樣,可以通過使用庫來簡化阻止大量函數的目標文件的問題。

GNU C編譯器可以不在命令行中獨立地包含每個獨立地函數目標文件,它允許吧所有目標文件組合在單一存檔文件中。在編譯C程序時,要做的工作就是包含單一的目標庫文件,在編譯時,編譯器可以從庫文件中挑出所需的正確目標文件。在庫文件中,經常按照應用程序類型或者函數類型把函數分組在一起,單一應用程序項目可以使用多個庫文件。

庫又分靜態庫和動態庫。靜態庫文件中包含的目標代碼會被編譯到主程序中,程序的運行就不需要庫文件了。動態庫文件的包含的目標代碼不會在編譯時編譯到主程序中,而是主程序在運行時調用該部分代碼,這樣可以節省內存需求。

ar命令可以創建靜態庫文件。下面為示例所用到的所有文件:

$ ls
add.s  libcal.a  main.c  Makefile  prt.s  sub.s
$

add.s文件包含2個彙編函數,sub.s包好2個彙編函數,prt.s包含1個彙編函數。現在將這三個文件打包為一個庫文件libcal.a,在編譯可執行文件是連接該庫文件。下面分別為上面文件的內容: add.s文件:

# add.s
.type add, @function
.globl add
add:    # add two integer
    pushl %ebp
    movl %esp, %ebp

    movl 8(%ebp), %eax
    addl 12(%ebp), %eax

    movl %ebp, %esp
    popl %ebp
    ret

.type add_inc, @function
.globl add_inc
add_inc:   # add 1 to int parameter
    pushl %ebp
    movl %esp, %ebp

    movl 8(%ebp), %eax
    inc %eax

    movl %ebp, %esp
    popl %ebp
    ret
sub.s文件:
# sub.s
.type sub, @function
.globl sub
sub:    # sub two integer
    pushl %ebp
    movl %esp, %ebp

    movl 8(%ebp), %eax
    subl 12(%ebp), %eax

    movl %ebp, %esp
    popl %ebp
    ret
.type sub_dec, @function
.globl sub_dec
sub_dec:   # sub 1 to int parameter
    pushl %ebp
    movl %esp, %ebp

    movl 8(%ebp), %eax
    dec %eax

    movl %ebp, %esp
    popl %ebp
    ret

.type print_hello, @function
.section .data
msg:
    .ascii "hello world!\n"
    len=.-msg
.section .text
.global print_hello
print_hello:
    movl $len, %edx
    movl $msg, %ecx
    movl $1, %ebx
    movl $4, %eax
    int $0x80
    movl $0, %ebx
    movl $1, %eax
    int $0x80

C文件:

# main.c
#include <stdio.h>

int add(int, int);
int add_inc(int);
int sub(int, int);
int sub_dec(int);
int print_hello(void);

int main(int argc, const char *argv[])
{
    int ret;

    ret = add(1989, 711);
    printf("The add() return is %d.\n", ret);

    ret = add_inc(1989);
    printf("The add_inc() return is %d.\n", ret);

    ret = sub(1989, 711);
    printf("The sub() return is %d.\n", ret);

    ret = sub_dec(1989);
    printf("The sub_dec() return is %d.\n", ret);

    print_hello();

    return 0;
}

Makefile:

# Makefile for linux as

CFLAGS= -Wall -g
ASFLAGS= -gstabs

SRC_BIN=target_bin

SRC_LIB=libcal.a

SRC_C=$(wildcard *.c)
SRC_S=$(wildcard *.s)

SRC_COBJ=$(SRC_C:.c=.o)
SRC_SOBJ=$(SRC_S:.s=.o)

SRC_OBJ=$(SRC_COBJ) $(SRC_SOBJ)

all: $(SRC_BIN)

$(SRC_BIN): $(SRC_COBJ) $(SRC_LIB)
 $(CC) -o $@ $(SRC_COBJ) -L./ -lcal

$(SRC_LIB) : $(SRC_SOBJ)
 ar rcs $@ $^

clean:
 $(RM) $(SRC_OBJ) $(SRC_BIN) *~
.PHONY:
 all clean

下面為執行輸出結果:

$ make
cc -Wall -g   -c -o main.o main.c
as -gstabs  -o add.o add.s
as -gstabs  -o prt.o prt.s
as -gstabs  -o sub.o sub.s
ar rcs libcal.a add.o prt.o sub.o
cc -o target_bin main.o -L./ -lcal
$ ./target_bin
The add() return is 2700.
The add_inc() return is 1990.
The sub() return is 1278.
hello world!
$

使用 ar命令t參數可以查看庫文件包含的目標文件。

$ ar t libcal.a
add.o
prt.o
sub.o
nm命令可以顯示每個函數名,並且顯示哪個目標文件包含哪個函數。
$ nm -s libcal.a
Archive index:
add in add.o
add_inc in add.o
print_hello in prt.o
sub in sub.o
sub_dec in sub.o
add.o:
00000000 T add
0000000d T add_inc
prt.o:
0000000d a len
00000000 d msg
00000000 T print_hello
sub.o:
00000000 T sub
0000000d T sub_dec

當應用程序和靜態庫一起編譯時,函數代碼被編譯到了應用程序中,也就是說程序所需所有代碼都在可執行文件中。但是如果函數代碼內容有改動,那麼使用次函數的每個應用程序都必須使用新的版本程序編譯,並且系統上多個程序可能會使用相同函數,那麼內存會多次把相同函數加載到內存中。

而共享庫包含目標代碼的文件被加載到操作系統的通用區域,當應用程序需要訪問共享庫中的函數時,操作系統自動把代碼加載到內存中,允許應用程序訪問它。當另一個應用程序也需要使用次函數代碼時,操作系統允許它訪問已經被加載到內存中的相同函數代碼,這裡只有函數代碼的一個拷貝加載到了內存,使用該函數代碼的每個程序都不會再把它加載到內存中。如果函數需要改動,只需要更新庫文件就可以了,不需要重新編譯新的新的應用程序。

現在我們將上面靜態庫的例子改為動態庫來實現。 修改Makefile如下:

$ cat Makefile.static
# Makefile for linux as
CFLAGS= -Wall -g
ASFLAGS= -gstabs
SRC_BIN=target_bin
SRC_LIB=libcal.a
SRC_C=$(wildcard *.c)
SRC_S=$(wildcard *.s)
SRC_COBJ=$(SRC_C:.c=.o)
SRC_SOBJ=$(SRC_S:.s=.o)
SRC_OBJ=$(SRC_COBJ) $(SRC_SOBJ)
all: $(SRC_BIN)
$(SRC_BIN): $(SRC_COBJ) $(SRC_LIB)
 $(CC) -o $@ $(SRC_COBJ) -L./ -lcal
$(SRC_LIB) : $(SRC_SOBJ)
 ar rcs $@ $^
clean:
 $(RM) $(SRC_OBJ) $(SRC_BIN) *~
.PHONY:
 all clean

編譯如下:

$ make -f Makefile.share
cc -Wall -g   -c -o main.o main.c
as -gstabs  -o add.o add.s
as -gstabs  -o prt.o prt.s
as -gstabs  -o sub.o sub.s
gcc -shared -o libcal.so add.o prt.o sub.o
cc -o target_bin main.o -L./ -lcal

用ldd命令可以查看應用程序依賴的庫文件:

$ ldd target_bin
 linux-gate.so.1 =>  (0x00dac000)
 libcal.so => not found
 libc.so.6 => /lib/libc.so.6 (0x00a42000)
 /lib/ld-linux.so.2 (0x00a1c000)

發現ldd命令顯示程序需要共享庫文件libcal.so,並且沒有找到它。此時試圖運行會出現如下錯誤:

$ ./target_bin
./target_bin: error while loading shared libraries: libcal.so: cannot open shared object file: No such file or directory

出現該錯誤是因為動態加載器不知道如何訪問共享庫libcal.so。文件/etc/ld.so.conf文件保存動態加載器在那些目錄中查找目錄清單,筆者系統上該文件如下:

$ cat /etc/ld.so.conf
include ld.so.conf.d/*.conf

即包含目錄 ld.so.conf.d/下的所有conf文件。

$ ls /etc/ld.so.conf.d/

atlas-i386.conf kernel-2.6.32-358.el6.i686.conf mysql-i386.conf qt-i386.conf xulrunner-32.conf

上面每個配置文件就是對於每個應用查找其對應共享庫的路徑。

一般做法是為應用程序創建單獨的目錄,並把目錄添加到文件ld.so.conf。所以只需要將要查找的的共享庫文件的目錄添加到ld.so.conf文件中來就可以了。可以效仿上面的已有的例子,將路徑放在/etc/ld.so.conf.d/目錄下的一個.conf文件中,然後使用ldconfig命令更新文件ld.so.cache。最後再運行程序如下:

# ./target_bin
The add() return is 2700.
The add_inc() return is 1990.
The sub() return is 1278.
hello world!

本系列linux彙編學習到此為止,在這二十節的內容中,可能沒有講解到彙編的所有方面,但還是把彙編語言的比較常用的基本知識通過一些很簡單很基礎的示例展示了出來,我希望讀者在看完本系列文章之後有所有收穫,也希望讀者在閱讀文章是能指出其中的錯誤和不足。