当前位置: 首页 > news >正文

2.3 携程的hook实现及dlsym函数

背景知识:(排除static 情况)

一个进程中可以有相同的命名吗? -- 不能

两个进程之间可以有相同的命名吗?--可以

一个进程和另一个静态库可以有相同的命名吗?--不能

一个进程和另一个动态库之间可以有相同的命名吗?--可以

在考虑 进程静态库动态库 中的函数与变量命名是否可以相同时,需要从作用域、链接阶段、加载机制等角度进行分析。以下分别讨论静态库和动态库的情况:


1. 进程与静态库之间的函数或变量命名是否可以相同?

可以,但需注意链接时的冲突。

原因:
  • 静态库的性质:静态库(.a 文件)在编译阶段被链接器整合到可执行文件中,其代码最终成为可执行文件的一部分。因此,静态库中的函数或变量在链接时会与进程(可执行文件)的代码共享同一命名空间。
  • 冲突情况
    • 如果进程代码中定义了与静态库相同的函数或变量名,链接器会发生符号重定义错误,除非通过弱符号等机制处理。
    • 如果名称不同,则不会产生冲突。
示例:
  1. 正常情况

    • 静态库 libexample.a 中定义了函数 void foo()
    • 可执行文件中没有定义 foo(),则链接器会将 foo() 从静态库中包含到可执行文件中。
  2. 冲突情况

    • 如果静态库和可执行文件都定义了 void foo(),链接器会报错:
      duplicate symbol: foo
      
  3. 解决方法

    • 避免冲突:确保静态库和可执行文件的全局命名空间中没有重复定义。
    • 使用弱符号:通过 __attribute__((weak)) 标记静态库的符号为弱符号,让进程中的定义优先。

2. 进程与动态库之间的函数或变量命名是否可以相同?

可以,但需注意运行时的符号解析规则。

原因:
  • 动态库的性质:动态库(.so 文件)中的代码在运行时由动态链接器加载到进程中。如果动态库与可执行文件存在同名函数或变量,符号解析规则决定了哪个定义会被使用。
符号解析规则:
  1. 默认规则

    • 可执行文件中的符号优先于动态库中的符号。
    • 如果可执行文件没有定义某个符号,则动态库中的定义会被使用。
  2. 显式控制规则

    • 使用 --export-dynamic 编译选项,可以让可执行文件中的符号在动态链接器中被导出,从而覆盖动态库中的同名符号。
    • 使用动态库加载函数(如 dlopen()dlsym())时,可以显式指定使用动态库中的符号。
示例:
  1. 正常情况

    • 动态库 libexample.so 中定义了函数 void bar()
    • 可执行文件中没有定义 bar(),则运行时会使用动态库中的 bar()
  2. 冲突情况

    • 如果动态库和可执行文件都定义了 void bar(),运行时会优先使用可执行文件中的 bar(),即使动态库的 bar() 被链接到进程中。
  3. 解决方法

    • 避免冲突:为动态库中的符号使用独立的命名空间(如通过 namespacestatic 修饰)。
    • 显式调用:通过 dlsym() 显式加载动态库中的函数,绕过默认的符号解析规则。

3. 总结

对象关系函数/变量是否可以相同冲突可能性解决方案
进程和静态库可以链接时可能冲突避免符号冲突或使用弱符号机制
进程和动态库可以运行时可能覆盖避免符号冲突、显式调用动态库的符号或隔离命名空间

4. 建议

  1. 命名空间隔离

    • 使用独特的命名或为库定义专属命名空间(如 C++ 的 namespace 或 C 的命名前缀)。
  2. 弱符号机制

    • 为静态库的符号使用 __attribute__((weak)),以便在进程中定义相同符号时优先使用进程的实现。
  3. 动态链接控制

    • 对动态库,尽量避免与可执行文件使用相同的全局符号。
    • 在需要显式控制时,使用 dlsym() 指定加载的符号。

这样可以有效避免命名冲突导致的链接错误或运行时行为异常。

如果一个函数位于我们需要链接的动态库中,则可以用dlsym函数把他hook住,同时我们在自己的进程中也可以再定义一个相同名字的函数,则可以实现动态库的函数"重载"


