2

我正在用 C 语言开发一个可以与 C、C++ 或 Fortran 代码一起使用的库。它使用的一种机制是捕获对堆栈、堆或数据/bss 段中页面的写入。在这种情况下,“堆”是库从映射文件中创建的特殊堆。我发现我的库未能捕获对 Fortran 应用程序中变量的写入。变量声明为

double precision u(5,I,J,K)

其中 I、J 和 K 是整数参数(即常量)。然后代码将 u 包含在一个称为“字段”的公共块中。

在GDB下调试的时候发现u的地址没有落入三个数据段中的任何一个的范围内。(因此库未能捕获写入!)然后我查看了 /proc//maps 伪文件,发现 u 的地址落入系统注释为“堆”的范围内。但是你是怎么进入这个“堆”的?在这种情况下,Fortran 77 代码不使用非标准的“allocate”关键字在堆上分配。谁能向我解释一下 Fortran 77(在 Ubuntu Linux x86-64 下)在“堆”上分配了哪些变量,以及如何首先创建这个“堆”?

4

1 回答 1

7

我在一个公共块中玩弄了一个数组。看起来,Linux 中的 .bss 段确实与堆合并(或者至少使用相同的brk(2)机制分配空间)。

这是相关的 Fortran 代码:

double precision u(5,20,20,20)
common /a/ u

产生的 GNU 汇编指令gfortran是:

.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 字节的附加数据。它仅被标记为可分配的,仅此而已 - 没有从文件中预填充数据。您还可以推断,从 VMA 0x600b80 开始,a_之前放置了 32 个字节的附加数据:u

(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)从文件中 -ed 的,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。

.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 中还是使用 .bss 分配实际上并不重要ALLOCATE()。不同之处在于 .bss 最初用零填充,而由mallocor分配的内存内容ALLOCATE()保持原样。

于 2012-05-07T12:03:13.580 回答