Od bar
jest dość duży kompilator generuje statyczne przydzielanie zamiast automatycznej alokacji na stosie. Macierze statyczne są tworzone za pomocą dyrektywy montażu .comm
, która tworzy przydział w tak zwanej sekcji WSPÓLNEJ. Symbole z tej sekcji są gromadzone, symbole o tej samej nazwie są scalane (zredukowane do jednego żądania symbolu o rozmiarze równym największemu żądanemu rozmiarowi), a następnie reszta jest odwzorowywana w sekcji BSS (niezainicjowane dane) w większości wykonywalnych formatów. W przypadku plików wykonywalnych ELF sekcja .bss
znajduje się w segmencie danych, tuż przed częścią segmentu danych sterty (istnieje inna partia sterty zarządzana przez anonimowe odwzorowania pamięci, które nie znajdują się w segmencie danych).
W modelu pamięci 32-bitowej small
używane są instrukcje adresowania symboli na architekturze x86_64. Dzięki temu kod jest mniejszy, a także szybszy. Pewne montażowej przy użyciu small
modelu pamięci:
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
używa się 32-bitowy MOVE (długość 5 bajtów) do wprowadzenia wartości bar.1535
symbolu (wartość ta równa się adres lokalizacji symbolu) do dolne 32 bity rejestru RBX
(górne 32 bity zostają wyzerowane). Sam symbol bar.1535
jest przydzielany przy użyciu dyrektywy .comm
. Pamięć dla bloku COMMON baz
jest przydzielana później. Ponieważ bar.1535
jest bardzo duży, baz_
kończy się więcej niż 2 GiB od początku sekcji .bss
. To stanowi problem w drugiej instrukcji movl
, ponieważ nie 32-bitowe (podpisane) przesunięcie od RIP
powinno być używane do adresowania zmiennej b
, do której należy przenieść wartość EAX
. Jest to wykrywane tylko w czasie połączenia. Sam asembler nie zna odpowiedniego offsetu, ponieważ nie wie, jaka byłaby wartość wskaźnika instrukcji (RIP
) (zależy to od bezwzględnego adresu wirtualnego, w którym kod jest załadowany i jest określany przez łącznik), więc po prostu ustawia przesunięcie 0
, a następnie tworzy żądanie relokacji typu R_X86_64_PC32
. Instruuje linker, aby poprawił wartość 0
z rzeczywistą wartością offsetu. Ale nie może tego zrobić, ponieważ wartość przesunięcia nie zmieściłaby się wewnątrz podpisanej 32-bitowej liczby całkowitej, a tym samym wygasła.
Z modelu medium
pamięci w miejsce rzeczy wyglądać następująco:
movabsq $bar.1535, %r10
...
movl %eax, baz_+4(%rip)
...
.local bar.1535
.largecomm bar.1535,2575411200,32
...
.comm baz_,12,16
Pierwszy 64-bitowy natychmiastowe instrukcja ruch (długość 10 bajtów) służy do wprowadzenia 64-bitowa wartość, która reprezentuje adres bar.1535
w rejestrze R10
. Pamięć do symbolu bar.1535
jest przydzielana przy użyciu dyrektywy .largecomm
, a zatem kończy się w sekcji .lbss
z możliwym do wybrania ELF. .lbss
służy do przechowywania symboli, które mogą nie pasować do pierwszych 2 GiB (i dlatego nie powinny być adresowane za pomocą instrukcji 32-bitowych lub adresowania względnego RIP), podczas gdy mniejsze rzeczy należą do .bss
(baz_
jest nadal przydzielane przy użyciu .comm
, a nie .largecomm
). Ponieważ sekcja .lbss
jest umieszczana po sekcji .bss
w skrypcie łącznika ELF, baz_
nie będzie w końcu niedostępna przy użyciu 32-bitowego adresowania związanego z RIP.
Wszystkie tryby adresowania są opisane w System V ABI: AMD64 Architecture Processor Supplement. Jest to ciężki techniczny odczyt, ale musi być przeczytany dla każdego, kto naprawdę chce zrozumieć, jak działa 64-bitowy kod na większości Uniksów x86_64.
Gdy ALLOCATABLE
tablica jest używana zamiast gfortran
przydziela pamięć sterty (najprawdopodobniej realizowany jako anonimowego mapy pamięci ze względu na duży rozmiar alokacji):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
Jest to w zasadzie RDI = malloc(2575411200)
. Odtąd elementów bar
są dostępne za pomocą pozytywnych przesunięcia od wartości zapisanej w RDI
:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
Na miejscach, które są więcej niż 2 Gib od początku bar
, stosuje się metody bardziej skomplikowane. Na przykład. wdrożyć b = bar(12,144*144*450)
gfortran
emituje:
; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
Kod ten nie ma wpływu na modelu pamięci, ponieważ nic nie zakłada się o adres gdzie dynamiczny przydział zostanie wykonany. Ponadto, ponieważ tablica nie jest przekazywana, nie buduje się deskryptora. Jeśli dodasz inną funkcję, która przyjmuje tablicę o założonym kształcie i przekazuje do niej bar
, tworzony jest deskryptor dla bar
jako zmienna automatyczna (tj. Na stosie foo
). Jeśli tablica jest statyczna z atrybutem SAVE
, deskryptor jest umieszczony w sekcji .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)
Pierwszy ruch przygotowuje argument wywołania funkcji (w moim przykładowym przypadku call boo(bar)
gdzie boo
posiada interfejs, który deklaruje to jako przyjmowanie tablicy o założonym kształcie). Przenosi adres deskryptora tablic z bar
do EDI
. Jest to 32-bitowy natychmiastowy ruch, więc deskryptor powinien znajdować się w pierwszych 2 GiB. Rzeczywiście, to jest alokowana w .bss
w obu small
i medium
pamięci modeli tak:
.local bar.1580
.comm bar.1580,72,32
To bardzo miłe wyjaśnienie. Dzięki. Daje mi to dobry początek, aby zagłębić się w gąszcz tych rzeczy (czego właśnie szukałem). – mgilson
@mgilson, dla uzupełnienia odpowiedzi, dodałem również wyjaśnienia do tego, co dzieje się, gdy 'bar' jest przekazywany przez deskryptor do innej podprocedury. –