dlsym函数的作用

`dlsym()` 和 `dlvsym()` 都是 Linux 动态链接库(`dl`)的一部分,用于在运行时动态加载库和获取符号(函数或变量)地址。它们主要用于实现**动态库**(`shared libraries`)的加载和符号解析。

### `dlsym()` 函数:

- **功能**:`dlsym()` 用于在指定的动态库中查找符号地址(函数或变量)。它通常和 `dlopen()` 配合使用,通过动态加载库后,使用 `dlsym()` 来获取某个符号的地址,然后可以通过这个地址调用动态库中的函数。

- **参数**:

  - `handle`:这是通过 `dlopen()` 获得的库句柄。

  - `symbol`:要查找的符号名称(字符串形式)。

- **返回值**:成功时返回符号的地址,失败时返回 `NULL`。

- **示例**:

  ```c

  void *handle = dlopen("libm.so", RTLD_LAZY);

  double (*cos_func)(double) = (double (*)(double)) dlsym(handle, "cos");

  ```

  在这个例子中,`dlsym()` 获取了动态库 `libm.so` 中 `cos` 函数的地址。

### `dlvsym()` 函数:

- **功能**:`dlvsym()` 与 `dlsym()` 类似,但它允许指定符号的**版本号**。这是用于那些支持符号版本控制的库。比如,当一个动态库的不同版本中同一个符号有不同实现时,可以使用 `dlvsym()` 指定加载特定版本的符号。

- **参数**:

  - `handle`:同样是由 `dlopen()` 返回的句柄。

  - `symbol`:要查找的符号名称。

  - `version`:符号的版本号。

- **返回值**:成功时返回符号的地址,失败时返回 `NULL`。

  例如,如果一个库在不同版本中定义了同名的函数,但行为不同,可以通过指定 `version` 参数,确保加载特定版本的符号。

### 区别:

- `dlsym()` 只查找符号,而 `dlvsym()` 允许指定符号的版本,这在符号有多个版本的库中尤为重要。

### 使用场景:

- **`dlsym()`** 常用于插件系统或动态库调用,能够在运行时决定调用的函数。

- **`dlvsym()`** 通常在库设计复杂、符号有多个版本时使用,确保兼容性和特定功能。

两者的配合使用可以让程序在运行时根据需求动态选择不同的库函数,从而增强灵活性。

dlsym() 常用于插件系统或动态库调用,能够在运行时决定调用的函数。这个怎么来理解

`dlsym()` 的功能可以通过“插件系统”或“动态库调用”来理解。这涉及到在程序运行时动态加载某个库,选择具体的函数进行调用,而不是在编译时就确定要调用的函数。通过这种机制,程序可以在运行时灵活选择功能,实现更高的可扩展性和灵活性。

### 具体理解:

1. **插件系统**:

   插件系统是一种软件设计模式,允许程序通过外部插件扩展其功能,而无需在程序主框架中直接包含这些功能。在插件系统中,每个插件通常是一个动态链接库(如 `.so` 文件)。当程序运行时,它会动态加载这些库,找到特定的符号(函数或变量)来执行插件的功能。

   **示例**:

   想象一个文本编辑器支持不同的文件格式,比如 `.txt`、`.md`、`.html`。文本编辑器的核心程序不知道所有这些格式的具体操作逻辑,而是允许插件来实现。插件通过动态库的形式提供特定功能,编辑器使用 `dlopen()` 动态加载插件,再用 `dlsym()` 查找并调用插件中处理这些文件的函数。

   ```c

   void *handle = dlopen("markdown_plugin.so", RTLD_LAZY);

   void (*convert_to_html)(const char *) = dlsym(handle, "convert_to_html");

   if (convert_to_html) {

       convert_to_html("example.md");

   }

   ```

   在这个例子中,主程序不知道如何处理 `.md` 文件,但通过 `dlsym()` 动态加载插件中的 `convert_to_html` 函数,它可以在运行时决定是否调用特定的插件函数。

