获取进程段C / C ++的开始和结束

时间:2014-10-18 21:51:51

标签: c linux unix memory-management system

我需要获取以下流程段的起始和结束地址:代码,数据,堆栈,环境。我理解它是如何位于内存中的,但不知道如何使用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执行此操作。

4 个答案:

答案 0 :(得分:4)

我不确定数据或堆段是否定义良好且唯一(特别是在多线程应用程序中,或仅在使用动态库的应用程序中,包括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)&amp; execve(2)&amp; elf(5)&amp; ld.so(8)&amp; libelf。另请阅读Levine's Linkers & Loaders本书和Drepper's paper: How To Write Shared Libraries

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

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

答案 1 :(得分: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个符号的地址是文本结束后的第一个地址,初始化数据和未初始化的数据段。

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

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

您还应该了解ELF辅助载体。有关C语言界面,请参阅man getauxval,有关说明,请参阅this article。用户程序永远不会使用ELF辅助矢量,但它与动态链接密切相关。

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:0)

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

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

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

如果您只想打印漂亮的堆栈跟踪或其他什么,那么有很多现成的库可以为您完成。如果你想做校验和,那么事情变得复杂得多;更好地使用代码签名或现成的库。你问题的真正答案取决于你真正的问题实际上是什么......