深入Os--动态链接

1.动态链接库的使用
动态库支持以两种模式使用,一种模式下,在程序加载运行时,完成动态链接。一种模式下,在程序运行中,完成动态链接。
1.1.程序加载运行时完成动态链接
我们通过一个实例介绍程序加载运行时,使用动态库的方式
在这里插入图片描述

(1). 构建动态库
动态库源文件及makefile位于dynamic
a.t1.cpp

// t1.cpp
int addcnt = 0;
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

b.t2.cpp

// t2.cpp
int mulcnt = 0;
void multvec(int *x, int *y, int *z, int n)
{
    int i;
    mulcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] * y[i];
}

c.makefile

main: t1 t2 dynamic

t1:
	g++ -fpic -std=c++11 t1.cpp -c
t2:
	g++ -fpic -std=c++11 t2.cpp -c

dynamic:
	g++ -std=c++11 -shared t1.o t2.o -o libt.so

clean:
	rm *.o libt.so *.txt

d.通过执行make完成构建。注意编译动态库源文件时需指定-fpic,基于.o得到动态库需指定-shared

(2).提供动态库导出符号声明文件
动态库导出符号声明文件放在include。
a.t.h

#ifndef _T_H
#define _T_H
extern int addcnt;
void multvec(int *x, int *y, int *z, int n);
void addvec(int *x, int *y, int *z, int n);
#endif

上述除了导出函数,我们还导出了变量addcnt。变量的声明需加上extern,否则会被视为变量定义。

(3).主程序使用动态库导出符号
a.主程序为main.cpp

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

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

int main()
{
    addvec(x, y, z, 2);
    printf("z=[%d %d]\n", z[0], z[1]);
    printf("addcnt_%d\n", addcnt);
    return 0;
}

我们采用加载运行时完成动态链接方式使用动态库时,在使用动态库导出符号时,需要先声明符号。然后直接使用即可。
上述使用了动态库导出的addvec,addcnt
b.构建可执行程序的makefile

main:
	g++ main.cpp -std=c++11 -I./include -L./dynamic -lt
clean:
	rm a.out *.o *.txt

我们采用加载运行时完成动态链接方式使用动态库时,构建可执行程序时,需通过-L -l来指定要链接的动态库的位置信息。-I用于指定编译期间头文件搜索路径。

(4).启动可执行程序
若上述编译完毕后,我们直接在a.out所在目录通过命令行执行./a.out是不行的。
在这里插入图片描述
因为类似编译链接过程需通过-L -l来指定要链接的动态库的位置信息。加载运行时,可以通过设置LD_LIBRARY_PATH来指定要链接的动态库的位置信息。上述结构下,我们提供s.sh。

// s.sh
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./dynamic
./a.out

这样执行./s.sh即可正常启动。LD_LIBRARY_PATH用于在程序启动运行时告知搜索程序依赖的动态库的路径。
要查看可执行程序依赖那些动态库,可使用ldd a.out
在这里插入图片描述

1.2.程序运行期间完成动态链接
我们通过一个实例介绍程序运行期间,使用动态库的方式。
在这里插入图片描述
(1). 构建动态库
和1.1部分相同。

(2).主程序中使用动态库导出符号
注意,运行期间使用动态库时,我们并不需要动态库导出符号声明文件。
因为使用导出符号的方式是通过dlsym直接取得导出符号地址后,转换为相应类型后使用。
a.主程序为main.cpp
这里的main.cpp放置在demo下

#include <iostream>
#include <dlfcn.h>

int x[2] = {1,2};
int y[2] = {3,4};
int z[2];

typedef void (*AddVec)(int*, int*, int*, int);
int main()
{
    void *handle;
    AddVec addvec = nullptr;
    char *error;
    handle = dlopen("libt.so", RTLD_LAZY);
    if(!handle)
    {
        printf("%s\n", dlerror());
        return 0;
    }

    addvec = (AddVec)dlsym(handle, "addvec");
    if((error = dlerror()) != NULL)
    {
        printf("%s\n", error);
        dlclose(handle);
        return 0;
    }

    int* addcnt = (int*)dlsym(handle, "addcnt");
    if((error = dlerror()) != NULL)
    {
        printf("%s\n", error);
        dlclose(handle);
        return 0;
    }

    addvec(x, y, z, 2);
    printf("z = [%d %d],cnt_%d\n", z[0], z[1], *addcnt);
    dlclose(handle);
    return 0;
}

