Linux下編譯生成SO並進行調用執行


參考

參考博客:

C編譯: 動態連接庫 (.so文件) - Vamei - 博客園 (cnblogs.com)

C 多個動態庫存在同名函數問題處理方法:-fvisibility=hidden_more_HH-CSDN博客_fvisibility

Linux編譯動態鏈接庫so避免運行時才發現函數未定義符號的錯誤undefined symbol的ld參數 (gmd20.github.io)

查看so庫的方法__臣本布衣_新浪博客 (sina.com.cn)

Linux 動態庫同名函數處理-iibull-ChinaUnix博客

https://mp.weixin.qq.com/s/o1C-cmsz8Mh4Cm7eodqcPg

1 動態庫介紹

image-20230214092241111

image-20230214092303675

2 示例

2.1 代碼結構

image-20211221100302725

(1)include中是用於生成SO的頭文件,當前只有一個test.h文件,內容如下:

void print_func(void);

(2)src中是用於生成SO的源文件,當前只有一個test.c文件,內容如下:

#include <stdio.h>

void print_func(void)
{
    int i = 0;

    for (; i < 10; i++)
        printf("i = %d\n", i);
 	return;   
}

(3)Makefile文件是用於生成SO的,內容如下:

PROJECT=libprint_func.so

CC?=gcc

