[英]How to determine the address range of global variables in a shared library at runtime?
在運行時,加載的共享庫中的全局變量是否保證占用一個連續的 memory 區域? 如果是這樣,是否有可能找出該地址范圍?
上下文:我們希望在 memory 中有多個共享庫的“實例”(例如協議棧實現)用於模擬目的(例如模擬具有多個主機/路由器的網絡)。 我們正在嘗試的一種方法是只加載一次庫,但通過創建和維護全局變量的“影子”集來模擬其他實例,並通過memcpy()
將適當的影子集輸入/輸出來在實例之間切換memory 庫的全局變量占用的區域。 (替代方法,如使用dlmopen()
多次加載庫,或在共享庫中引入間接訪問全局變量也有其局限性和困難。)
我們嘗試過的事情:
使用dl_iterate_phdr()
查找共享庫的數據段。 結果地址范圍並不太有用,因為(1)它沒有指向包含實際全局變量的區域,而是指向從 ELF 文件(在只讀內存中)加載的段,並且(2)它不僅包含全局變量,還有額外的內部數據結構。
將 C 中的開始/結束保護變量添加到庫代碼中,並確保(通過 linker 腳本)將它們放置在共享.data
部分的開始和結束處。 (我們使用objdump -t
驗證了這一點。)這個想法是,在運行時,所有全局變量都將位於兩個保護變量之間的地址范圍內。 然而,我們的觀察是,memory 中實際變量的相對順序與共享 object 中的地址完全不同。 一個典型的 output 是:
$ 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!
這意味着“保護變量”方法不起作用。
有什么我們忽略的嗎? 如果需要,我很樂意澄清或發布我們的測試代碼。
編輯:為了澄清,有問題的共享庫是一個第三方庫,我們不想修改其源代碼,因此尋求上述通用解決方案。
EDIT2:作為進一步澄清,以下代碼概述了我希望能夠做的事情:
// 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
EDIT3:添加dl_iterate_phdr()
output
共享庫被編譯為Position-Independent Code 。 這意味着與可執行文件不同,地址不是固定的,而是在動態鏈接期間決定的。
從軟件工程的角度來看,最好的方法是使用對象(結構)來表示所有數據並避免使用全局變量(這種數據結構通常稱為“上下文”)。 然后所有 API 函數都帶有一個上下文參數,它允許您在同一進程中擁有多個上下文。
在運行時,加載的共享庫中的全局變量是否保證占用一個連續的 memory 區域?
是的:在任何ELF
平台(例如 Linux)上,所有可寫全局變量通常都分組到一個可寫的PT_LOAD
段中,並且該段位於固定地址(在庫加載時確定)。
如果是這樣,是否有可能找出該地址范圍?
當然。 您可以使用dl_iterate_phdr
找到庫加載地址,並遍歷它提供給您的程序段。 程序頭之一將具有.p_type == PT_LOAD
, .p_flags == PF_R|PF_W
。 您想要的地址范圍是[dlpi_addr + phdr->p_vaddr, dlpi_addr + phdr->p_vaddr + phdr->p_memsz)
。
這里:
# actual addresses: completely different order:
您實際上是在查看主可執行文件中GOT
條目的地址,而不是變量本身的地址。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.