GNU/Linux >> Znalost Linux >  >> Linux

Snažíme se porozumět složitému zarovnání zásobníku gcc v horní části main, které kopíruje návratovou adresu

Zkoušel jsem to:

;# As you have already noticed, the compiler wants to align the stack
;# pointer on a 16 byte boundary before it pushes anything. That's
;# because certain instructions' memory access needs to be aligned
;# that way.
;# So in order to first save the original offset of esp (+4), it
;# executes the first instruction:
lea    ecx,[esp+0x4]

;# Now alignment can happen. Without the previous insn the next one
;# would have made the original esp unrecoverable:
and    esp,0xfffffff0

;# Next it pushes the return addresss and creates a stack frame. I
;# assume it now wants to make the stack look like a normal
;# subroutine call:
push   DWORD PTR [ecx-0x4]
push   ebp
mov    ebp,esp

;# Remember that ecx is still the only value that can restore the
;# original esp. Since ecx may be garbled by any subroutine calls,
;# it has to save it somewhere:
push   ecx

To se provádí, aby byl zásobník zarovnán na hranici 16 bajtů. Některé instrukce vyžadují, aby určité datové typy byly zarovnány až na hranici 16 bajtů. Aby GCC splnil tento požadavek, zajišťuje, aby byl zásobník zpočátku zarovnán na 16 bajtů, a přiděluje prostor zásobníku v násobcích 16 bajtů. To lze ovládat pomocí volby -mpreferred-stack-boundary=num . Pokud použijete -mpreferred-stack-boundary=2 (pro 2=4bajtové zarovnání), tento kód zarovnání se nevygeneruje, protože zásobník je vždy zarovnán alespoň na 4bajty. Pokud však váš program používá jakékoli datové typy, které vyžadují silnější zarovnání, můžete mít potíže.

Podle příručky gcc:

Na systémech Pentium a PentiumPro by hodnoty double a long double měly být zarovnány s 8bajtovou hranicí (viz -malign-double), jinak by měly být výrazně sníženy výkony za běhu. Na Pentiu III nemusí datový typ Streaming SIMD Extension (SSE) __m128 fungovat správně, pokud není zarovnán na 16 bajtů.

Aby bylo zajištěno správné zarovnání těchto hodnot na zásobníku, musí být hranice zásobníku zarovnány tak, jak to vyžaduje jakákoli hodnota uložená v zásobníku. Dále musí být každá funkce generována tak, aby udržovala zásobník zarovnaný. Volání funkce zkompilované s vyšší preferovanou hranicí zásobníku z funkce zkompilované s nižší preferovanou hranicí zásobníku tedy s největší pravděpodobností nevyrovná zásobník. Je doporučeno, aby knihovny, které používají zpětná volání, vždy používaly výchozí nastavení.

Toto dodatečné zarovnání spotřebovává další místo v zásobníku a obecně zvyšuje velikost kódu. Kód, který je citlivý na využití prostoru zásobníku, jako jsou vestavěné systémy a jádra operačních systémů, může chtít snížit preferované zarovnání na -mpreferred-stack-boundary=2.

lea načte původní ukazatel zásobníku (z doby před voláním main ) do ecx , protože ukazatel zásobníku se brzy změní. Používá se ke dvěma účelům:

  1. pro přístup k argumentům main funkce, protože jsou relativní k původnímu ukazateli zásobníku
  2. pro obnovení ukazatele zásobníku na původní hodnotu při návratu z main

lea    ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of     the main...why ?
and    esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ???
push   DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ?
push   ebp                
mov    ebp,esp
push   ecx  ;why is ecx pushed too ??

I kdyby každá instrukce fungovala perfektně bez omezení rychlosti navzdory libovolně zarovnaným operandům, zarovnání by stále zvýšilo výkon. Představte si smyčku odkazující na 16bajtové množství, které právě překrývá dva řádky mezipaměti. Nyní, aby bylo možné načíst tento malý wchar do mezipaměti, musí být vymazány dvě celé řádky mezipaměti a co když je potřebujete ve stejné smyčce? Mezipaměť je tak ohromně rychlejší než RAM, že výkon mezipaměti je vždy kritický.

Obvykle také dochází k penalizaci rychlosti za posunutí nesprávně zarovnaných operandů do registrů. Vzhledem k tomu, že se zásobník přerovnává, musíme přirozeně staré zarovnání uložit, abychom mohli procházet snímky zásobníku pro parametry a návrat.

ecx je dočasný registr, takže musí být uložen. V závislosti na úrovni optimalizace mohou být některé operace propojení rámců, které se nezdají být nezbytně nutné ke spuštění programu, také důležité pro nastavení řetězce rámců připravených ke sledování.


Linux
  1. Linuxové jádro:5 nejlepších inovací

  2. Vrátit pouze odpovídající řetězec v Sed?

  3. Chápete význam `$_`?

  1. Jak zobrazit IP adresu vašeho PC na horním panelu v Ubuntu

  2. Najděte geolokaci IP adresy z příkazového řádku

  3. 20 nejlepších průvodců a výukových programů pro správce systému

  1. Najít počítač v síti LAN?

  2. Jak zjistím maximální velikost zásobníku?

  3. Jaký je uživatel debian-+?