2. **动态库调用**:

   动态库调用的核心思想是程序在运行时决定要使用的库函数,而不是在编译时将库函数硬编码到程序中。通过 `dlsym()`,程序可以灵活选择不同的实现。

   **动态库的好处**:

   - **节省资源**:不需要将所有库函数都加载到内存中,只有在需要时才动态加载,节省内存资源。

   - **灵活扩展**:可以随时添加或替换动态库,而不需要重新编译程序。比如,一个图像处理程序可以通过 `dlsym()` 在运行时选择使用 `libjpeg` 还是 `libpng` 来处理不同的图片格式。

3. **运行时决定调用的函数**:

   通常在程序中,函数调用是在编译时就确定好的。但通过 `dlsym()`,程序可以在运行时动态决定调用哪个函数。这个机制常用于需要高扩展性、功能多样性或者有插件需求的程序。

   **应用场景**:

   - **数据库驱动**:许多数据库应用支持多种数据库引擎(如 MySQL、PostgreSQL 等),通过 `dlsym()`,程序可以动态选择要使用的数据库驱动。

   - **图形库或音频库**:可以动态选择使用不同的图形或音频库,依赖不同的库文件来支持不同的功能。

总结来说,`dlsym()` 通过在运行时决定要调用的函数,使得程序在无需重新编译的情况下能够加载不同的模块或插件,从而实现动态加载和功能扩展。

dlsym函数hook write函数

这段代码的主要作用是在拦截和包装系统调用 `write()`,通过动态链接库机制重定向它的执行。下面是详细的解释:

### 1. **自定义 `write` 函数:**

```c

ssize_t write(int fd, const void *buf, size_t count) {

    printf("write: %s\n", (const char *)buf);

    return write_f(fd, buf, count);

}

```

- 这个 `write()` 函数是一个自定义的 `write`,与系统提供的 `write` 函数同名。

- 它首先输出一个调试信息 `write: %s\n`,将要写入的数据(假设是字符串)打印出来。这用于调试,帮助开发者看到传入的字符串内容。

- 然后它调用了 `write_f(fd, buf, count)`,这是真正执行写操作的函数。

### 2. **使用 `dlsym()` 获取原始 `write` 函数:**

```c

if (!write_f) {

    write_f = dlsym(RTLD_NEXT, "write");

}

```

- 这部分代码通过 `dlsym(RTLD_NEXT, "write")` 获取系统中原始的 `write()` 函数地址,并将其存储在 `write_f` 中。

- **`RTLD_NEXT`** 是一个特殊的标志,它告诉 `dlsym()` 查找下一个符号,跳过当前动态库,找到真正的系统 `write()` 函数。这样避免了递归调用自己定义的 `write` 函数,导致死循环。

- `write_f` 变量会缓存 `dlsym()` 找到的系统调用地址,以避免每次调用 `write()` 时都使用 `dlsym()`,提高性能。

### 3. **作用和应用场景:**

- **函数拦截**:这段代码实现了函数拦截技术,用于拦截程序中的 `write()` 系统调用,然后进行额外的处理,比如打印调试信息或修改写入数据。

- **库注入与动态分析**:这种技术常用于调试和性能分析工具,如 `strace` 或 `ld_preload` 技术中,可以通过拦截某些系统调用来观察程序行为。

- **调试与日志**:通过 `printf()` 打印出写入的数据内容,可以帮助开发者在不修改程序源代码的情况下追踪 `write()` 调用。

### 总结:

这段代码的作用是通过 `dlsym()` 拦截并扩展系统调用 `write()` 的功能,在调用系统 `write()` 之前进行一些自定义操作,比如输出调试信息,然后再调用原始的 `write()` 实现写操作。这种方法在需要监控、记录或修改系统调用行为时非常有用。

示例

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>ssize_t (*read_f)(int fd, void *buf, size_t count);
ssize_t (*write_f)(int fd, const void *buf, size_t count);ssize_t read(int fd, void *buf, size_t count)
{read_f = dlsym(RTLD_NEXT, "read"); // RTLD_NEXT 表示跳过自己当前程序中定义的read函数if (read_f == NULL) {return -1;}puts("my hook read");return read_f(fd, buf, count);
}int main(int argc, char *argv[])
{system("echo -n \"this is a test file!\" > test.file");int fd = open("test.file", O_RDONLY);if (fd == -1) {perror("open err");return -1;}char buf[100] = {0};if (read(fd, buf, 99) != -1) {puts(buf);close(fd);}system("rm ./test.file");return 0;
}

