我在C中开发一个应该使用C,C ++或Fortran代码的库。它使用的一种机制是捕获堆栈,堆或数据/ bss段中的页面的写入。在这种情况下,“堆”是库从映射文件创建的特殊堆。我发现我的库未能在Fortran应用程序中捕获对变量的写入。变量声明为
double precision u(5,I,J,K)
其中I,J和K是整数参数(即常数)。然后代码将u包含在一个名为“fields”的公共块中。
在GDB下调试时,我发现u的地址不属于三个数据段中的任何一个。 (因此库无法捕获写入!)然后我查看/ proc // maps伪文件,发现u的地址落入系统注释为“堆”的范围内。但你是怎么进入这个“堆”的?在这种情况下,Fortran 77代码不使用非标准的“allocate”关键字在堆上进行分配。有人可以向我解释Fortran 77(Ubuntu Linux x86-64下)在“堆”上分配的变量,以及首先如何创建这个“堆”?
答案 0 :(得分:7)
我在公共区块玩了一个数组。它看起来像是Linux中的.bss段真的与堆合并(或者至少使用相同的brk(2)
机制分配空间)。
以下是相关的Fortran代码:
double precision u(5,20,20,20)
common /a/ u
gfortran
生成的GNU程序集指令是:
.comm a_,320000,32
它声明了一个名为a_
的公共符号,其大小为320000字节,应该在32字节边界上对齐。当链接器看到此声明而没有a_
的其他定义时,它会在.bss中为它保留空间,这在生成的二进制文件上运行objdump
时可以清楚地看到:
Sections:
Idx Name Size VMA LMA File off Algn
...
22 .data 00000010 0000000000600b40 0000000000600b40 00000b40 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 0004e220 0000000000600b60 0000000000600b60 00000b50 2**5
ALLOC
...
这里.bss是320000(0x4e200)字节加上一些32字节的附加数据。它仅被标记为可分配,仅此而已 - 没有数据从文件中预先填充。您还可以推断,在a_
u
开始之前,在{0}} VMA 0x600b80开始之前,已放置32字节的附加数据:
(gdb) info address u
Symbol "u" is static storage at address 0x600b80.
(gdb) info symbol &u
a_ in section .bss of /path/to/a.out
u
实际上只是Fortran主函数中局部变量的符号,而a_
是全局可见存储。这就是为什么你可以在不同的子程序/函数中以不同的方式命名数组,但如果将它们放在适当的公共块中,它仍然可以访问相同的内存。
看起来.bss的尴尬VMA是ELF文件中.data段偏移的结果,因为.bss紧跟在内存中的.data段之后。在Linux中加载.data段的方式是mmap(2)
- 来自带有MAP_PRIVATE
的文件,它为映射提供了写时复制语义:
00400000-00401000 r-xp 00000000 00:1d 25681168 /path/to/a.out
00600000-00601000 rw-p 00000000 00:1d 25681168 /path/to/a.out <-- .data
00601000-00670000 rw-p 00000000 00:00 0 [heap]
.bss在与.data映射相同的页面中启动,这是有意义的,因为它们都保存读/写数据并且可以写入,并且通过不在单独的页面上启动.bss来节省一点VM。 / p>
.data细分之后的所有内容都没有通过文件映射备份,因此属于[heap]
中/proc/pid/maps
可见的动态可调空间。通过使用brk(2)
移动数据段的末尾(所谓的程序中断)来控制此空间以及堆本身。内核中的ELF加载程序最初将程序中断移动到足以为.bss保留空间,可执行文件的strace
可以看到:
execve("./a.out", ["a.out"], [/* 230 vars */]) = 0
brk(0) = 0x64f000 <-- already moved past the .bss
我们知道.data段从00600000开始.bss从00600B60开始。公共块分配在0x600b80,其大小为0x4e200,因此它以0x64ed80结束,向上舍入到页边界给出0x64f000。如果没有其他动态链接库自己分配空间,那么真正的程序堆就会开始。
由于动态内存分配器malloc(3)
使用相同的brk(2)
机制(或匿名mmap(2)
进行大型分配,或者当数据段大小的限制用尽时)它实际上没有如果数组是在.bss中,或者是用ALLOCATE()
分配的话。区别在于.bss最初填充为零,而malloc
或ALLOCATE()
分配的内存内容保留原样。