Počáteční adresa je obvykle nastavena skriptem linkeru.
Například v GNU/Linuxu se podívejte na /usr/lib/ldscripts/elf_x86_64.x
vidíme:
...
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); \
. = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
Hodnota 0x400000
je výchozí hodnota pro SEGMENT_START()
fungovat na této platformě.
Další informace o skriptech linkeru naleznete v příručce linkeru:
% info ld Scripts
ld
Výchozí skript linkeru má hodnotu 0x400000
hodnota zapečená pro spustitelné soubory, které nejsou ve formátu PIE.
PIE (Position Independent Executables) nemají výchozí základní adresu; jsou vždy přemístěny jádrem s jádrem výchozí je 0x0000555...
plus nějaký posun ASLR, pokud není ASLR pro tento proces nebo celý systém zakázán. ld
nemá nad tím kontrolu. Všimněte si, že většina moderních systémů konfiguruje GCC pro použití -fPIE -pie
ve výchozím nastavení, takže projde -pie
až ld
a změní C na asm, který je nezávislý na poloze. Ručně psaný asm se musí řídit stejnými pravidly, pokud jej propojíte tímto způsobem.
Ale co dělá 0x400000
(4 MiB) dobrá výchozí hodnota?
Musí být vyšší než mmap_min_addr
=65536 =ve výchozím nastavení 64 kB.
A to, že je hodně daleko od 0, dává mnohem více prostoru k ochraně proti NULL deref s offsetem čtení .text
nebo .data
/.bss
paměť (array[i]
kde array
je NULL). I bez zvýšení mmap_min_addr
(pro které to ponechává prostor bez porušení spustitelných souborů), obvykle mmap
náhodně vybírá vysoké adresy, takže v praxi máme alespoň 4MiB ochrany proti NULL deref.
Zarovnání 2M je dobré
Tím se umístí na začátek adresáře stránek o další úroveň výše tabulek stránek, což znamená, že stejný počet 4K záznamů tabulky stránek bude rozdělen do méně 2 milionů záznamů v adresáři stránek, což šetří paměť tabulky stránek jádra a pomáhá stránkám -lepší hardwarová mezipaměť. Pro velká statická pole je také dobré blízko začátku 1G podstromu o další úroveň výše.
IDK proč 4MiB místo 2MiB, nebo jaká byla úvaha vývojářů. 4MiB je 32bitová velikost velké stránky bez PAE (4bajtové PTE, takže 10 bitů na úroveň místo 9), ale CPU musí používat x86-64 tabulky stránek, aby bylo v 64bitovém režimu.
Nízká počáteční adresa umožňuje téměř 2 GiB statických polí
(Bez použití většího modelu kódu, kde alespoň velká pole musí být adresována způsoby, které jsou někdy méně efektivní. Podrobnosti o modelech kódu naleznete v části 3.5.1 Architektonická omezení v dokumentu x86-64 System V ABI.)
Výchozí kódový model pro spustitelné soubory bez PIE ("malé") umožňuje programům předpokládat, že jakákoli statická adresa je v nízkých 2GiB virtuálního adresového prostoru. Takže jakákoli absolutní adresa v .text
/.rodata
, .data
, .bss
lze použít jako 32bitové znaménko rozšířené bezprostředně ve strojovém kódu, kde je to efektivnější.
(Toto není případ PIE nebo sdílené knihovny:viz 32bitové absolutní adresy již nejsou povoleny v x86-64 Linuxu? pro věci, které vy / kompilátor v důsledku toho nemůže dělat v x86-64 asm, zejména addss xmm0, [foo + rdi*4]
místo toho vyžaduje LEA relativní k RIP k získání počáteční adresy pole do registru. Jediným režimem adresování relativním k RIPu x86-64 je [RIP+rel32], bez jakýchkoli obecných registrů.)
Spuštění sekcí/segmentů spustitelného souboru v dolní části virtuálního adresního prostoru ponechá téměř celé 2GiB k dispozici pro text+data+bss, aby bylo tak velké. (Mohlo by být možné mít vyšší výchozí hodnotu a mít velké spustitelné soubory, aby ld zvolil nižší adresu, aby vyhovovaly, ale to by byl složitější skript linkeru.)
To zahrnuje nulově inicializovaná pole v .bss, která netvoří spustitelný soubor obrovský, jen obraz procesu v paměti. V praxi mají programátoři Fortran tendenci narážet na toto více než na C a C++, protože zde jsou oblíbená statická pole. Například gfortran pro figuríny:Co přesně dělá mcmodel=medium? má dobré vysvětlení chyby sestavení s výchozí hodnotou small
model a výsledný rozdíl x86-64 asm pro medium
(kde se nepředpokládá, že objekty nad určitým prahem velikosti jsou v nízkých 2G nebo v rozmezí +-2G kódu. Ale kód a menší statická data stále platí, takže penalizace rychlosti je menší.)
Například static float arr[1UL<<28];
je pole 1 GiB. Pokud jste měli 3 z nich, nemohly všechny spustit uvnitř nízkých 2 GiB (což může být vše, co potřebujete pro ručně psaný asm), natož mít přístupný každý prvek.
gcc -fno-pie
očekává, že bude schopen zkompilovat float *p = &arr[size-1];
na mov $arr+1073741820, %edi
, 5bajtový mov $imm32
. RIP-relativní nebude fungovat ani v případě, že je cílová adresa vzdálena více než 2GiB od kódu generujícího adresu (nebo z něj načítá pomocí movss arr+1073741820(%rip), %xmm0
; RIP-relativní je normální způsob, jak načíst/uložit statická data i v non-PIE, když není k dispozici index proměnných za běhu.) Proto má model small-PIC také omezení velikosti 2GiB na text+data+bss (plus mezery mezi segmenty):všechna statická data a kód musí být v dosahu 2GiB od ostatních, kteří by je mohli chtít dosáhnout.
Pokud váš kód přistupuje pouze k vysokým prvkům nebo jejich adresám prostřednictvím indexů proměnných za běhu, stačí, aby začátek každého pole, samotný symbol, byl na nízkých 2 GiB. Zapomněl jsem, jestli linker vynutí mít konec bss v rámci nízkých 2GiB; může to být proto, že skript linkeru tam vloží symbol, na který může odkazovat nějaký spouštěcí kód CRT.
Poznámka 1 :Pro kódový model menší než 2GiB neexistují žádné užitečné menší velikosti. Strojový kód x86-64 používá 8 nebo 32bitový pro režim okamžitého a adresování. 8bitový (256 bajtů) je příliš malý na to, aby byl použitelný, a mnoho důležitých instrukcí, jako je call rel32
, mov r32, imm32
a [rip+rel32]
adresování, jsou dostupné pouze se 4bajtovými, nikoli 1bajtovými konstantami.
Omezení na nízké 2 GiB (místo 4) znamená, že adresy lze bezpečně nulově rozšířit jako u mov edi, OFFSET arr
, nebo znaménko rozšířené, jako u mov eax, [arr + rdi*4]
. Pamatujte, že adresy nejsou jediným případem použití [reg + disp32]
režimy adresování; [rbp - 256]
může často dávat smysl, takže je dobré, že x86-64 strojový kód znak-rozšíří disp8 a disp32 na 64bitové, nikoli zero-extends.
K implicitnímu nulovému rozšíření na 64bitový dochází při zápisu 32bitového registru, jako u mov
-immediate pro vložení adresy do registru, kde 32bitová velikost operandu je menší instrukce strojového kódu než 64bitová velikost operandu. Viz Jak načíst adresu funkce nebo štítku do registru (který také zahrnuje RIP-relativní LEA).
Související s 32bitovým systémem Windows
Raymond Chen napsal článek o tom, proč stejný 0x400000
základní adresa je výchozí pro 32bitové Windows .
Zmiňuje, že DLL se ve výchozím nastavení načítají na vysokých adresách a nízká adresa k tomu má daleko. Sdílené objekty x86-64 SysV lze načíst kdekoli, kde je dostatečně velká mezera v adresním prostoru, přičemž jádro se standardně nachází blízko vrcholu virtuálního adresního prostoru uživatelského prostoru, tj. vrcholu kanonického rozsahu. Ale sdílené objekty ELF musí být plně přemístitelné, takže by fungovaly dobře kdekoli.
Volba 4MiB pro 32bitová Windows byla také motivována tím, že se vyhnulo nízkému 64 kB (NULL deref) a výběrem začátku adresáře stránek pro starší 32bitové tabulky stránek. (Tam, kde je velikost "velké stránky" 4M, ne 2M pro x86-64 nebo PAE.) Se spoustou starších paměťových map Win95 a Win3.1 důvody, proč bylo částečně nutné alespoň 1MiB nebo 4MiB, a věci jako práce kolem CPU chyby.