打印
[STM32MP1]

Linux 之 静态库及动态库的编写和使用

[复制链接]
911|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
kxsi|  楼主 | 2021-9-2 19:13 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
ELF 文件规范
  ELF(Executable and Linking Format)是一个二进制文件规范。用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。

  现在流行的二进制可执行文件格式 (Executable File Format),主要是 Windows 下的 PE(Portable Executable)和 Linux 的 ELF(Executable and Linking Format)可执行和链接格式)。他们都是 COFF(Common Object File Format)的变种。ARM 体系中采用的也是 ELF 文件格式。

  COFF 是在 Unix System V Release 3 时由 UNIX 系统实验室(UNIX System Laboratories, USL)首先提出并且使用的文件规范,后来微软公司基于 COFF 格式,制定了 PE 格式标准,并将其用于当时的 Windows NT 系统。在 System V Release 4 时,UNIX 系统实验室在 COFF 的基础上,开发和发布了 ELF 格式,作为应用程序二进制接口 (Application Binary Interface,ABI)。

  此后,工具接口标准委员会(Tool Interface Standard Committee,TISC)选择了正在发展中的 ELF 标准作为工作在 32 位 INTEL 体系上不同操作系统之间可移植的二进制文件格式。可以从这里 找到详细的标准文档。如下图:


TISC 共出过两个版本(v1.1和 v1.2)的标准文档。两个版本内容上差不多,但 v1.2 版本重新组织了原本在 v1.1 版本中的内容。可读性更高。两个版本的目录如下所示:


在 ELF 文件规范中,把系统中采用 ELF 格式的文件(规范中称为对象文件(Object File))归类为以下三种:

可重定位文件(Relocatable File ): 这类文件包含代码和数据,可用来连接成可执行文件或共享对象文件(Object File),静态链接库归为此类,对应于 Linux 中的 .o ;Windows 的 .obj.
可执行文件(Executable File ): 这类文件包含了可以直接执行的程序,它的代表就是 ELF 可执行文件。Linux 下,他们一般没有扩展名,比如 /bin/bash;Windows 下的 .exe
共享对象文件(Object File)(Shared Object File ): 这种文件包含代码和数据,链接器可以使用这种文件跟其他可重定位文件的共享对象文件(Object File)链接,产生新的对象文件(Object File)。对应于Linux 中的 .so,Windows 中的 DLL
另外是动态链接器可以将几个这种共享对象文件(Object File)与可执行文件结合,作为进程镜像文件来运行。
  在 Linux 系统中,还有一类文件,被称为核心转储文件(Core Dump File) ,当进程意外终止,系统可以将该进程地址空间的内容及终止时的一些信息转存到核心转储文件。 对应 Linux 下的 core dump。


使用特权

评论回复
沙发
kxsi|  楼主 | 2021-9-2 19:13 | 只看该作者
本帖最后由 kxsi 于 2021-9-2 19:15 编辑

库文件
  为了避免一些重复性的工作并且便于编程,开发人员定义了一系列的标准函数以供调用,这些函数都放在相应的库中,而我们在进行开发时直接使用这些标准函数。

  库文件就是在系统层面对于 ELF 各种文件的称呼。从本质上来说就是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。

静态库
  在编译目标程序时,所使用的静态库中的函数的所有数据都会被整合进目标代码中,编译出的目标程序的执行程序不需要外部的函数库支持。linux 中静态库文件的扩展名一般为 .a,a 就是 archive 的缩写。其编写步骤很简单:

编写函数代码
编译生成各目标文件
用 ar 文件对目标文件归档,生成静态库文件。注意:归档文件名必须以 lib 打头。
使用要点:
在 gcc 的 -I 参数后加上静态库头文件的路径
在 gcc 的 -L 参数后加上库文件所在目录
在 gcc 的 -l 参数后加上库文件名,但是要去掉 lib 和 .a 扩展名。比如库文件名是 libtest.a,那么参数就是 -ltest
静态库必须按照 lib[name].a 的规则命名
编写最简单的静态库文件
编写如下两个文件,注意放在同一目录中
myalib.h 文件的内容
void test();


myalib.c 文件的内容
#inlcude <stdio.h>
void test()
{
        printf("test\n");
}


制作库文件
生成目标文件。执行命令 gcc -c myalib.c,其中:-c 表示只编译,不链接 myalib.c,执行完后会生成一个 myalib.o 文件
用 ar 命令归档。格式为 ar -rc <生成的档案文件名> <.o文件名列表>。归档文件名一定要以 lib 打头及 .a 结尾。ar -rc libtest.a myalib.o,执行完后会生成一个 libtest.a 文件
使用库文件
编写一个测试程序 main.c,内容为
   //main.c 测试静态库调用的程序
   #include "myalib.h"   //要把函数的头文件包含进来,否则编译时会报错
   int main(int argc,char* argv[])
   {
       test();
       return 0;  
   }


