簡體   English   中英

如何在運行時確定共享庫中全局變量的地址范圍?

[英]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!

這意味着“保護變量”方法不起作用。

  • 也許我們應該遍歷全局偏移表(GOT)並從那里收集全局變量的地址? 但是,如果可能的話,似乎沒有官方的方法可以做到這一點。

有什么我們忽略的嗎? 如果需要,我很樂意澄清或發布我們的測試代碼。

編輯:為了澄清,有問題的共享庫是一個第三方庫,我們不想修改其源代碼,因此尋求上述通用解決方案。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM