【问题标题】:Get start and end of process segments C/C++获取进程段的开始和结束 C/C++
【发布时间】:2014-12-14 05:04:29
【问题描述】:

我需要获取以下进程段的开始和结束地址:代码、数据、堆栈、环境。我了解它如何位于内存中,但不知道如何使用 api 调用或其他方式获取它。我已经找到了如何使用这段代码开始一些片段

#include <stdio.h>

int temp_data = 100;
static int temp_bss;

void print_addr ( void )
{
        int local_var = 100;
        int *code_segment_address = ( int* ) &print_addr;
        int *data_segment_address = &temp_data;
        int *bss_address = &temp_bss;
        int *stack_segment_address = &local_var;

        printf ( "\nAddress of various segments:" );
        printf ( "\n\tCode Segment : %p" , code_segment_address );
        printf ( "\n\tData Segment : %p" , data_segment_address );
        printf ( "\n\tBSS : %p" , bss_address );
        printf ( "\n\tStack Segment : %p\n" , stack_segment_address );

}

int main ( )
{
        print_addr ();
        return 0;
}

但我不知道如何找到每个段的结尾。我唯一的想法是一个段的结束是另一个段的开始。 请解释我如何使用 C 和 linux API 来做到这一点。

【问题讨论】:

  • 你想从内存中的一个进程中得到这个?或者来自磁盘上的 ELF 可执行文件?无论哪种方式,信息都在 ELF 标头中。你应该看看 ELF 规范。
  • 你为什么要问?您的应用程序是多线程的吗?
  • 我很好奇;为什么你需要得到这些东西?
  • 您计算代码段开头的代码不正确。很难预测代码最终在可执行文件中的顺序,但链接器通常会在第一个函数之前插入 PLT 表,因此您永远不会仅通过获取函数的地址来获得代码段的开头。跨度>
  • 这同样适用于您的其他代码。您的print_addr() 函数不会是第一个堆栈帧,temp_data 之前的数据部分中还会有其他变量。请考虑参考objdump 的输出,了解您的可执行文件的外观。

标签: c linux unix memory-management system


【解决方案1】:

我不确定数据或堆段是否定义明确且唯一(特别是在多线程应用程序中,或者只是在使用动态库的应用程序中,包括libc.so)。换句话说,不再有任何明确定义的文本、数据或堆段的开始和结束,因为今天一个进程有很多这样的段。所以你的问题在一般情况下甚至没有意义。

大多数malloc 实现使用mmap(2)munmap 远远超过sbrk

您应该阅读有关proc(5) 的更多信息。特别是,您的应用程序可以读取/proc/self/maps(或/proc/1234/maps 用于pid 1234 的进程)或/proc/self/smaps;尝试cat /proc/self/maps 并考虑在"/proc/self/maps" 上使用fopen(3)(然后在fgetsreadline 上循环,最后快速fclose)。也许dladdr(3) 可能是相关的。

您还可以阅读程序的 ELF 标头,例如的/proc/self/exe。另见readelf(1)objdump(1) & execve(2) & elf(5) & ld.so(8) & libelf。另请阅读Levine's Linkers & Loaders book 和Drepper's paper: How To Write Shared Libraries

另请参阅this answer 的相关问题(以及that question)。注意最近的Linux系统有ASLR,所以在相同环境下运行相同程序的两个相似进程的地址布局会有所不同。

也尝试strace(1) 一些简单的命令或您的程序。你会更了解相关的syscalls(2)。另请阅读Advanced Linux Programming

【讨论】:

  • 谢谢回答,你能举个例子如何使用proc获取地址。
  • 不再有单个代码段。在终端中运行cat /proc/self/maps 并尝试了解其输出。
  • 哦((我对此感到非常失望,我是新手。您能否提供一个从该文件中获取任何段地址的示例?提前谢谢
  • @ketazafor 现代 Linux 系统上不再有文本和数据段之类的东西。 tex 分布在多个共享库中,数据分散在来自多个mmap() 调用的地址空间中。你想要的根本不存在了。
  • @ketazafor:所有相关信息都在这个答案中。细节供您发现和理解。
【解决方案2】:

请参阅man 3 end 以获得帮助:

#include <stdio.h>
extern etext;
extern edata;
extern end;
int
main(int ac, char **av, char **env)
{
        printf("main %p\n", main);
        printf("etext %p\n", &etext);
        printf("edata %p\n", &edata);
        printf("end %p\n", &end);
        return 0;
}

这3个符号的地址分别是文本结束后的第一个地址、已初始化的数据和未初始化的数据段。

您可以通过main() 的第三个参数获取enivonrment 变量,如上面的示例代码中所示,但您也可以从地址&amp;argv[0] 开始向上遍历堆栈。在指向命令行参数字符串的最后一个指针之后有一个 NULL 值字(32 位或 64 位,具体取决于 CPU)。之后是 NULL 环境。

堆栈的顶部几乎不可能以编程方式获得 - 现代操作系统都执行“地址空间布局随机化”(ASLR) 以缓解缓冲区溢出。堆栈的“结束”是模糊的,因为您可以在堆栈上分配(通过递归或alloca()),直到您遇到堆顶。所以堆栈的“结束”取决于相关程序的分配模式。

您还应该了解 ELF 辅助向量。有关 C 语言接口,请参阅 man getauxval,有关说明,请参阅 this article。用户程序从不使用 ELF 辅助向量,但它与动态链接密切相关。

【讨论】:

  • 这与当前的 Linux 系统和动态链接的可执行文件和多线程进程不太相关。它在上个世纪更为相关。
  • 请注意,访问环境的可移植方式是通过全局变量environ
【解决方案3】:

正如另一条评论中所说,文本、数据和堆栈段的概念在今天的 Linux 上并不存在。程序文本分布在共享库中,内存分配使用mmap() 而不是brk() 完成,导致分配的数据分布在程序的整个地址空间中。

也就是说,您可以使用brk() 系统调用来查找数据段的结尾,您可以使用符号etextedataend 来查找可执行文件的边界。文本段的开头传统上是固定的(也称为“加载地址”),取决于体系结构和链接器配置。请注意,您的程序很可能会在二进制文件的文本部分之外执行代码,并且很可能不会使用 brk 分配任何动态内存。

有关详细信息,请参阅相应的手册页。

【讨论】:

    【解决方案4】:

    当前版本的 Windows 和 Linux 使用平面地址空间,这意味着代码和数据是相同的,并且几乎总是从 0 到 2^32-1(对于 32 位系统)和 2^64-1(对于 64 位系统)。除了共享内存,不同的进程通常有完全不同的地址空间。通常只有部分地址空间有任何内存映射到它,有些部分甚至可能由于硬件限制而无法寻址。

    链接器的代码和数据段成为可运行映像的,Linux 下常见的 ELF 格式为此添加了一些不可告人的复杂性。访问是高度特定于操作系统的,因此不是真正的 C++ 问题。

    在 Windows 下,您可以通过 GetModuleHandle(0) 获得指向加载图像开始的指针。通过遍历可执行文件头,您可以找到 COFF 节表,它允许您将作为映射的可执行映像一部分的所有地址反向映射到它们各自的节。对其他地址进行分类比较困难;它们可能属于其他映射的运行映像(加载的 DLL),或者它们可能属于以另一种方式分配的地址范围,即直接通过 VirtualAlloc() 或以某种方式间接分配(HeapAlloc()、内存映射文件等)。

    如果您只想打印漂亮的堆栈跟踪或其他任何东西,那么有很多现成的库可以为您做到这一点。如果您想做校验和,那么事情会变得复杂得多;最好使用代码签名或现成的库。你问题的真正答案取决于你真正的问题是什么......

    【讨论】:

      猜你喜欢
      • 2022-11-05
      • 2012-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-17
      • 2023-03-28
      • 1970-01-01
      相关资源
      最近更新 更多