Programujeme v jazyce Assembler v Linuxu: Úvod

1. 7. 2004
Doba čtení: 4 minuty

Sdílet

Můj článek se zabývá programováním v nízkoúrovňovém jazyce Assembler v Linuxu pro znalé tohoto jazyka. Pokusím se zde popsat možnosti, které nám Linux nabízí, a vlastnosti překladače NASM, volání systému a odlaďování aplikací, a také to, že Tux umí Assembler:).

Můj článek se zabývá programováním v nízkoúrovňovém jazyce Assembler v Linuxu pro znalé tohoto jazyka. Pokusím se zde popsat možnosti, které nám Linux nabízí, a vlastnosti překladače NASM, volání systému a odlaďování aplikací, a také to, že Tux umí Assembler:).

Předpokládejme tedy, že jste seznámeni s programováním v Assembleru nebo jste již pár programů vytvořili, zde jsou možnosti, jak v něm vytvořit efektivní programy pro Linux a využít jeho možnosti.

V prvním dílu tohoto seriálu se seznámíme s uspořádáním paměti procesu, předáváním argumentů příkazové řádky a voláním operačního systému.

Jak to tam uvnitř vypadá – uspořádání paměti procesoru

Linux jako víceprocesový (multitaskingový) operační systém, jehož jádro je z největší části napsáno v jazyce C, ale obsahuje i části psané v Assembleru, používá 32bitový systém adresování paměti. Díky této vlastnosti máme k dispozici 4 GB adresového prostoru pro každý proces v něm běžící. Tyto procesy „nevidí“ jádro, protože to je všem procesům skryto, a „nepoflakuje“ se v jejich paměti. To z důvodů bezpečnosti, jeden proces totiž neovlivňuje druhý. Tudíž havárie jednoho programu neshodí jiný běžící program nebo celý systém. Proces je zcela samostatný ve svém vlastním prostoru, který má k dispozici pouze pro sebe.

Tedy prostor pro náš program je veliký, je osamocen ve 4GB prostoru. Programy se skládají z několika segmentů (sekcí), Linux však podporuje mnoho formátů spustitelných souborů, my se zaměříme třeba na formát ELF – executable and linkable format. Pořadí načítání sekcí se u růzých spustitelných formátů může velmi lišit, některé formáty jsou si podobné, jiné jsou však zcela odlišné. Program bez dynamicky vázaných knihoven tedy bude spuštěn po svých částech v tomto pořadí.

Adresa 0×08048000

Tabulka č. 575
Spustitelný kód ;kód našeho programu, to co se má vykonat
Statická data ;data, která nebudou změněna
Dynamická (heap) data  
Volný prostor  
Zásobník ;zásobník dat, k jeho využití použijeme instrukci POP

Adresa 0×BFFFFFFF       hranice 4 GB

Program obvykle bývá nahrán do 128 MB adresového prostoru. Program se do paměti stránkuje (to znamená, že v paměti existuje právě jedna stránka programu). Další stránky jsou nahrány až po uvolnění té první, ve chvíli, kdy jsou zapotřebí. Původní stránka zcela z paměti mizí, části programu, které při jeho spuštění nebudou použity, se v paměti nikdy neobjeví. V programu samotném tyto sekce nejsou fyzicky odděleny, jsou rozdělovány až během zavádění programu do paměti. Na disku se jedná pouze o jeden kompletní a ucelený soubor.

Má-li program dynamicky zaváděné knihovny (linked libraries), budou jejich moduly nahrávány do stejného adresového prostoru, ale jejich adresy budou v pozici nad 1 GB a výše. V prostoru 3 GB, která nám zbývají do zásobníku, je volná pamět patřící procesu, ale ta není stránkovaná (nemá přiděleny žádné stránky). Když program zapíše na nealokované místo, bude proces ukončen (SIGKILL kernelu). Jedná se o velmi závažnou chybu, které bychom se jako programátoři měli vyvarovat. Program s touto chybou nikdy nebude proveden do svého konce, takže je zcela neužitečný, avšak důvodů k této chybě je více, podrobněji se budeme problematice chyb věnovat v nějakém z dalších dílů v sekci odlaďování programů. Pro přidělení dynamického stránkování pro data se musí jádro požádat, to ji pak přidělí, o tom si povíme později.

Kdepak to vše leží – předávání argumentů příkazove řádky

Proces je tedy naprosto sám ve své paměti, jak ale k němu dostat parametry a argumenty příkazové řádky nebo proměnné prostředí? Najdeme je na vrcholu zásobníku, který vypadá takto:

Tabulka č. 576
argc počet parametrů (dword)
argv[0] pointer na název programu
argv[1] … argv[argc-1] pointery na parametry programu
NULL konec pointerů parametrů
env[x] x=(0;n)
NULL konec pointerů na prostředí

Použití těchto parametrů se provádí přes instrukci POP pro výběr dat ze zásobníku. Nezapomeňme však, že zásobník používá metodu LIFO (last in first out), to znamená, že to, co do něj přijde jako poslední, jde ven jako první. Doporučuji jako první ze zásobníku přečíst počet parametrů (argc).

První pointer bude obsahovat jméno našeho programu, ostatní pointery, s hodnotou argc větší než 1, budou obsahovat další pointery uložené v zásobníku.

Haló, vy tam – volání operačního systému

Pro systémová volání se v Linuxu používá přerušení int 0×80. Řekněme si jak se dovolat jádra z našeho programu. Při provedení instrukce int 0×80 se změní hodnota segmentového registru a začne běžet jaderný kód. Po jeho skončení se normálně pokračuje ve vykonávání našeho programu, jakoby se nechumelilo :).

Parametry jednotlivých systémových volání (syscalls) se předávají v registrech. Každá služba má své číslo, toto číslo se předává v registru EAX. Parametr této služby, pokud nějaký má, se předává v registru EBX, další parametr je předán pomocí registru ECX a další pomocí, můžete hádat, pokud jste hádali EDX, tak jste hádali správně:). Parametry služby se tedy předávají v pevně stanovených registrech. Od jádra 2.4.0 se poslední parametr předává v registru EBP.

bitcoin školení listopad 24

Závěrem

Dnes jsme si ukázali pár základních věcí o vlastnostech a chování aplikace ve formátu spustitelného souboru ELF v Linuxu a o jeho paměti a registru, ze kterého umíme vybrat důležité informace. Příště budeme pokračovat o něco dále a povíme si více o chybových kódech a o tom, kde najít více informací o nich, napíšeme si jednoduchý program a povíme si, co jsou to ASMUTILS.

Doufám, že se vám tento článek líbil a přečtete si i jeho pokračování, které nebude již tolik teoretické, vneseme do něj více praxe a ukázek:).