C 如何在运行时确定共享库中全局变量的地址范围?

C 如何在运行时确定共享库中全局变量的地址范围?,c,linux,shared-libraries,C,Linux,Shared Libraries,在运行时,加载的共享库中的全局变量是否保证占用连续的内存区域?如果是这样的话,有可能找到地址范围吗 上下文:我们希望在内存中有一个共享库的多个“实例”(例如协议栈实现),用于模拟(例如,模拟具有多个主机/路由器的网络)。我们正在尝试的方法之一是只加载库一次,但通过创建和维护全局变量的“阴影”集来模拟其他实例,并通过memcpy()”在库的全局变量占用的内存区域中输入/输出适当的阴影集来在实例之间切换。(使用dlmopen()多次加载库,或在共享库中引入间接访问全局变量等替代方法也有其局限性和困难

在运行时,加载的共享库中的全局变量是否保证占用连续的内存区域?如果是这样的话,有可能找到地址范围吗

上下文:我们希望在内存中有一个共享库的多个“实例”(例如协议栈实现),用于模拟(例如,模拟具有多个主机/路由器的网络)。我们正在尝试的方法之一是只加载库一次,但通过创建和维护全局变量的“阴影”集来模拟其他实例,并通过
memcpy()
”在库的全局变量占用的内存区域中输入/输出适当的阴影集来在实例之间切换。(使用
dlmopen()
多次加载库,或在共享库中引入间接访问全局变量等替代方法也有其局限性和困难。)

我们尝试过的事情:

  • 使用
    dl\u iterate\u phdr()
    查找共享库的数据段。产生的地址范围并不太有用,因为(1)它不指向包含实际全局变量的区域,而是指向从ELF文件加载的段(在只读内存中),以及(2)它不仅包含全局变量,还包含其他内部数据结构

  • 将C中的开始/结束保护变量添加到库代码中,并确保(通过链接器脚本)将它们放置在共享对象中
    .data
    部分的开始和结束处。(我们用
    objdump-t
    验证了这一点)其思想是,在运行时,所有全局变量都将位于两个保护变量之间的地址范围内。然而,我们的观察结果是,内存中实际变量的相对顺序与共享对象中地址的相对顺序大不相同。典型输出为:


EDIT3:添加了
dl\u iterate\u phdr()
output

共享库编译为。这意味着与可执行文件不同,地址不是固定的,而是在动态链接期间确定的


从软件工程的角度来看,最好的方法是使用对象(结构)来表示所有数据并避免全局变量(此类数据结构通常称为“上下文”)。然后,所有API函数都采用一个上下文参数,该参数允许您在同一进程中拥有多个上下文。

共享库编译为。这意味着与可执行文件不同,地址不是固定的,而是在动态链接期间确定的

从软件工程的角度来看,最好的方法是使用对象(结构)来表示所有数据并避免全局变量(此类数据结构通常称为“上下文”)。然后,所有API函数都接受一个上下文参数,该参数允许您在同一进程中拥有多个上下文

在运行时,加载的共享库中的全局变量是否保证占用连续的内存区域

是:在任何
ELF
平台(如Linux)上,所有可写全局文件通常被分组到单个可写
PT_LOAD
段中,该段位于固定地址(在库加载时确定)

如果是这样的话,有可能找到地址范围吗

当然可以。您可以使用
dl\u iterate\u phdr
查找库加载地址,并迭代它提供给您的程序段。其中一个程序头将具有
.p_type==PT_LOAD
.p_标志==PF_R | PF_W
。您想要的地址范围是
[dlpi_addr+phdr->p_vaddr,dlpi_addr+phdr->p_vaddr+phdr->p_memsz)

在这里:

实际地址:完全不同的顺序:

实际上,您查看的是主可执行文件中
get
项的地址,而不是变量本身的地址

在运行时,加载的共享库中的全局变量是否保证占用连续的内存区域

是:在任何
ELF
平台(如Linux)上,所有可写全局文件通常被分组到单个可写
PT_LOAD
段中,该段位于固定地址(在库加载时确定)

如果是这样的话,有可能找到地址范围吗

当然可以。您可以使用
dl_iterate_phdr
找到库加载地址,并对它提供给您的程序段进行迭代。其中一个程序头将具有
。p_type==PT_load
。p_标志==PF_R | PF_W
。您想要的地址范围是
[dlpi_addr+phdr->p_vaddr,dlpi_addr+phdr->p_vaddr+phdr->p_memsz)

在这里:

实际地址:完全不同的顺序:


实际上,您查看的是主可执行文件中
get
项的地址,而不是变量本身的地址。

请确保查看确保查看谢谢,但您的回答并没有回答原始问题。使用上下文对象的建议是有效的,基本上与其中一个相同问题中提到的备选方案(“在共享库中引入间接寻址以访问全局变量”)。为了澄清这一点,我们选择不这样做,因为这需要对库源代码进行大量更改,并且所讨论的库是第三方软件,我们希望尽可能保持完整。但我的主要观点是,如果您有多个全局变量实例,您还需要多个函数实例(由于PIC)。实际上我只使用一个全局变量实例。我希望通过使用全局变量的单独卷影/备份副本来“模拟”多个实例,并使用
memcpy
进行切换。也就是说,在调用库函数之前,我从相应的卷影集中覆盖库的变量,然后
$ objdump -t libx.so | grep '\.data'
0000000000601020 l    d  .data  0000000000000000              .data
0000000000601020 l     O .data  0000000000000000              __dso_handle
0000000000601038 l     O .data  0000000000000000              __TMC_END__
0000000000601030 g     O .data  0000000000000004              custom_data_end_marker
0000000000601028 g     O .data  0000000000000004              custom_data_begin_marker
0000000000601034 g       .data  0000000000000000              _edata
000000000060102c g     O .data  0000000000000004              global_var

$ ./prog
# output from dl_iterate_phdr()
name=./libx.so (7 segments)
    header  0: type=1 flags=5 start=0x7fab69fb0000 end=0x7fab69fb07ac size=1964
    header  1: type=1 flags=6 start=0x7fab6a1b0e08 end=0x7fab6a1b1038 size=560  <--- data segment
    header  2: type=2 flags=6 start=0x7fab6a1b0e18 end=0x7fab6a1b0fd8 size=448
    header  3: type=4 flags=4 start=0x7fab69fb01c8 end=0x7fab69fb01ec size=36
    header  4: type=1685382480 flags=4 start=0x7fab69fb0708 end=0x7fab69fb072c size=36
    header  5: type=1685382481 flags=6 start=0x7fab69bb0000 end=0x7fab69bb0000 size=0
    header  6: type=1685382482 flags=4 start=0x7fab6a1b0e08 end=0x7fab6a1b1000 size=504

# addresses obtained via dlsym() are consistent with the objdump output:
dlsym('custom_data_begin_marker') = 0x7fab6a1b1028
dlsym('custom_data_end_marker') =   0x7fab6a1b1030  <-- between the begin and end markers

# actual addresses: at completely different address range, AND in completely different order!
&custom_data_begin_marker = 0x55d613f8e018
&custom_data_end_marker =   0x55d613f8e010  <-- end marker precedes begin marker!
&global_var =               0x55d613f8e01c  <-- after both markers!
// x.c -- source for the shared library
#include <stdio.h>

int global_var = 10;

void bar() {
    global_var++;
    printf("global_var=%d\n", global_var);
}
// a.c -- main program
#include <stdlib.h>
#include <dlfcn.h>
#include <memory.h>

struct memrange {
    void *ptr;
    size_t size;
};

extern int global_var;
void bar();

struct memrange query_globals_address_range(const char *so_file)
{
    struct memrange result;
    // TODO what generic solution can we use here instead of the next two specific lines?
    result.ptr = &global_var;
    result.size = sizeof(int);
    return result;
}

struct memrange g_range;


void *allocGlobals()
{
    // allocate shadow set and initialize it with actual global vars
    void *globals = malloc(g_range.size);
    memcpy(globals, g_range.ptr, g_range.size);
    return globals;
}

void callBar(void *globals) {
    memcpy(g_range.ptr, globals, g_range.size); // overwrite globals from shadow set
    bar();
    memcpy(globals, g_range.ptr, g_range.size);  // save changes into shadow set
}

int main(int argc, char *argv[])
{
    g_range = query_globals_address_range("./libx.so");

    // allocate two shadow sets of global vars
    void *globals1 = allocGlobals();
    void *globals2 = allocGlobals();

    // call bar() in the library with a few times with each
    callBar(globals1);
    callBar(globals2);
    callBar(globals2);
    callBar(globals1);
    callBar(globals1);

    return 0;
}
#! /bin/sh
gcc -c -g -fPIC x.c -shared -o libx.so
gcc a.c -g -L. -lx -ldl -o prog
LD_LIBRARY_PATH=. ./prog