编译目标文件。注意要把静态库头文件的路径加到 -I 参数里面。gcc -I ~ -o main.o -c main.c。其中:-o 表示输出文件 mian.o,-c 表示只编译 main.c 不进行链接。现在生成了一个 main.o 文件。注意:~ 表示库文件在当前目录下,根据自己目录修改。
生成可执行文件。注意要把静态库文件的路径加到 -L 参数里面,把库文件名(去掉打头的 lib 和结尾的 .a )加到 -l 参数后面。执行命令 gcc -o main -L~ main.o -ltest 后会生成一个名为 main 的可执行文件 注意:~ 表示库文件在当前目录下,根据自己目录修改。另外,注意 -l 参数好象应该加到输入文件名的后面,否则会报错。

执行可执行文件 ./main 查看效果, 如下:

说明执行成功。
动态库
  动态库也有的称为共享库。在编译目标程序的时候,其使用的动态函数库不会被编译进目标程序中,编译生成的目标程序执行到相关函数时必须要调用所使用的动态库中的对应函数。在 linux 中,动态库一般以 .so 结尾,so 就是 shared object 的缩写。其基本生成步骤为:

编写函数代码
编译生成动态库文件,要加上 -shared 和 -fpic 选项 ,动态库文件名以 lib 开头, 以 .so 结尾。
使用方式分为两种: 隐式调用和显示调用
隐式调用:类似于静态库的使用,但需修改动态链接库的配置文件 /etc/ld.so.conf;
显示调用:则是在主程序里使用 dlopen、dlsym、dlerror、dlclose 等系统函数。




使用特权

评论回复
板凳
kxsi|  楼主 | 2021-9-2 19:15 | 只看该作者
编写最简单的动态库文件
编写如下两个文件,注意放在同一目录中
myalib.h 文件的内容
void test();


myalib.c 文件的内容
#inlcude <stdio.h>
void test()
{
        printf("test\n");
}


使用命令:gcc -fpic -shared -o libtest.so myalib.c 编译生成动态库,库文件名以 lib 开头, 以 .so 结尾。此时就生成一个 libtest.so 文件
隐式调用
  隐式调用的含义是代码里不出现库文件名,使用方式和调用静态库的代码是类似的。下面我们以一个示例来说明一下。

编写测试文件
//main.c 测试动态库隐式调用的程序
#include "myalib.h"   // 要把函数的头文件包含进来,否则编译时会报错
int main(int argc,char* argv[])
{
        test();
        return 0;  
}


使用命令:gcc -I ~ -o main.o -c main.c 编译测试程序,与静态库类似,要把头文件的路径加到 -I 参数里面。现在生成了一个 main.o 文件
使用命令:gcc -o main -L~ main.o -ltest 连接生成测试程序。此时会生成了一个 main 文件
执行测试程序 ./main。不出意外的话,应该会报错。这是由于 linux 查找动态库的方式导致的。此时,可以使用命令:LD_LIBRARY_PATH=. ./mian 来运行。
显式调用
  显式调用的含义是代码出现库文件名,用户需要自己去打开和管理库文件。注意以下两点:

需要包含 dlfcn.h 系统头文件
用 dlopen 函数打开库文件,并指定打开方式。
下面以示例来说明一下:

编写测试文件
#include <dlfcn.h>  //用于动态库管理的系统头文件
#include "myalib.h" //要把函数的头文件包含进来,否则编译时会报错
int main(int argc,char *argv[])
{
    //声明对应的函数的函数指针
    void(pTest)();
    //加载动态库
    void pdlHandle = dlopen("libtest.so", RTLD_LAZY);
    //错误处理
    if (pdlHandle == NULL)
    {
        printf("Failed load library\n");
        return -1;
   }
    char pszErr = dlerror();
    if (pszErr != NULL)
    {
        printf("%s\n", pszErr);
        return -1;
    }
    //获取函数的地址
    pTest = dlsym(pdlHandle, "test");
    pszErr = dlerror();
    if (pszErr != NULL)
   {
       printf("%s\n", pszErr);
        dlclose(pdlHandle);
        return -1;
    }
    //实现函数调用
    (pTest)();
    //程序结束时关闭动态库
    dlclose(pdlHandle);
    return 0;
}

使用命令 gcc -o main -ldl main.c 编译测试文件。使用 -ldl 选项指明生成的对象模块需要使用共享库。执行完后就生成了一个 main 文件.
执行测试程序。执行 ./main


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

44

主题

3309

帖子

2

粉丝