我们采用运行期间完成动态链接的方式使用动态库,在使用动态库导出符号时,通过dlsym取得导出符号地址后,转换为匹配类型后,即可使用。上述使用了动态库导出的addvec,addcnt

b.构建可执行程序的makefile
makefile放置在demo。

main:
	g++ -std=c++11 -rdynamic main.cpp -I../include -ldl
clean:
	rm a.out *.o *.txt

我们采用运行期间完成动态链接方式使用动态库时,构建可执行程序时,不需要通过-L -l来指定要链接的动态库的位置信息。因为编译链接过程尚未用到运行期间要链接的动态库。但需指定-rdynamic -ldl,因为我们此时需要链接到服务于运行期间动态连接的动态库dl。
在这里插入图片描述
(3).启动可执行程序
类似的,我们在启动前需通过LD_LIBRARY_PATH来指定dlopen中搜索动态库的路径信息。
我们的放置在demo下的s.sh为

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../dynamic
./a.out

但执行./s.sh时,报错了:
在这里插入图片描述
因为我们采用c++方式编译动态库时,库内addvec的符号实际编译出的符号名称为:
在这里插入图片描述
这是因为c++编译器对编译时,针对函数类型会结合其形参为其构建符号名称。c编译器不会。
c++支持同名函数重载,所以,这样是需要的。c不支持同名函数重载,所以,不需要。

上述报错是因为我们通过dlsym取出addvec符号地址时,通过名称addvec在动态库中找不到匹配的符号。
为了正常使用dlsym取得导出符号地址:
(1).我们要么将dlsym传入的符号名修改为_Z6addvecPiS_S_i
(2).要么通过设置使得c++编译时,针对addvec导出符号不要采用符号重新命名机制。我们只需在动态库源文件符号定义处,添加extern "C"修饰即可。若我们采取了此种方式,应该同步在类库导出符号声明文件中为addvec的声明也添加extern "C"修饰。这样,1.1中使用动态库时,也会直接采用addvec来在动态库中定位符号的定义位置。

针对变量类型导出符号,如addcnt,c++编译器不会对符号执行重新命名。所以,直接使用符号名即可。

值得注意的是,添加extern "C"后由于关闭结合形参重命名机制,所以,此时也就不允许同名符号重载了。

int addcnt = 0;
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

void addvec(int *x, int *y, int *z)
{
    int i;
    i = 0;
    i++;
}

上述内容,作为t1.cpp内容时,可正常编译。

int addcnt = 0;
extern "C" void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

extern "C" void addvec(int *x, int *y, int *z)
{
    int i;
    i = 0;
    i++;
}

上述内容作为t1.cpp内容时,无法编译通过。因为存在同名符号问题。
在这里插入图片描述

2.动态链接库的链接
以下均以程序加载运行时完成动态链接为例说明动态链接过程。
(1). 程序编译时,动态库中被引用的符号定义不会被拷贝到可执行文件,但动态库中相关的符号表和重定位信息会被拷贝到编译得到的可执行文件中。
(2). 程序加载运行时,通过动态链接器将可执行程序依赖的动态库加载到内存中,并映射到进程某个线程区域,再完成对引用符号的重新定位过程。可执行程序中引用的动态库中定义的导出符号需要重定位,动态库中引用的其他动态库导出的符号也需重定位。

某个动态库一旦被加载到物理内存,后续其他进程需要加载此动态库时,可以共享此内存区域,通过虚拟内存映射到自身的一块线性区域即可。

2.1.位置无关代码
我们编译动态库时,对源文件使用了-fpic选项,告诉编译器采用位置无关方法来编译。位置无关可以认为是无论符号的定义放在线性区域的那个位置均是可以的。而可执行程序的各个部分所在的线性区域位置一般是编译时已经确定了的。

2.2.分析动态链接重定位过程
我们分析实例1.1.中可执行程序中对addvec的动态链接重定位过程。
我们对1.1.中实例的a.out执行:objdum -dx a.out > 1.txt
截取输出:
在这里插入图片描述
在这里插入图片描述
上述代码中,执行 addvec 访问时,将通过 call 跳转到 0x4005b0。
0x4005b0 通过 jmpq 跳转到 0x601030 地址处的值代表的位置。
为了获取到 0x601030 处的内容,我们对 a.out 执行:objdump -sx a.out > 11.txt。截取输出:
在这里插入图片描述
因为采用小端存储数值,故此处的 8 字节数值为:0x4005b6。
在这里插入图片描述
从0x4005b6处执行时,先是 pushq $0x3,然后跳到 0x400570。
在这里插入图片描述
从0x400570处执行时,先是 pushq 0x200a92(%rip),这是把 0x601008 位置的内容压入栈。
在这里插入图片描述
然后执行 jmpq *0x200a94(%rip),这是跳转到 0x601010 处继续执行
在这里插入图片描述

我们目前通过objdump -sx a.out > 11.txt得到的内容里,0x601008 和 0x601010 内容均是0,但是需要明白,在 a.out 被加载运行时, 这两部分分别会被设置为 重定位项起始位置,动态链接器代码段位置。

所以,上述过程实际是通过 两个 pushq 加上一个 jmpq 实现了传参并调用动态链接器。调用的结果是 会通过传参完成对引用符号 addvec 的动态重定位,重定位的结果是 0x601030 处会被写入 addvec 符号的定义位置对应的线性地址。这样,后续再次引用 addvec 时,将在执行jmpq *0x200a7a(%rip) # 601030 <addvec>时直接跳转到符号定义位置,略过首次调用时的动态链接过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/226263.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Dubbo-1.初识Dubbo

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

12.7作业

1. #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//***********窗口相关设置***********//设置窗体大小this->resize(540,410);this->setFixedSize(540,410);//取消菜单栏this->setWindowFlag(Qt::FramelessWindowHint);/…

【数据库】基于时间戳的并发访问控制,乐观模式,时间戳替代形式及存在的问题,与封锁模式的对比

使用时间戳的并发控制 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会…

MySQL之binlog文件过多处理方法

背景 MySQL由于大量读写&#xff0c;导致binlog文件特别的多。从而导致服务器disk空间不足问题。 先备份binlog文件 tar -zcvf mysql.tar.gz mysql/data/mysql-bin.00* 修改MySQL配置 binlog过期时间 show variables like expire_logs_days; 这里 0 表示 永不过期 如果为 n…

LeedCode刷题---双指针问题(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、盛水最多的容器 题目链接&#xff1a;盛最多水的容器 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xf…

Nacos下载、启动与使用的保姆级教程!

nacos下载及启动 nacos下载 首先打开nacos官方仓库链接。Nacos发布版本仓库。 点击Tags按钮找到nacos的历史发布的所有版本&#xff0c;点击download。 然后选择需要的下载即可。 后缀为.tar.gz为linux系统上运行的压缩包 后缀为.zip为windows系统上运行的压缩包 zip格式的…

vivado时序方法检查5

TIMING-14 &#xff1a; 时钟树上的 LUT 在时钟树上发现 LUT <cell_name> 。不建议在时钟路径上包含 LUT 单元。 描述 时钟路径上的 LUT 可能导致偏差过大 &#xff0c; 因为时钟必须在穿过互连结构的常规布线资源上进行布线。除偏差过大外 &#xff0c; 这些路径更…

[论文阅读]BEVFusion

BEVFusion BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework BEVFusion&#xff1a;简单而强大的激光雷达相机融合框架 论文网址&#xff1a;BEVFusion 论文代码&#xff1a;BEVFusion 简读论文 论文背景&#xff1a;激光雷达和摄像头是自动驾驶系统中常用的两…

SRC挖掘漏洞XSS

Markdown是一种轻量级标记语言&#xff0c;创始人为约翰格鲁伯&#xff08;John Gruber&#xff09;。它允许人们使用易读易写的纯文本格式编写文档&#xff0c;然后转换成有效的 XHTML&#xff08;或者HTML&#xff09;文档。这种语言吸收了很多在电子邮件中已有的纯文本标记的…

App自动化测试持续集成效率提高50%

持续集成是一种开发实践&#xff0c;它倡导团队成员需要频繁的集成他们的工作&#xff0c;每次集成都通过自动化构建&#xff08;包括编译、构建、自动化测试&#xff09;来验证&#xff0c;从而尽快地发现集成中的错误。让正在开发的软件始终处于可工作状态&#xff0c;让产品…

C++STL的string类(一)

文章目录 前言C语言的字符串 stringstring类的常用接口string类的常见构造string (const string& str);string (const string& str, size_t pos, size_t len npos); capacitysize和lengthreserveresizeresize可以删除数据 modify尾插插入字符插入字符串 inserterasere…

模电笔记。。。。

模电 2.8 蜂鸣器 按照蜂鸣器驱动方式分为有源蜂鸣器和无源蜂鸣器 有源的有自己的震荡电路&#xff0c;无源的要写代码控制。 里面有个线圈&#xff0c;相当于电感&#xff0c;储能&#xff0c;通直隔交。 蜂鸣器的参数&#xff1a;额定电压&#xff0c;工作电压&#xff0…

深入理解mysql的explain命令

1 基础 全网最全 | MySQL EXPLAIN 完全解读 1.1 MySQL中EXPLAIN命令提供的字段包括&#xff1a; id&#xff1a;查询的标识符。select_type&#xff1a;查询的类型&#xff08;如SIMPLE, PRIMARY, SUBQUERY等&#xff09;。table&#xff1a;查询的是哪个表。partitions&…

【算法每日一练]-结构优化(保姆级教程 篇4 树状数组,线段树,分块模板篇)

除了基础的前缀和&#xff0c;后面还有树状数组&#xff0c;线段树&#xff0c;分块的结构优化。 目录 分块 分块算法步骤&#xff1a; 树状数组 树状数组步骤&#xff1a; 线段树点更新 点更新步骤&#xff1a; 线段树区间更新 区间更新步骤&#xff1a; 分块 分块算…

【wvp】测试记录

ffmpeg 这是个莫名其妙的报错&#xff0c;通过排查&#xff0c;应该是zlm哪个进程引起的 会议室的性能 网络IO也就20M

业绩超预期,股价却暴跌,MongoDB股票还值得投资吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 尽管MongoDB(MDB)本季度的财报超出了预期&#xff0c;并提高了全年预期&#xff0c;但它的股价在财报发布后还是出现了暴跌。 MongoDB截至2023年10月31日的第三财季&#xff0c;收入同比增长了30%&#xff0c;达到了4.329亿…

各大电商平台商品详情API调用(API接口)、淘宝API、京东API、拼多多API、1688API文档案例演示

电商API接口的作用主要表现在以下几个方面&#xff1a; 数据支持&#xff1a;通过开放API接口&#xff0c;其他软件、应用、网站等可以访问电商平台的数据库和功能&#xff0c;利用这些数据提供更丰富的功能和更好的服务。例如&#xff0c;API接口可以收集用户的购物记录、搜索…

第二十一章总结。。

计算机网络实现了堕胎计算机间的互联&#xff0c;使得它们彼此之间能够进行数据交流。网络应用程序就是再已连接的不同计算机上运行的程序&#xff0c;这些程序借助于网络协议&#xff0c;相互之间可以交换数据&#xff0c;编写网络应用程序前&#xff0c;首先必须明确网络协议…

状态机的练习:按键控制led灯

设计思路&#xff1a; 三个按键控制led输出。 三个按键经过滤波(消抖)&#xff0c;产生三个按键标志信号。 三个led数据的产生模块&#xff08;流水&#xff0c;跑马&#xff0c;闪烁模块&#xff09;&#xff0c;分别产生led信号。 这六路信号&#xff08;三路按键信号&am…

4种常见的限流算法

限流算法 1、固定窗口 含义&#xff1a; 在一个固定长度的时间窗口内限制请求数量&#xff0c;每来一个请求&#xff0c;请求次数加一&#xff0c;如果请求数量超过最大限制&#xff0c;就拒绝该请求 优点&#xff1a; 实现简单&#xff0c;容易理解。 缺点&#xff1a; ①限流…