我有一些代码在编译时给我重定位错误,下面是一个说明问题的例子:
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
但如果我使用gfortran -O3 -mcmodel=medium -g -o test test.f
,它就有效。另请注意,如果我将数组分配并在子例程中分配它,它就可以工作。
我的问题是-mcmodel=medium
究竟做了什么?我的印象是代码的两个版本(带有allocatable
数组的那个和没有代码的那个)或多或少等同......
答案 0 :(得分:28)
由于bar
非常大,编译器会生成静态分配而不是堆栈上的自动分配。使用.comm
程序集指令创建静态数组,该指令在所谓的COMMON部分中创建分配。收集该部分中的符号,合并相同名称的符号(缩减为一个符号请求,其大小等于所请求的最大大小),然后以大多数可执行格式将其映射到BSS(未初始化数据)部分。对于ELF可执行文件,.bss
部分位于数据段中,就在堆的数据段部分之前(存在另一个由匿名内存映射管理的堆部分,它不驻留在数据段中)。
使用small
内存模型,32位寻址指令用于寻址x86_64上的符号。这使代码更小,也更快。使用small
内存模型时的某些程序集输出:
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个字节长)将bar.1535
符号的值(此值等于符号位置的地址)放入{{1的低32位)寄存器(高32位变为零)。 RBX
符号本身是使用bar.1535
指令分配的。之后会分配.comm
COMMON块的内存。由于baz
非常大,bar.1535
从baz_
部分的开头结束时超过2 GiB。这会在第二个.bss
指令中出现问题,因为应该使用movl
的非32位(带符号)偏移来处理RIP
变量,其中b
的值为EAX
被搬进去。仅在链接时检测到此情况。汇编程序本身不知道适当的偏移量,因为它不知道指令指针(RIP
)的值是什么(它取决于加载代码的绝对虚拟地址,这取决于链接器),因此它只是放置0
的偏移量,然后创建类型为R_X86_64_PC32
的重定位请求。它指示链接器使用实际偏移值修补0
的值。但它无法做到这一点,因为偏移值不适合签名的32位整数,因此会失败。
使用medium
内存模型,事情如下所示:
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 exectuable的.lbss
部分结束。 .lbss
用于存储可能不适合前2 GiB的符号(因此不应使用32位指令或RIP相对寻址进行寻址),而较小的内容则转移到.bss
({仍然使用baz_
而非.comm
分配{1}}。由于.largecomm
部分位于ELF链接描述文件中的.lbss
部分之后,因此使用32位RIP相关的寻址不会导致.bss
无法访问。
System V ABI: AMD64 Architecture Processor Supplement中描述了所有寻址模式。这是一个繁重的技术阅读,但对于任何真正想要理解64位代码如何在大多数x86_64 Unix上运行的人来说,必须阅读。
当使用baz_
数组时,ALLOCATABLE
分配堆内存(很可能在给定大量分配的情况下实现为匿名内存映射):
gfortran
这基本上是movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
。从那时起,使用RDI = malloc(2575411200)
中存储的值的正偏移来访问bar
的元素:
RDI
对于movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
开头超过2 GiB的位置,使用更精细的方法。例如。实施bar
b = bar(12,144*144*450)
发布:
gfortran
此代码不受内存模型的影响,因为没有假设动态分配的地址。此外,由于没有传递数组,因此没有构建描述符。如果添加另一个采用假定形状数组并将; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
传递给它的函数,则会创建bar
的描述符作为自动变量(即在bar
的堆栈上)。如果使用foo
属性使数组成为静态,则描述符将放在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)
,其中call boo(bar)
有一个接口,声明它采用假定形状数组)。它将boo
的数组描述符的地址移动到bar
。这是一个32位的立即移动,因此描述符应该在前2 GiB中。实际上,它是在EDI
和.bss
内存模型的small
中分配的,如下所示:
medium
答案 1 :(得分:8)
不,如果您不使用bar
,大型静态数组(因为您的-mcmodel=medium
)可能超出限制。但是,可分配的东西当然更好。对于allocatables,只有数组描述符必须适合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.