Sign in · GitLab

相关文章:

2.3 携程的hook实现及dlsym函数

背景知识&#xff1a;&#xff08;排除static 情况&#xff09; 一个进程中可以有相同的命名吗&#xff1f; -- 不能 两个进程之间可以有相同的命名吗&#xff1f;--可以 一个进程和另一个静态库可以有相同的命名吗&#xff1f;--不能 一个进程和另一个动态库之间可以有相同…...

机器学习之KNN算法

K-Nearest Neighbors (KNN) 是一种常见的机器学习算法&#xff0c;广泛应用于分类和回归问题。KNN是一种基于实例的学习方法&#xff0c;它利用训练数据集的实例来进行分类或回归预测。在KNN中&#xff0c;预测的结果依赖于距离度量函数计算出的最近邻实例的标签或值。下面我们…...

《全排列问题》

题目描述 按照字典序输出自然数 11 到 nn 所有不重复的排列&#xff0c;即 nn 的全排列&#xff0c;要求所产生的任一数字序列中不允许出现重复的数字。 输入格式 一个整数 nn。 输出格式 由 1∼n1∼n 组成的所有不重复的数字序列&#xff0c;每行一个序列。 每个数字保留…...

pycharm 快捷键

PyCharm 是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供了丰富的快捷键来提高开发效率。以下是一些常用的 PyCharm 快捷键&#xff08;基于 Windows/Linux 系统&#xff0c;Mac 系统可能略有不同&#xff09;&#xff1a; 通用快捷键 功能快捷键&a…...

若依微服务如何获取用户登录信息

文章目录 1、需求提出2、应用场景3、解决思路4、注意事项5、完整代码第一步&#xff1a;后端获取当前用户信息第二步&#xff1a;前端获取当前用户信息 5、运行结果6、总结 1、需求提出 在微服务架构中&#xff0c;获取当前用户的登录信息是开发常见的需求。无论是后端处理业务…...

RunCam WiFiLink连接手机图传测试

RunCam WiFiLink中文手册从这里下载 一、摄像头端 1.连接天线&#xff08;易忘&#xff09; 2.打开摄像头前面的盖子&#xff08;易忘&#xff09; 3.接上直流电源&#xff0c;红线为正&#xff0c;黑线为负 4.直流电源设置电压为14v&#xff0c;电流为3.15A&#xff0c; 通…...

TCP三次握手,四次挥手

三次握手 第一次握手&#xff1a;客户端向服务器发送一个 SYN 包&#xff0c;其中 SYN 标志位被设置为 1&#xff0c;表示客户端请求建立连接&#xff0c;并随机生成一个初始序列号 seqx 。此时客户端进入 SYN_SENT 状态&#xff0c;等待服务器的确认1.第二次握手&#xff1a;服…...

Mono里建立调试C#脚本运行环境

前面已经介绍了怎么样来执行一个嵌入式的脚本框架, 这个框架是mono编写的一个简单的例子。 如果不清楚,可以参考前文: https://blog.csdn.net/caimouse/article/details/144632391?spm=1001.2014.3001.5501 本文主要来介绍一下,我们的C#脚本是长得怎么样的,它大体如下…...

Linux dnf 包管理工具使用教程

简介 dnf 是基于 Red Hat Linux 发行版的下一代包管理工具&#xff0c;它代替 yum 提供更好的性能、更好的依赖处理和更好的模块化架构。 基础语法 dnf [options] [command] [package] 常用命令用法 更新元数据缓存 sudo dnf check-update# 检查已安装的包是否有可用的更…...

Java 创建线程的方式有哪几种

在 Java 中&#xff0c;创建线程的方式有四种&#xff0c;分别是&#xff1a;继承 Thread 类、实现 Runnable 接口、使用 Callable 和 Future、使用线程池。以下是详细的解释和通俗的举例&#xff1a; 1. 继承 Thread 类 通过继承 Thread 类并重写 run() 方法来创建线程。 步…...

计算机的错误计算(一百八十七)

摘要 用大模型计算 sin(123.456789). 其自变量为弧度。结果保留16位有效数字。第一个大模型是数学大模型。先是只分析&#xff0c;不计算&#xff1b;后经提醒&#xff0c;才给出结果&#xff0c;但是是错误结果。第二个大模型&#xff0c;直接给出了Python代码与结果&#xf…...

12. 最大括号深度

题目描述 现有一字符串仅由"("&#xff0c;")", "{","}", "[", "]"六种括号组成。若字符串满足以下条件之一&#xff0c; 则为无效字符串:任一类型的左右括号数量不相等 存在未按正确顺序(先左后右)闭合的括号输出…...

进程与线程以及如何查看

长期补充&#xff0c;建议关注收藏&#xff01; 定义 特性进程线程定义程序执行的基本单位执行中的最小单位资源拥有独立的内存空间和资源共享进程的资源开销创建和销毁的开销较大创建和销毁的开销较小执行单位进程中可以有多个线程线程是执行单元&#xff0c;必须依赖于进程并…...

BlueLM:以2.6万亿token铸就7B参数超大规模语言模型

一、介绍 BlueLM 是由 vivo AI 全球研究院自主研发的大规模预训练语言模型&#xff0c;本次发布包含 7B 基础 (base) 模型和 7B 对话 (chat) 模型&#xff0c;同时我们开源了支持 32K 的长文本基础 (base) 模型和对话 (chat) 模型。 更大量的优质数据 &#xff1a;高质量语料…...

Webpack学习笔记(4)

1.缓存 可以通过命中缓存降低网络流量&#xff0c;是网站加站速度更快。 然而在部署新版本时&#xff0c;不更改资源的文件名&#xff0c;浏览器可能认为你没有更新&#xff0c;所以会使用缓存版本。 由于缓存存在&#xff0c;获取新的代码成为问题。 接下来将配置webpack使…...

28、论文阅读:基于像素分布重映射和多先验Retinex变分模型的水下图像增强

A Pixel Distribution Remapping and Multi-Prior Retinex Variational Model for Underwater Image Enhancement 摘要介绍相关工作基于模型的水下图像增强方法&#xff1a;无模型水下图像增强方法&#xff1a;基于深度学习的水下图像增强方法&#xff1a; 论文方法概述像素分布…...

5.interview-self-introduction

1.保证电话面试来的时候&#xff0c;可以接听&#xff0c;保持电话通常 interviews will be arranged recently.please keep your phone line open and make sure you can answer th call when the phone interview comes. speak loudly and slow down your speaking voice &a…...

高性能MySQL-查询性能优化

查询性能优化 1、为什么查询这么慢2、慢查询基础&#xff1a;优化数据访问2.1 是否向数据库请求了不需要的数据2.2 MySQL是否存在扫描额外的记录 3、重构查询方式3.1 一个复杂查询还是多个简单查询3.2 切分查询3.3 分解联接查询 4、查询执行的基础4.1 MySQL的客户端/服务器通信…...

如何有效修复ffmpeg.dll错误:一站式解决方案指南

当您遇到提示“ffmpeg.dll文件丢失”的错误时&#xff0c;这可能导致相关的应用程序无法启动或运行异常。本文将详细介绍如何有效地解决ffmpeg.dll文件丢失的问题&#xff0c;确保您的应用程序能够恢复正常运行。 ffmpeg.dll是什么&#xff1f;有哪些功能&#xff1f; ffmpeg.…...

8086汇编(16位汇编)学习笔记00.DEBUG命令使用解析及范例大全

8086汇编(16位汇编)学习笔记00.DEBUG命令使用解析及范例大全-C/C基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net[md]启动 Debug&#xff0c;它是可用于测试和调试 MS-DOS 可执行文件的程序。  Debug [[drive:][path] filename [parameters]]  参数  [drive:…...

查看mysql的冷数据配置比例