SOURCES=$(wildcard src/*.c)

OBJECTS=$(patsubst %.c,%.o,$(SOURCES))

.PHONY:clean

CFLAG = -Iinclude -fPIC -shared
LD_FLAG = -fPIC -s -Wl,-z,relro,-z,now,-z,noexecstack -fstack-protector-all

$(PROJECT): $(OBJECTS)
	mkdir -p lib
	$(CC)  -shared -o lib/$@ $(patsubst %.o,obj/%.o,$(notdir $(OBJECTS))) $(LD_FLAG)
	@echo "finish $(PROJECT)"

.c.o:
	@mkdir -p obj
	$(CC) -c $< $(CFLAG) -o obj/$(patsubst %.c,%.o,$(notdir $<))

clean:
	-rm -rf obj lib
	@echo "clean up"

生成的SO的名字為libprint_func.so

(4)main.c用於測試,函數內部會調用test.c中的函數,代碼如下:

#include <stdio.h>
#include "test.h"

int main(void)
{
    print_func();

    return 0;
}

2.2 編譯SO

編譯方式,直接在Makefile所在目錄執行make即可。

$ ls
include/  main.c  Makefile  src/
$ make
cc -c src/test.c -Iinclude -fPIC -shared -o obj/test.o
mkdir -p lib
cc  -shared -o lib/libprint_func.so obj/test.o -fPIC -s -Wl,-z,relro,-z,now,-z,noexecstack -fstack-protector-all
finish libprint_func.so

執行了make之后,會在當前目錄生成兩個文件夾lib和obj,lib目錄存放SO文件,obj目錄存在生成的obj文件。

image-20211221101011250

$ ls lib/libprint_func.so 
lib/libprint_func.so*
$ ls obj/test.o 
obj/test.o

2.3 生成可執行文件

接着編譯main.c,生成可執行文件進行測試。

編譯方式:

gcc -o test main.c -lprint_func -Llib -Iinclude -Wall

-l:說明庫文件的名字,使用-lprint_func (即libprint_func庫文件)

-L:指定編譯庫文件所在位置

-I(大寫的i):指定頭文件所在文件

-Wall:打印所有警告

注意:編譯時必須包括SO中函數的頭文件(test.h),否則會提示隱形聲明的警告。

例如:在main.c中注釋掉#include "test.h"。

#include <stdio.h>
//#include "test.h"

int main(void)
{
    print_func();

    return 0;
}

編譯時會提示警告:

$ gcc -o test main.c -lprint_func -Llib -Iinclude -Wall
main.c: In function ‘main’:
main.c:6:5: warning: implicit declaration of function ‘print_func’ [-Wimplicit-function-declaration]
     print_func();

2.4 執行可執行文件

直接執行可執行文件,會提示找不到libprint_func.so文件。

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

可通過gcc -print-search-dirs命令來查看庫的搜索路徑:

$ gcc -print-search-dirs
install: /usr/lib/gcc/x86_64-redhat-linux/4.8.5/
programs: =/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/bin/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/bin/
libraries: =/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/lib/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/x86_64-redhat-linux/4.8.5/:/lib/../lib64/:/usr/lib/x86_64-redhat-linux/4.8.5/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/lib/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/

可以通過ldd命令查看可執行文件依賴的庫:

$ ldd ./test             
        linux-vdso.so.1 =>  (0x00007ffd5b943000)
        libprint_func.so => not found
        libc.so.6 => /lib64/libc.so.6 (0x00007efec2afc000)
        /lib64/ld-linux-x86-64.so.2 (0x00005593b3bba000)

可以看到找不到libprint_func.so庫,有兩種方法可以解決。

方法一:將libprint_func.so拷貝到系統的/lib/路徑下,並執行ldconfig命令(此操作需要root權限才能搞定);

方法二:將libprint_func.so所在路徑,增加到LD_LIBRARY_PATH環境變量中。

1)先使用方法二解決:

$ export LD_LIBRARY_PATH=./lib
$ ldd ./test                  
        linux-vdso.so.1 =>  (0x00007ffeb81dd000)
        libprint_func.so => ./lib/libprint_func.so (0x00007f35a3dfc000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f35a3a25000)
        /lib64/ld-linux-x86-64.so.2 (0x00005625bf8b5000)
$ ./test     
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

2)再使用方法一解決:

$ export LD_LIBRARY_PATH=./
$ ./test 
./test: error while loading shared libraries: libprint_func.so: cannot open shared object file: No such file or directory
$ sudo cp lib/libprint_func.so /lib
$ sudo ldconfig
$ ./test       
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

3 擴展

問題一:如果在不同的SO中存在相同的函數名,如何處理?

在之前test.c中的代碼基礎上,修改print_func函數內容:

#include <stdio.h>

void print_func(void)
{
    int i = 0;

    for (i = 10; i < 20; i++)
        printf("i = %d\n", i);
    return;
}

然后重新生成一個SO文件,重命名為libprint_func00.so(更改Makefile)。

先刪除/lib庫下的libprint_func.so。

$ sudo rm /lib/libprint_func.so 
$ sudo ldconfig

將libprint_func.so 和 libprint_func00.so 都放到當前目錄的lib下:

$ ls ./lib/
libprint_func00.so*  libprint_func.so*

然后使用方法二鏈接SO文件:

$ export LD_LIBRARY_PATH=./lib

編譯:

$ gcc -o test main.c -lprint_func -lprint_func00  -Llib -Iinclude -Wall
$ ldd test 
        linux-vdso.so.1 =>  (0x00007ffd04d9b000)
        libprint_func.so => ./lib/libprint_func.so (0x00007f729a480000)
        libprint_func00.so => ./lib/libprint_func00.so (0x00007f729a27d000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f7299ea7000)
        /lib64/ld-linux-x86-64.so.2 (0x000055f88ea81000)

執行:

$ ./test 
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

可以看出,執行時調用的是libprint_func.so庫

下面更改編譯時SO文件的順序(先-lprint_func00),然后執行:

$ gcc -o test main.c -lprint_func00 -lprint_func  -Llib -Iinclude -Wall  
$ ./test 
i = 10
i = 11
i = 12
i = 13
i = 14
i = 15
i = 16
i = 17
i = 18
i = 19

可以看出,執行時調用的是libprint_func00.so庫

總結:SO中重名時,優先調用的是先鏈接的庫。

對於不同庫中函數重名的問題,可參考博客Linux 動態庫同名函數處理-iibull-ChinaUnix博客,這里不進行說明。

問題2:鏈接不使用的SO

參考:https://mp.weixin.qq.com/s/o1C-cmsz8Mh4Cm7eodqcPg

注意:高版本的GCC,默認會去掉不使用的SO,像完成這個實驗,需要價格-Wl,--no-as-needed選項。

首先編譯一個libprint.so:

$ gcc -c -fPIC -shared print.c 
$ gcc -shared -o libprint.so print.o -fPIC 

然后,編譯一個可執行程序,需要鏈接libprint.so:

$ gcc -o main main.c -lprint -Lso -Iinc -Wall
$ ldd main
        linux-vdso.so.1 =>  (0x00007ffcceffb000)
        libprint.so => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4c93a46000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f4c93e10000)

接着鏈接一個不需要使用的SO:

$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609

# 鏈接一個不需要使用的SO(libm.so)
$ gcc -o main main.c -Wl,--no-as-needed -lprint -lm -Lso -Iinc -Wall          
[root@ubuntu] /home/grace/SO
$ ldd main
        linux-vdso.so.1 =>  (0x00007ffcb63fc000)
        libprint.so => not found
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f8f666bd000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8f662f3000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8f669c6000)

可使用ldd -u 可執行程序命令查看不需要鏈接的SO:

$ ldd -u main
Unused direct dependencies:
        libprint.so   # 這個庫其實是需要的
        /lib/x86_64-linux-gnu/libm.so.6
        /lib/x86_64-linux-gnu/libc.so.6

解決方式:使用編譯選項-Wl,--as-needed

$ gcc -o main main.c -Wl,-as-needed -lprint -lm -Lso -Iinc -Wall    
$ ldd -u main  # 少了libm.so
Unused direct dependencies:
        libprint.so
        /lib/x86_64-linux-gnu/libc.so.6
$ ldd main
        linux-vdso.so.1 =>  (0x00007fff877e4000)
        libprint.so => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe34045a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe340824000)

另一種運行時指定SO路徑方式

$ ldd main
        linux-vdso.so.1 =>  (0x00007ffcceffb000)
        libprint.so => not found  # 找不到庫的路徑,運行時會報錯
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4c93a46000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f4c93e10000)

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

在運行鏈接了動態庫的時候需要去找到SO,一般使用方法是修改LD_LIBRARY_PATH這個環境變量。LD_LIBRARY_PATH則既屬於鏈接期搜索路徑,又屬於運行時期的搜索路徑。

-rpath鏈接選項,它是指定運行時候都使用的搜索路徑。通過這個選項,可以將運行庫的路徑硬編碼到可執行文件內部。

$ gcc -o main main.c -Wl,-rpath so -lprint -lm -Lso -Iinc -Wall 
$ ldd main
        linux-vdso.so.1 =>  (0x00007ffdbf72b000)
        libprint.so => so/libprint.so (0x00007f48bf894000) # 指定了庫的路徑
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f48bf4ca000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f48bfa96000)

當然,這種方式也有使用限制,比如將庫從指定路徑移除后,則運行時會報錯,沒有使用LD_LIBRARY_PATH指定庫的路徑靈活:

$ mv so/libprint.so ./
$ ./main
./main: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

4 總結

由於這是第一次嘗試生成SO文件,部分地方可能存在問題,希望網友指出。

再次感謝參考的博客:

C編譯: 動態連接庫 (.so文件) - Vamei - 博客園 (cnblogs.com)

C 多個動態庫存在同名函數問題處理方法:-fvisibility=hidden_more_HH-CSDN博客_fvisibility

Linux編譯動態鏈接庫so避免運行時才發現函數未定義符號的錯誤undefined symbol的ld參數 (gmd20.github.io)

查看so庫的方法__臣本布衣_新浪博客 (sina.com.cn)

Linux 動態庫同名函數處理-iibull-ChinaUnix博客

Linux下部分用於SO相關的命令:

打印SO中的符號信息:nm -D libxxx.so

$ nm -D ./lib/libprint_func.so 
0000000000201000 B __bss_start
                 w __cxa_finalize
0000000000201000 B _edata
0000000000201008 B _end
0000000000000700 T _fini
                 w __gmon_start__
0000000000000580 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
                 U printf
00000000000006c5 T print_func

查看依賴關系:ldd libxxx.so

$ ldd ./lib/libprint_func.so 
        linux-vdso.so.1 =>  (0x00007fff56150000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fd3fd4e7000)
        /lib64/ld-linux-x86-64.so.2 (0x00005598885c5000)

查看so庫的屬性:file libxxx.so

$ file ./lib/libprint_func.so 
./lib/libprint_func.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=24735da88c6198d81974645c25367fae14a267a5, stripped


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM