You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux环境下使用LD_PRELOAD+initfirst时读取环境变量的问题及相关机制疑问

Linux环境下使用LD_PRELOAD+initfirst时读取环境变量的问题及相关机制疑问

我之前也踩过这个坑——给LD_PRELOAD加载的共享库加了-Wl,-z,initfirst后,构造函数里调用getenv()居然返回NULL,当时也是一脸懵。咱们来拆解这个问题,一步步解决:

一、怎么在initfirst的情况下读取环境变量?

因为initfirst会让共享库的构造函数在glibc完成初始化之前就被动态链接器调用,而getenv()依赖glibc维护的__environ变量,这时候__environ还没被赋值,自然拿不到环境变量。针对Linux amd64+gcc+glibc的场景,有两个可靠的办法:

方法1:直接读取/proc/self/environ

Linux的proc文件系统会把进程的环境变量以\0分隔的形式存在/proc/self/environ里,我们可以直接读取这个文件来解析目标环境变量:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void setup(void) __attribute__((constructor));

void setup(void) {
    int fd = open("/proc/self/environ", O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Failed to open /proc/self/environ\n");
        return;
    }

    char buf[1024];
    ssize_t read_len = read(fd, buf, sizeof(buf) - 1);
    close(fd);

    if (read_len <= 0) {
        fprintf(stderr, "Failed to read environment data\n");
        return;
    }
    buf[read_len] = '\0';

    // 遍历环境变量(以\0分隔)
    char *env_entry = buf;
    while (*env_entry) {
        if (strncmp(env_entry, "FOO=", 4) == 0) {
            fprintf(stderr, "FOO=\"%s\"\n", env_entry + 4);
            break;
        }
        env_entry += strlen(env_entry) + 1;
    }
}

编译和运行命令不变,这次就能正常输出FOO="123"了。唯一要注意的是,如果进程运行在没有挂载proc文件系统的环境(比如特殊chroot场景),这个方法会失效,但一般Ubuntu环境下没问题。

方法2:通过栈直接获取内核传递的envp

Linux内核启动进程时,会把argc、argv、envp依次放在栈上。我们可以通过内联汇编直接从栈上计算出envp的地址,完全不依赖glibc:

#include <stdio.h>
#include <string.h>

void setup(void) __attribute__((constructor));

// 通过内联汇编获取内核传递的envp
char **get_kernel_envp(void) {
    char **envp;
    __asm__ volatile (
        // amd64栈布局:栈顶是argc,接着是argv指针数组,最后是envp数组
        "movq 8(%%rsp), %%rax\n"    // 先拿到argv的地址(argc在0(rsp))
        "movq (%%rax), %%rcx\n"     // 取出argc的值
        "leaq 8(%%rax, %%rcx, 8), %%rax\n"  // envp = argv + (argc + 1)*8(跳过argc个argv元素+1个NULL)
        "movq %%rax, %0\n"
        : "=r"(envp)
        :
        : "%rax", "%rcx"
    );
    return envp;
}

void setup(void) {
    char **envp = get_kernel_envp();
    while (*envp) {
        if (strncmp(*envp, "FOO=", 4) == 0) {
            fprintf(stderr, "FOO=\"%s\"\n", *envp + 4);
            break;
        }
        envp++;
    }
}

这个方法更底层,完全不依赖外部文件,在任何标准Linux amd64环境下都能工作。

二、__environ是被谁初始化的?

答案是:glibc的启动代码(crt0),具体流程是这样的:

  1. 内核启动进程时,把argc、argv、envp放在进程的栈上,然后跳转到进程的_start入口(这个入口是glibc提供的crt0.o里的代码)。
  2. _start函数会把内核传递的envp地址赋值给glibc全局变量__environ,之后才会调用__libc_start_main完成libc的初始化工作。
  3. 正常情况下(没有initfirst),共享库的构造函数会在__libc_start_main之后被动态链接器调用,这时候__environ已经初始化完成,getenv()就能正常工作。
  4. 但加了initfirst后,动态链接器会跳过libc初始化步骤,优先调用这个共享库的构造函数,这时候__environ还没被赋值,所以getenv()返回NULL。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:53:03