【问题标题】:reading the environment when executing ELF IFUNC dispatch functions执行 ELF IFUNC 调度函数时读取环境
【发布时间】:2013-12-19 15:12:42
【问题描述】:

最近在 Linux 上的 ELF 工具中的 IFUNC 机制允许在运行时选择函数的实现。查看 GCC 文档中的 iunc 属性以获得更详细的描述:http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html IFUNC机制的另一种描述:http://www.agner.org/optimize/blog/read.php?i=167

我想根据环境变量的值来选择我的实现。但是,我的实验表明,当解析器函数运行时,libc(至少是关于环境的部分)尚未初始化。因此,经典接口(extern char**environ 或 getenv())不起作用。

有人知道如何在很早的阶段访问Linux中的程序环境吗?该环境是由内核在 execve(2) 系统调用时设置的,因此它在早期初始化时已经位于程序地址空间中的某个位置(但具体在哪里?)。

提前致谢 文森特

要测试的程序:

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

extern char** environ;
char** save_environ;
char* toto;
int saved=0;

extern int fonction ();

int fonction1 () {
return 1;
}

int fonction2 () {
return 2;
}

static typeof(fonction) * resolve_fonction (void) {
saved=1;
save_environ=environ;
toto=getenv("TOTO");
/* no way to choose between fonction1 and fonction2 with the TOTO envvar */
return fonction1;
}

int fonction () __attribute__ ((ifunc ("resolve_fonction")));

void print_saved() {
printf("saved: %dn", saved);
if (saved) {
printf("prev environ: %pn", save_environ);
printf("prev TOTO: %sn", toto);
}
}

int main() {

print_saved();
printf("main environ: %pn", environ);
printf("main environ[0]: %sn", environ[0]);
printf("main TOTO: %sn", getenv("TOTO"));
printf("main value: %dn", fonction());

return 0;
}

编译和执行:

$ gcc -Wall -g ifunc.c -o ifunc
$ env TOTO=ok ./ifunc
saved: 1
prev environ: (nil)
prev TOTO: (null)
main environ: 0x7fffffffe288
main environ[0]: XDG_VTNR=7
main TOTO: ok
main value: 1
$ 

在解析器函数中,environ 为 NULL,getenv("TOTO") 返回 NULL。在main函数中,信息就在这里。

【问题讨论】:

  • 您的操作级别低于 C 运行时。所以你需要阅读libc的代码,找出它是从哪里得到环境的。

标签: c linux glibc elf


【解决方案1】:

函数指针

我发现没有办法在早期合法地使用 env。解析器函数比 preinit_array 函数更早地在链接器中运行。解决这个问题的唯一合法方法是使用函数指针并决定在 .preinit_array 部分的函数中使用什么函数:

extern char** environ;
int(*f)();

void preinit(int argc, char **argv, char **envp) {
        f = f1;
        environ = envp; // actually, it is done a bit later
        char *v = getenv("TOTO");
        if (v && strcmp(v, "ok") == 0) {
                f = f2;
        }
}

__attribute__((section(".preinit_array"))) typeof(preinit) *__preinit = preinit;

ifunc & GNU ld 内部

Glibc 的链接器 ld 包含一个局部符号 _environ 并且它已被初始化,但提取它相当困难。我找到了另一种方法,但它有点棘手,而且相当不可靠。

在链接器的入口点 _start 仅初始化堆栈。程序参数和环境值通过堆栈发送到进程。参数按以下顺序存储:

argc, argv, argv + 1, ..., argv + argc - 1, NULL, ENV...

链接器 ld 共享一个全局符号 _dl_argv,它指向堆栈上的这个位置。借助它,我们可以提取所有需要的变量:

extern char** environ;
extern char **_dl_argv;

char** get_environ() {
    int argc = *(int*)(_dl_argv - 1);
    char **my_environ = (char**)(_dl_argv + argc + 1);
    return my_environ;
}

typeof(f1) * resolve_f() {
    environ = get_environ();
    const char *var = getenv("TOTO");
    if (var && strcmp(var, "ok") == 0) {
        return f2;
    }
    return f1;
}

int f() __attribute__((ifunc("resolve_f")));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-16
    • 1970-01-01
    • 2016-01-11
    • 2018-06-16
    相关资源
    最近更新 更多