我需要得到以下过程段的开始和结束地址:代码,数据,堆栈,环境。 我明白它是如何在内存中,但不知道如何使用API调用或其他东西。 我已经find了如何使用这段代码开始一些细分市场
#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; }
但我不知道如何find每个细分市场的末端。 我只想到,一个细分市场的结束是另一个细分市场的开始。 请解释如何使用C和linux API来做到这一点。
我不确定数据或堆段是否定义良好和独特(特别是在多线程应用程序中,或者仅在使用动态库(包括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) (然后在fgets
或readline
上循环,最后快速地fclose
)。 也许dladdr(3)可能是相关的。
你也可以读你程序的ELF头文件,比如/proc/self/exe
。 另见readelf(1)和objdump(1) & execve(2) & elf(5) & ld.so(8) & libelf 。 另请阅读Levine的Linkers&Loaders书籍和Drepper的论文: 如何编写共享库 。
另请参阅相关问题的回答(以及该问题 )。 注意最近的Linux系统有ASLR ,所以在同一个环境下运行同一个程序的两个类似进程的地址布局是不一样的。
尝试一下(1)一些简单的命令或你的程序。 你会更多地了解相关的系统调用(2) 。 另请阅读高级Linux编程
见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()
如上面的示例代码中的main()
获取环境变量,但也可以从地址&argv[0]
开始走上堆栈。 在最后一个指向命令行参数字符串的指针之后,有一个NULL值字(32或64位,取决于CPU)。 之后,NULL就是环境。
堆栈的顶部几乎不可能以编程方式获得 – 现代操作系统都做“地址空间布局随机化”(ASLR)来缓解缓冲区溢出。 堆栈的“结束”是模糊的,因为您可以在堆栈中分配(通过递归或alloca()
),直到您运行到堆栈顶部。 所以堆栈的“结束”取决于有问题的程序的分配模式。
你也应该知道ELF辅助矢量。 请参阅man getauxval
的一个C语言界面, 这篇文章为一些解释。 用户程序永远不会使用ELF辅助矢量,但它与动态链接密切相关。
正如另一条评论所说,文本,数据和堆栈段的概念在Linux上并不存在。 程序文本分布在共享库上,内存分配由mmap()
而不是brk()
使分配的数据遍布整个程序的地址空间。
也就是说,可以使用brk()
系统调用来查找数据段的结尾,并且可以使用符号etext
, edata
和end
来查找可执行文件的边界。 文本段的开始是传统固定的(也称为“加载地址”),并且取决于体系结构和链接器配置。 注意你的程序很可能会在你的二进制的文本部分之外执行代码,并且很可能不会用brk分配任何动态的内存。
请参阅相应的手册页获取更多详细信息。
Windows和Linux的当前版本使用平坦的地址空间,这意味着代码和数据段是相同的,几乎总是从0到2 ^ 32-1(对于32位系统)和2 ^ 64-1(对于64位)位系统)。 除了共享内存之外,不同的进程通常具有完全不同的地址空间。 通常只有地址空间的某些部分有任何内存映射到它,并且由于硬件限制,有些部分可能甚至不可寻址。
链接器的代码和数据段成为可运行映像的一部分,在Linux下常见的ELF格式增加了一些其他的复杂性。 访问是高度操作系统特定的,因此不是一个真正的C ++问题。
在Windows下,您可以通过GetmoduleeHandle(0)获得一个指向已加载图像的开始位置的指针。 通过遍历可执行文件头,您可以找到COFF部分表,该表允许您将映射的可执行映像的所有地址映射到各自的部分。 对其他地址进行分类比较困难; 他们可能属于其他映射的运行图像(加载的DLL),或者它们可能属于以另一种方式分配的地址范围,即直接通过VirtualAlloc()或以某种方式间接(HeapAlloc(),内存映射文件)。
如果你只想打印漂亮的堆栈跟踪,那么现在有很多现成的库可以为你做。 如果你想做校验,那么情况会变得复杂得多。 更好地使用代码签名或现成的库。 你问题的真正答案取决于你真正的问题实际上是…