c - LD_PRELOAD 是否可能只影响主可执行文件?

标签 c linux dynamic-linking ld-preload

实际问题
我有一个默认情况下使用 EGL 和 SDL 1.2 分别处理图形和用户输入的可执行文件。使用 LD_PRELOAD ,我已经用 GLFW 替换了两者。
这正常工作,除非用户安装了 GLFW 的 Wayland 版本,这取决于 EGL 本身。因为所有的 EGL 调用要么不做任何事情,要么调用 GLFW 等效项,所以它不起作用(即 eglSwapBuffers 调用 glfwSwapBuffers 调用 eglSwapBuffers 等等)。我无法删除 EGL stub ,因为它会同时调用 EGL 和 GLFW,并且主可执行文件是闭源的,因此我无法修改它。
有什么办法可以制作LD_PRELOAD影响主要的可执行文件但不影响 GLFW?或任何其他解决方案以获得相同的效果?
简化的问题
我做了一个简化的例子来演示这个问题。
主要可执行文件:

#include <stdio.h>

extern void do_something();

int main() {
    do_something();
    fputs("testing B\n", stderr);
}
共享库:
#include <stdio.h>

void do_something() {
    fputs("testing A\n", stderr);
}
预装库:
#include <stdio.h>

int fputs(const char *str, FILE *file) {
    // Do Nothing
    return 0;
}
当不使用预加载的库时,输出为:
testing A
testing B
使用时,输出什么都没有。
我正在寻找一种方法使预加载的库只影响主可执行文件,输出将是:
testing A
谢谢!

最佳答案

您可以检查返回地址是否在可执行文件或库中,然后调用“真实”函数或执行 stub 代码,如下所示:

#define _GNU_SOURCE

#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
#include <stdlib.h>

static struct {
    ElfW(Addr) start, end;
} *segments;
static int n;
static int (*real_fputs)(const char *, FILE *);

static int callback(struct dl_phdr_info *info, size_t size, void *data) {
    n = info->dlpi_phnum;
    segments = malloc(n * sizeof *segments);
    for(int i = 0; i < n; ++i) {
        segments[i].start = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
        segments[i].end = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr + info->dlpi_phdr[i].p_memsz;
    }
    return 1;
}

__attribute__((__constructor__))
static void setup(void) {
    real_fputs = dlsym(RTLD_NEXT, "fputs");
    dl_iterate_phdr(callback, NULL);
}

__attribute__((__destructor__))
static void teardown(void) {
    free(segments);
}

__attribute__((__noinline__))
int fputs(const char *str, FILE *file) {
    ElfW(Addr) addr = (ElfW(Addr))__builtin_extract_return_addr(__builtin_return_address(0));
    for(int i = 0; i < n; ++i) {
        if(addr >= segments[i].start && addr < segments[i].end) {
            // Do Nothing
            return 0;
        }
    }
    return real_fputs(str, file);
}
不过,这有一些警告。例如,如果您的可执行文件调用一个库函数,该函数对您正在 Hook 的函数进行尾调用,那么这将错误地认为该库调用是一个可执行文件调用。 (您也可以通过为这些库函数添加包装器来缓解这个问题,即无条件地转发到“真实”函数,并使用 -fno-optimize-sibling-calls 编译包装器代码。)此外,无法区分匿名可执行内存(例如, JITted 代码)最初来自可执行文件或库。
要对此进行测试,请将我的代码另存为 hook_fputs.c ,您的主要可执行文件为 main.c ,以及您的共享库为 libfoo.c .然后运行这些命令:
clang -fPIC -shared hook_fputs.c -ldl -o hook_fputs.so
clang -fPIC -shared libfoo.c -o libfoo.so
clang main.c ./libfoo.so
LD_PRELOAD=./hook_fputs.so ./a.out

关于c - LD_PRELOAD 是否可能只影响主可执行文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67945100/

相关文章:

delphi - Delphi DLL 是注定要用于加载程序锁的吗?

c - 进程的退出状态如何取决于它是否是静态构建的?

c - 链接器如何解决动态可加载库中的重复符号?

C - 返回一个没有 malloc 的 char 指针

c - 伪代码中的高斯消去法

c - 使用 openssl 库获取 x509 证书哈希

php - Linux 上 php 服务内存使用率高

c - 海湾合作委员会正则表达式

c - C中生产者-消费者的线程安全环形缓冲区

node.js - Jenkins 不能单独运行 npm 或 pm2