查看mysql的冷数据配置比例 -- 真正的LRU链表&#xff0c;会被拆分为两个部分&#xff0c;一部分是热数据&#xff0c;一部分是冷数据&#xff0c;这个冷热数据的比例是由innodb_old_blocks_pct参数控制的&#xff0c;它默认是37&#xff0c;也就是说冷数据占比37%。 show GLO…...

【Java基础面试题028】Java中的hashCode和equals方法,与==操作符有什么区别?

回答重点 hashcode、equals 和 都是Java中用于比较对象的三种方式&#xff0c;但是它们的用途和实现还是有挺大区别的。 hashcode用于散列存储结构中确定对象的存储位置。可用于快速比较两个对象是否不同&#xff0c;因为如果它们的哈希码不同&#xff0c;那么它们肯定不相等…...

在C#中测试比较目录的不同方法以查看它们有哪些共同的文件

C# 中的示例“比较目录以查看它们有哪些共同的文件”使用Directory.GetFiles获取两个目录中的文件。它对文件进行排序&#xff0c;并比较两个排序后的列表以查看哪些文件位于第一个目录中、第二个目录中或两个目录中。有关其工作原理的详细信息&#xff0c;请参阅该示例。 Kur…...

harbor离线安装 配置https 全程记录

1. 下载harbor最新版本 下载网址: 找最新的版本: https://github.com/goharbor/harbor/releases/download/v2.11.2/harbor-offline-installer-v2.11.2.tgz 这里我直接使用迅雷下载, 然后上传 1.1解压 sudo tar -xf harbor-offline-installer-v2.11.2.tgz -C /opt/ 2. 配置Harb…...

C++简明教程(文章要求学过一点C语言)(5)

在开始之前必须阅读这个文章 https://blog.csdn.net/weixin_45100742/article/details/135152562 这篇文章完全是对C语言的补课&#xff0c;如果C语言学的好&#xff0c;可跳过。 变量、数据类型与运算符 在 C 编程中&#xff0c;理解变量、数据类型和运算符是构建程序的基石…...

Halcon单相机+机器人=眼在手上#标定心得

首先&#xff0c;这个标定板肯定是放在我们要作业的工作台上的 目的 **1&#xff0c;得到标定物&#xff08;工作台&#xff09;与机器人底座之间的pose转换关系。2&#xff0c;得到相机与机器人末端tool的的转换关系。 两个不确定的定量 1&#xff0c;标定板与机器人底座b…...

【hackmyvm】Diophante 靶场

1. 基本信息^toc 这里写目录标题 1. 基本信息^toc2. 信息收集2.1. 端口扫描2.2. 目录扫描2.3. knock 3. WordPress利用3.1. wpscan扫描3.2. smtp上传后门 4. 提权4.1. 提权leonard用户4.2. LD劫持提权root 靶机链接 https://hackmyvm.eu/machines/machine.php?vmDiophante 作者…...

C++类的继承关系中什么时候要用到上行转换和下行转换

一、C类继承关系中的上行转换 1. 多态性实现 在C中&#xff0c;上行转换&#xff08;将派生类转换为基类&#xff09;是实现多态性的关键。例如&#xff0c;当有一个基类Animal&#xff0c;以及派生类Dog和Cat。如果有一个函数接受Animal类型的参数&#xff0c;我们可以将Dog或…...

Ubuntu 22.04永久保存路由

在 Ubuntu 22.04 上&#xff0c;可以按照以下方式配置让流量访问 172.19.201.207 走指定的路由。 1. 临时添加路由 临时路由规则只在当前系统会话中有效&#xff0c;重启后会丢失。 添加路由规则 运行以下命令&#xff1a; sudo ip route add 172.19.201.207 via 192.168.2…...

数据结构十大排序之(冒泡,快排,并归)

接上期&#xff1a; 数据结十大排序之&#xff08;选排&#xff0c;希尔&#xff0c;插排&#xff0c;堆排&#xff09;-CSDN博客 前言&#xff1a; 在计算机科学中&#xff0c;排序算法是最基础且最重要的算法之一。无论是大规模数据处理还是日常的小型程序开发&#xff0c;…...