我有一些代码在编译时给我重定位错误,下面是一个说明问题的例子:
program main
common/baz/a,b,c
real a,b,c
b = 0.0
call foo()
print*, b
end
subroutine foo()
common/baz/a,b,c
real a,b,c
integer, parameter :: nx = 450
integer, parameter :: ny = 144
integer, parameter :: nz = 144
integer, parameter :: nf = 23*3
real :: bar(nf,nx*ny*nz)
!real, allocatable,dimension(:,:) :: bar
!allocate(bar(nf,nx*ny*nz))
bar = 1.0
b = bar(12,32*138*42)
return
end
使用 gfortran -O3 -g -o test test.f
编译它,我得到以下错误:
relocation truncated to fit: R_X86_64_PC32 against symbol `baz_' defined in COMMON section in /tmp/ccIkj6tt.o
但是如果我使用< code > gfortran-O3-MC model = medium-g-o test test . f 的话就可以了。还要注意,如果我使数组可分配,并在子例程中分配它,它就会工作。
我的问题是-mcmodel=medium
到底是做什么的?我的印象是代码的两个版本(一个带有可分配数组的版本和一个没有可分配
数组的版本)或多或少是等效的......
由于 bar
非常大,编译器生成静态分配,而不是在堆栈上自动分配。静态数组是使用 .comm
程序集指令创建的,该指令在所谓的 COMMON 部分创建分配。收集该部分中的符号,合并同名符号(减少到一个大小等于请求的最大大小的符号请求),然后以大多数可执行格式将其余部分映射到BSS(未初始化的数据)部分。对于 ELF 可执行文件,.bss
部分位于数据段中,就在堆的数据段部分之前(还有另一个堆部分由匿名内存映射管理,它不驻留在数据段中)。
对于小
存储器型号,32位寻址指令用于寻址x86_64上的符号。这使得代码更小,也更快。使用小
内存模型时的一些程序集输出:
movl $bar.1535, %ebx <---- Instruction length saving
...
movl %eax, baz_+4(%rip) <---- Problem!!
...
.local bar.1535
.comm bar.1535,2575411200,32
...
.comm baz_,12,16
它使用32位移动指令(5字节长)将< code>bar.1535符号的值(该值等于符号位置的地址)放入< code>RBX寄存器的低32位(高32位归零)。使用< code >分配< code>bar.1535符号本身。comm指令。随后分配< code>baz公共块的内存。因为< code>bar.1535非常大,所以< code>baz_从< code >的开头开始超过2 GiB。bss部分。这在第二条< code>movl指令中造成了问题,因为来自< code>RIP的非32位(有符号)偏移量应该用于寻址< code>b变量,其中< code>EAX的值必须移入。这仅在链接期间检测到。汇编程序本身不知道适当的偏移量,因为它不知道指令指针(< code>RIP)的值是什么(它取决于加载代码的绝对虚拟地址,这是由链接器确定的),所以它只是将偏移量设置为< code>0,然后创建类型为< code>R_X86_64_PC32的重定位请求。它指示链接器用实际的偏移量值修补< code>0的值。但是它不能这样做,因为偏移值不适合有符号的32位整数,因此会被抛出。
有了媒体
内存模型,事情看起来像这样:
movabsq $bar.1535, %r10
...
movl %eax, baz_+4(%rip)
...
.local bar.1535
.largecomm bar.1535,2575411200,32
...
.comm baz_,12,16
首先,使用 64 位即时移动指令(10 字节长)将表示 bar.1535
地址的 64 位值放入寄存器 R10
。bar.1535
符号的内存是使用 .largecomm
指令分配的,因此它以 ELF 可执行的 .lbss
部分结束。.lbss用于存储可能不适合前2 GiB的符号(因此不应使用32位指令或RIP相对寻址进行寻址),而较小的符号则转到.bss
(baz_
仍然使用.comm
而不是.largecomm
分配)。由于
.lbss
部分位于 ELF 链接器脚本中的 .bss
部分之后,因此使用 32 位 RIP 相关寻址最终不会无法访问baz_
。
所有寻址模式都在系统V ABI: AMD64架构处理器补充中有所描述。这是一个沉重的技术阅读,但对于任何真正想了解64位代码如何在大多数x86 _ 64 Unixes上工作的人来说都是必读的。
当使用< code>ALLOCATABLE数组时,< code>gfortran分配堆内存(考虑到分配的大规模,很可能实现为匿名内存映射):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
这基本上是RDI=malloc(2575411200)
。此后,通过使用存储在RDI
中的值的正偏移量访问bar
的元素:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
对于从bar
开头超过2 GiB的位置,使用了更详细的方法。例如。为了实现b=bar(12,144*144*450)
gfortran
发出:
; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
此代码不受内存模型的影响,因为对于动态分配的地址没有任何假设。此外,由于没有传递数组,因此没有构建描述符。如果您添加另一个采用假定形状数组的函数,并将<code>bar</code>传递给它,则会创建<code>bar</code<的描述符作为自动变量(即在<code>foo</code〕的堆栈上)。如果使用SAVE
属性将数组设置为静态,则描述符将放置在中。bss
部分:
movl $bar.1580, %edi
...
; RAX still holds the address of the allocated memory as returned by malloc
; Computations, computations
movl -232(%rax,%rdx,4), %eax
movl %eax, baz_+4(%rip)
第一步是准备函数调用的参数(在我的示例中< code>call boo(bar),其中< code>boo有一个接口声明它采用假定形状的数组)。它将< code>bar的数组描述符的地址移动到< code>EDI中。这是一个32位的立即移动,所以描述符应该在前2个GiB中。事实上,它是在< code >中分配的。bss在< code >小型和< code >中型内存型号中,如下所示:
.local bar.1580
.comm bar.1580,72,32
不,如果不使用< code>-mcmodel=medium,大型静态数组(如您的< code>bar)可能会超出限制。但是可分配的当然更好。对于可分配的数组,只有数组描述符必须适合2 GB,而不是整个数组。
来自GCC参考:
-mcmodel=small
Generate code for the small code model: the program and its symbols must be linked in the lower 2 GB of the address space. Pointers are 64 bits. Programs can be statically or dynamically linked. This is the default code model.
-mcmodel=kernel
Generate code for the kernel code model. The kernel runs in the negative 2 GB of the address space. This model has to be used for Linux kernel code.
-mcmodel=medium
Generate code for the medium model: The program is linked in the lower 2 GB of the address space but symbols can be located anywhere in the address space. Programs can be statically or dynamically linked, but building of shared libraries are not supported with the medium model.
-mcmodel=large
Generate code for the large model: This model makes no assumptions about addresses and sizes of sections. Currently GCC does not implement this model.