Programi brez špeha - 2. del
Prejšnji mesec smo izvedljivo datoteko s prvotnih 11480 bajtov ob neokrnjeni funkcionalnosti skrčili na vsega 352 bajtov. Tokrat bomo zadevo prignali do skrajnih meja in napisali nič manj kot najkrajši izvedljiv program, kar se jih sploh da napisati za sistem GNU/Linux z zapisom ELF.
Skrajne meje pa imajo svojo ceno. Če smo v prvem koraku krčenje lahko opravili z bolj ali manj legalnimi sredstvi, kot je uporaba zbirnika in ogibanje odvečnih klicev sistemskih knjižnic, bomo zdaj počeli nekaj umazanih stvari z zapisom ELF. Metodo opisuje Brian Raiter v svojem spisu "A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux" in kopici drobcenih programčkov, dosegljivi na strani http://www.muppetlabs.com/~breadbox/software/tiny/.
Zapis ELF
Preden se zares lotimo dela, pa se moramo podučiti o strukturi izvedljivih datotek. Zapis ELF (Executable and Linking Format) so razvili konec osemdesetih let prejšnjega stoletja pri Unix System Labs (USL) z namenom, da bi ponudili enoten binarni vmesnik za različna strojna okolja in različne operacijske sisteme. Le uporabniki GNU/Linuxa z najdaljšim stažem se morda spomnijo, da GNU/Linux ni od nekdaj uporabljal zapisa ELF; nekako od leta 1995 naprej je namreč težko najti distribucijo Linuxa za Intelovo strojno okolje, ki ne bi uporabljala zapisa ELF za predmetne in prevedene datoteke.
Kanonična dokumentacija za zapis ELF - njegova specifikacija - je na voljo na naslovu ftp://tsx.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz. Ker gre dvomiti, da bo veliko takih, ki jo bodo pazljivo prebrali od prve do zadnje strani, bomo tu ponovili najnujnejše.
Vsaka datoteka, ki uporablja obliko zapisa ELF, se začne z glavo ELF. To je 52 bajtov dolga podatkovna struktura, ki vsebuje več podatkov o vsebini datoteke. Prvih šestnajst bajtov, denimo, vsebuje "identifikator", kar vključuje "magično" signaturo (7F 45 4C 46) in nekaj enobajtnih zastavic, ki določajo, ali je datoteka prevedena za 32- ali 64-bitno arhitekturo s padajočim ali naraščajočim vrstnim redom bajtov v besedi. Drugi podatki v glavi ELF določajo še ciljno strojno arhitekturo, podatek o tem, ali gre za izvedljiv program, predmetno datoteko ali deljeno knjižnico, naslov začetka programa ter naslova dveh tabel, programske tabele (program header table) in tabele delov (section header table).
Prva in druga tabela sta načeloma lahko kjerkoli v datoteki, v praksi pa je programska tabela navadno takoj za glavo ELF, tabela delov pa na koncu (ali pa vsaj precej pri koncu) datoteke. Namen obeh tabel je podoben - evidenca komponent datoteke - ne pa povsem enak. Tabela delov vodi evidenco predvsem o tem, kje v datoteki najdemo različne dele programa, programska tabela pa o tem, kje in kako naj se ti deli naložijo v pomnilnik. Po domače, tabela delov je namenjena prevajalniku in povezovalniku, programska tabela pa nalagalniku. Programska tabela zato ni obvezen del predmetnih datotek in v praksi je navadno v teh datotekah tudi zares ne najdemo. Tabela delov po drugi strani ni obvezen del izvedljivih programov, a je v resnici skoraj vedno navzoč!
Imamo torej odgovor na vprašanje, s katerim smo končali prejšnji del - program je lahko krajši od 352 bajtov.
Smo pa zdaj res povsem na svojem. Nobenega standardnega orodja nimamo na voljo, ki bi nam v izvedljivi datoteki osmukalo to ali ono tabelo (resnici na ljubo je na voljo vsaj eno nestandardno orodje, program sstrip že omenjenega Briana Raiterja, http://www.muppetlabs.com/~breadbox/software/elfkickers.html). Če res hočemo program brez tabele delov, ga moramo torej ustvariti sami.
Naj potolažimo tiste, ki jih skrbi, da se bomo zdaj zadeve lotili z binarnim urejevalnikom - ni še povsem tako hudo. Zbirnik NASM, s katerim smo se seznanili v prejšnjem nadaljevanju, lahko namesto predmetne datoteke izdela navaden binarni zapis, kar bo ravno primerno za naše potrebe. Vse, kar potrebujemo, je slika prazne izvedljive datoteke v zapisu ELF, ki nam bo rabila kot lupina. Dodali bomo naš program - le naš program in čisto nič drugega.
Do izvedljive datoteke brez povezovanja
Kako do lupine izvedljive datoteke ELF? No, omenili smo že specifikacijo zapisa ELF. Pomagamo si lahko tudi z glavo /usr/include/linux/elf.h in šestnajstiškimi izpisi datotek, ustvarjenimi s standardnimi orodji. Če pa ste nestrpni, lahko porabite to, ki jo imamo tu:
BITS 32
org 0x08048000
ehdr: ; Elf32_Ehdr
db 0x7F, "ELF", 1, 1, 1 ; e_ident
times 9 db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
ehdrsize equ $ - ehdr
phdr: ; Elf32_Phdr
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align
phdrsize equ $ - phdr
_start:
; naš program se začne tu
filesize equ $ - $$
Čas je, da malo pokomentiramo, kar imamo. Glava ELF identificira datoteko kot izvedljivo datoteko za okolje Intel x86, brez tabele delov in s programsko tabelo, vsebujočo en vnos. Ta vnos je ukaz programskemu nalagalniku, naj naloži celoten program v pomnilnik (navadno programi vključijo glavo ELF in programsko tabelo v svojo pomnilniško sliko), začenši s pomnilniškim naslovom 0x08048000 (kar je privzeti naslov, kamor se naložijo izvedljivi programi), in začne izvajati program na naslovu _start, ki se začenja takoj za programsko tabelo. Datoteka ne vsebuje nobenega segmenta .data, nobenega segmenta .bss, nobenega komentarja - vsebuje torej le najnujnejše.
Prilepimo le še naš program in imamo:
_start:
; naš program se začne tu
mov bl, 42
xor eax, eax
inc eax
int 0x80
filesize equ $ - $$
Poskusimo ga prevesti in pognati:
$ nasm -f bin -o test7 test7.s
$ chmod +x test7
$ ./test7 ; echo $?
42
Izvedljiv program smo izdelali v enem samem koraku in brez povezovalnika. Priznajte, da tega ne vidite pogosto. Kaj pa velikost?
$ wc -c test7
91 test7
Enaindevetdeset bajtov. To je manj kot četrtina tistega, s čimer smo tokrat začeli, in manj kot en odstotek običajne velikosti datoteke.
Še več, opravičimo lahko čisto vsak bajt v datoteki. Za vsakega natančno vemo, kje v izvedljivi datoteki je, in zakaj mora biti tam. To je torej končna meja velikosti datoteke, bolj se je ne da oskubiti. Ali pač?
Podle zvijače
Vsi, ki so se pomudili z branjem specifikacije zapisa ELF, so morda opazili nekaj zanimivih stvari. Prvič: razen glave ELF, ki mora biti na začetku datoteke, so druge komponente lahko kjerkoli v datoteki. In drugič: nekatera polja v glavi ELF so neizrabljena in čakajo na morebitne prihodnje razširitve zapisa.
Še posebej lakomno se oziramo po devetih zaporednih ničlah na koncu 16-bajtnega identifikacijskega polja. To je zgolj mašilo, ki čaka, da bo morda prišlo prav v kateri od naslednjih izdaj specifikacije zapisa. Operacijskemu sistemu je vseeno, ali tam stojijo ničle ali kaj drugega. In tako ali tako smo se že odločili, da bomo naložili vse skupaj v pomnilnik, in naš program je dolg vsega sedem bajtov...
Morda lahko program vtaknemo kar v glavo ELF?
Pa dajmo. Navsezadnje, bajt je le bajt.
; test8.s
BITS 32
org 0x08048000
ehdr: ; Elf32_Ehdr
db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
_start: mov bl, 42
xor eax, eax
inc eax
int 0x80
db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
ehdrsize equ $ - ehdr
phdr: ; Elf32_Phdr
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align
phdrsize equ $ - phdr
filesize equ $ - $$
Pričakujemo lahko, da bo datoteka z izvedljivim programom natanko sedem bajtov krajša. Res:
$ nasm -f bin -o test8 test8.s
$ chmod +x test8
$ ./test8 ; echo $?
42
$ wc -c test8
84 test8
To je pa verjetno res konec. Datoteka z izvedljivim programom je zdaj dolga natanko toliko kakor glava ELF in programska tabela in obe sta nujno potrebni, da datoteka ustreza najnujnejšim kriterijem za izvedljiv program. Skrčiti ne moremo ničesar več. Razen...
No, seveda, tudi s programsko tabelo lahko poskusimo napraviti isto kot s programom samim, torej jo prekriti z glavo ELF. Bo to šlo?
Bo. Še enkrat si oglejmo prejšnji programski izpis. Zadnjih osem bajtov v glavi ELF je nekam podobnih prvim osmim bajtom v programski tabeli. Za te podobnosti je v rabi tudi izraz identiteta...
Pa poskusimo delno prekriti glavo ELF s programsko tabelo:
; test9.s
BITS 32
org 0x08048000
ehdr:
db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
_start: mov bl, 42
xor eax, eax
inc eax
int 0x80
db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
dd 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
phdr: dd 1 ; e_phnum ; p_type
; e_shentsize
dd 0 ; e_shnum ; p_offset
; e_shstrndx
ehdrsize equ $ - ehdr
dd $$ ; p_vaddr
dd $$ ; p_paddr
dd filesize ; p_filesz
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align
phdrsize equ $ - phdr
filesize equ $ - $$
Linuxa, kot kaže, naša skopost ne moti preveč:
$ nasm -f bin -o test9 test9.s
$ chmod +x test9
$ ./test9 ; echo $?
42
$ wc -c test9
76 test9
To pa mora biti zdaj res meja. Obeh podatkovnih struktur ne moremo prekriti bolj, kot smo jih, saj se vsebini ne ujemata. To je torej to.
No, ja. Morda pa lahko kaj pogoljufamo. Morda GNU/Linux niti ne bere vsebine obeh podatkovnih struktur preveč natančno in lahko kaj zamenjamo v njiju, da bi se bolje ujemali? Katera polja so zares pomembna? Ali GNU/Linux zares preverja, ali je v polju e_machine vrednost 3 (ta vrednost označuje okolje Intel 386), ali kar predpostavlja, da je?
Izkaže se, da v tem konkretnem primeru zares preverja vrednost vsebine polja. Vseeno pa je presenetljivo veliko število polj potihem lepo prezrto.
Inventura glave ELF
Napravimo torej kratko inventuro, katera polja v glavi ELF so zares pomembna, katera pa nekaj manj. Prvi štirje bajti so magično število: če do zadnjega bita ne ustrezajo predpisani vrednosti, se nalagalnik datoteke ne bo niti pritaknil. Polje e_ident pa se ne preverja, kar pomeni, da imamo zaporedje celih dvanajstih bajtov, katerih vrednost lahko nastavimo na karkoli. Vrednost polja e_type mora biti 2, kar označuje izvedljiv program, vrednost polja e_machine pa 3, kot smo malo prej omenili. Vrednost e_version se ne preverja, enako kot se ni preverjala oznaka različice v polju e_ident. Oboje je po svoje razumljivo, ker imamo za zdaj opravka z eno samo izdajo standarda ELF. Polje e_entry mora seveda biti pravi naslov začetka programa. Enako mora tudi e_phoff vsebovati pravilni odmik programske tabele od začetka datoteke, e_phnum pa pravo število vnosov v omenjeni tabeli. Po drugi strani pa polje e_flags za zdaj nima pomena v strojnem okolju Intel in ga lahko recikliramo. Polje e_ehsize naj bi rabilo preverjanju dolžine glave ELF, a tega GNU/Linux ne počenja. Podobno velja tudi za polje e_phentsize, ki je namenjeno preverjanju velikosti programske tabele. To polje preverjajo le jedra Linuxa iz veje 2.2, novejša od 2.2.17. Starejša jedra ga niso, prav tako tega polja ne preverjajo jedra iz veje 2.4 (preverjeno velja za jedra do vključno 2.4.20). Preostanek glave ELF je namenjen tabeli delov, ki za izvedljive programe, kot smo že povedali, ni obvezna.
Kaj pa programska tabela? No, polje p_type mora vsebovati vrednost 1, ki označuje segment, ki ga je moč naložiti. Polje p_offset mora vsebovati pravi odmik od začetka datoteke, če naj se program pravilno naloži v pomnilnik. Podobno mora tudi polje p_vaddr vsebovati pravilni naslov za nalaganje. Opozorimo pa naj, da ni nujno, da se program naloži na naslov 0x08048000. Skoraj katerikoli naslov je enako dober, da je le večji od 0x00000000, manjši od 0x80000000 in poravnan s pomnilniško stranjo. Za polje p_paddr specifikacija priznava, da ni v rabi, kar pomeni, da je prosto. Polje p_filesz navaja, koliko bajtov moramo iz datoteke naložiti v pomnilnik, p_memsz pa določa, kolikšen je pomnilniški segment, zato morata ti polji vsebovati razmeroma razumne vrednosti. Polje p_flags določa dovoljenja za delo s pomnilniškim segmentom. V njem mora biti postavljen bit 4 (dovoljenje za branje) - brez tega pomnilniški segment sploh ni uporaben - ob njem pa mora biti postavljen še bit 1 (dovoljenje za izvajanje), sicer programa ne moremo izvajati. Preostali biti so verjetno lahko tudi brez škode postavljeni, omenjena dva pa morata biti. In končno, polje p_align navaja zahteve za poravnavanje pomnilniškega segmenta v pomnilniku. To polje je predvsem uporabno za deljene knjižnice, kjer je treba premikati segmente, ki vsebujejo od položaja neodvisno kodo. Pri izvedljivih programih lahko v to polje vpišemo karkoli - GNU/Linux bo vsebino brez pardona prezrl.
Kaže torej, da je med polji kar nekaj zraka. Posebej za glavo ELF se zdi, da je večina pomembnih polj v njeni prvi polovici, medtem ko lahko drugo polovico skoraj poljubno mrcvarimo. Poskusimo torej z nekaj agresivnejšim prekrivanjem obeh podatkovnih struktur.
; test10.s
BITS 32
org 0x00200000
db 0x7F, "ELF" ; e_ident
db 1, 1, 1, 0
_start:
mov bl, 42
xor eax, eax
inc eax
int 0x80
db 0
dw 2 ; e_type
dw 3 ; e_machine
dd 1 ; e_version
dd _start ; e_entry
dd phdr - $$ ; e_phoff
phdr: dd 1 ; e_shoff ; p_type
dd 0 ; e_flags ; p_offset
dd $$ ; e_ehsize ; p_vaddr
; e_phentsize
dw 1 ; e_phnum ; p_paddr
dw 0 ; e_shentsize
dd filesize ; e_shnum ; p_filesz
; e_shstrndx
dd filesize ; p_memsz
dd 5 ; p_flags
dd 0x1000 ; p_align
filesize equ $ - $$
Kot vidimo, se zdaj prvih dvajset bajtov programske tabele prekriva z zadnjimi dvajsetimi bajti glave ELF. Oba dela sta tako rekoč kot ulita drug za drugega. V glavi ELF sta pravzaprav le dve polji, ki sta zares pomembni. Prvo je polje e_phnum, ki smo ga prekrili s poljem p_paddr, enim tistih polj v programski tabeli, ki jih lahko brez skrbi prezremo. Drugo je polje e_phentsize, ki se prekriva z zgornjo polovico polja p_vaddr. Slednje polje lahko popravimo tako, da se bo ujemalo, če izberemo drugačen (a še vedno legalen) naslov, kamor naj se naloži program, torej tak, katerega vrhnja dva bajta bosta enaka 0x0020. Pretenzije o prenosljivosti so zdaj dokončno za nami...
$ nasm -f bin -o test10 test10.s
$ chmod +x test10
$ ./test10 ; echo $?
42
$ wc -c test10
64 test10
Zadeva deluje! Program pa je dvanajst bajtov krajši, kot smo predvidevali.
Na tej točki navadno sklenemo, da smo zdaj res dosegli skrajne meje, nato pa presenečeno ugotovimo, da jih nismo. Enkrat ali dvakrat je morda presenečenje delovalo, zdaj pa si verjetno že mislite, potem pa se je stvar začela ponavljati. Naš cilj je naslednji: ali lahko programsko tabelo v celoti pospravimo v glavo ELF? Je to iskanje svetega grala?
Tega seveda ne moremo napraviti preprosto tako, da jo premaknemo še za dodatnih dvanajst bajtov proti začetku datoteke, ne da bi naleteli na nepremostljive ovire v poljih ene ali druge podatkovne strukture. Druga možnost je, da jo začnemo takoj za prvimi štirimi bajti glave. S tem pospravimo prvi del programske tabele lepo pod steho znotraj polja e_ident, težave pa imamo z ostankom. Nekaj eksperimentiranja pokaže, da to morda vendarle ni mogoče.
Ostalo pa je še nekaj polj v programski tabeli, ki jih morda lahko zlorabimo.
Omenili smo, da polje p_memsz določa, koliko pomnilnika je treba rezervirati za pomnilniški segment. Očitno je, da mora biti vrednost vsaj tako velika kot p_filesz, načeloma pa ne sme škoditi niti, če je kaj večja...
Druga, na videz presenetljiva možnost pa je, da lahko izpustimo bit, ki določa izvedljivost v polju p_flags, pa bo program vseeno deloval. Razlog je, da Linux ne ustvarja selektorjev dinamično za vsak nov proces, temveč ob zagonu ustvari globalno tabelo deskriptorjev in pozneje dodeljuje selektorje iz te tabele. Vnosi v njej so označeni bodisi z dovoljenji za branje in izvajanje, bodisi za branje in pisanje. Ti deskriptorji se preberejo v registre CS in DS/ES/SS vsakega procesa. Vzrok za na videz nelogično ravnanje, zakaj Linux ustvari izvedljiv selektor, čeprav noben od segmentov datoteke ELF ni označen kot izvedljiv, je torej v tem, da ga ni ustvaril ob nalaganju programa, temveč že davno, preden smo program sploh prevedli...
Poskusimo zdaj vse, kar smo se naučili, uporabiti v našem zdaj že res precej grdo poonegavljenem programčku:
; test11.s
BITS 32
org 0x00001000
db 0x7F, "ELF" ; e_ident
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dw 2 ; e_type ; p_paddr
dw 3 ; e_machine
dd filesize ; e_version ; p_filesz
dd _start ; e_entry ; p_memsz
dd 4 ; e_phoff ; p_flags
_start:
mov bl, 42 ; e_shoff ; p_align
xor eax, eax
inc eax ; e_flags
int 0x80
db 0
dw 0x34 ; e_ehsize
dw 0x20 ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
filesize equ $ - $$
Vrednost polja p_flags smo spremenili s 5 na 4, kot smo se naučili, da lahko napravimo brez resnejših posledic. Zdaj se bo pokazalo, zakaj nam je bilo toliko do te štirice: štiri je obenem tudi vrednost polja e_phoff, ki navaja odmik programske tabele od začetka datoteke - in natančno tja smo tabelo tudi postavili. Program sam smo premaknili navzdol; zdaj se začenja s poljem e_shoff in končuje sredi polja e_flags.
Opazimo lahko tudi, da smo naslov, na katerega naj se naloži program, spremenili na precej nižjo vrednost - pravzaprav najnižjo, kar se da. S tem smo vrednost polja e_entry ohranili na razmeroma nizki vrednosti, a to ni napak, ker je to obenem tudi polje p_memsz (v resnici to pri sistemih, ki podpirajo virtualni pomnilnik, niti ni tako nujno - mirno bi lahko pustili vrednost polja na izvirni vrednosti, pa bi vseeno delovalo. A malo vljudnosti nič ne škodi.)
Bo to še vedno delovalo?
$ nasm -f bin -o test11 test11.s
$ chmod +x test11
$ ./test11 ; echo $?
42
$ wc -c test11
52 test11
Zdaj imamo programsko tabelo in program sam v celoti vdelan v glavo ELF in celotna izvedljiva datoteka je natančno tako dolga kakor glava ELF. In Linux jo še vedno izvede brez pripomb!
To mora biti zdaj konec, kajne? Navsezadnje mora imeti datoteka ELF vsaj celo glavo ELF (čeravno grdo zmrcvarjeno, kot je naša), da se hoče nalagalnik ukvarjati z njo.
Hja, še eno zvijačo imamo na zalogi. Če je datoteka krajša od dolžine glave ELF, jo bo Linux vseeno naložil in se ukvarjal z njo, manjkajoča polja pa bo sam nadomestil z ničlami. In v naši datoteki imamo kar nekaj ničel na koncu datoteke, ki jih mirno lahko prepustimo v dopolnitev jedru...
; test12.s
BITS 32
org 0x00001000
db 0x7F, "ELF" ; e_ident
dd 1 ; p_type
dd 0 ; p_offset
dd $$ ; p_vaddr
dw 2 ; e_type ; p_paddr
dw 3 ; e_machine
dd filesize ; e_version ; p_filesz
dd _start ; e_entry ; p_memsz
dd 4 ; e_phoff ; p_flags
_start:
mov bl, 42 ; e_shoff ; p_align
xor eax, eax
inc eax ; e_flags
int 0x80
db 0
dw 0x34 ; e_ehsize
dw 0x20 ; e_phentsize
db 1 ; e_phnum
; e_shentsize
; e_shnum
; e_shstrndx
filesize equ $ - $$
Presenetljivo se program prevede in izvede brez napak:
$ nasm -f bin -o test12 test12.s
$ chmod +x test12
$ ./test12 ; echo $?
42
$ wc -c test12
45 test12
To je pa zdaj res dokončno konec. Nobenega obvoza ni več okrog dejstva, da mora biti 45. bajt v datoteki, ki določa število vnosov v programski tabeli, navzoč v datoteki, neničeln in postavljen natanko 45 bajtov od začetka datoteke. Manjše izvedljive datoteke ELF niso mogoče. Petinštirideset bajtov je manj kot ena osmina najmanjšega izvedljivega programa ELF, ustvarjenega s standardnimi orodji, in manj kot dva odstotka najmanjše izvedljive datoteke, ki jo lahko napišemo v jeziku C. Datoteko smo oskubili, kolikor smo le mogli; tisto, kar je ostalo, pa večinoma služi dvojnemu namenu.
Za konec
Ogledali smo si torej, kako je videti najmanjša izvedljiva datoteka ELF na svetu. Resnici na ljubo je treba priznati, da kakšna polovica vrednosti polj tako ali drugače krši standard ELF in čudež je, da jo Linux sploh povoha, kaj šele, da ji dodeli procesno številko... V resnici ta program res ni tiste sorte, s katerim bi se običajno hvalili okrog.
Pisanje programskih miniatur je po svoje podobno sestavljanju modelčkov starih jadrnic v steklenici - človek je lahko očaran nad končnim izdelkom, po drugi strani pa se mora vprašati tudi o koristnosti tega početja. Slednje je navsezadnje tudi razlog, da nobena od omenjenih ni posebej množičen šport. Za tiste, ki jih vseeno srbijo prsti, da bi se sami poskusili, pa bo ob že v uvodu omenjeni strani Briana Raiterja zanimiva še stran projekta Asmutils, http://linuxassembly.org/asmutils.html, na kateri je na voljo nekaj manj trivialnih zgledov programiranja v zbirniku.
Po drugi strani pa smo, upam, pokazali, da programiranje v zbirniku v sistemu GNU/Linux ne le, da ni tako strah zbujajoče, kot se morda kaže, temveč je v resnici uporabno, kadar hočemo imeti res natančen nadzor nad obliko prevedenega programa. Če nam je ob tem uspelo še nekoliko demistificirati zapis ELF, pa sploh toliko bolje.
Opravičilo: Po krivdi prevajalca je v prvem delu članka, objavljenem v januarski številki, izpadlo ime avtorja, Briana Raiterja.
Dodatni viri
Podobno temo kakor Brian Raiter obdeluje tudi Wataru Nishida v spisu "How to communicate with Linux kernel", http://www.skyfree.org/linux/assembly/section1.html. "Ročno" izdelano glavo ELF demonstrira tudi program Basila Starynkevitcha, http://perso.wanadoo.fr/starynkevitch/basile/tinygasp.tar.gz.
Verjetno najobsežnejše dveri za programiranje v zbirniku v Linuxu je http://www.linuxassembly.org/. Na njem med drugim najdemo tudi povezave na različne učbenike in priročnike, od krajših uvodnih, kot sta "Introduction to UNIX assembly programming" Konstantina Boldysheva, http://www.linuxassembly.org/intro/Assembly-Intro.html, in "Linux Assembly Howto" Konstantina Boldysheva in Françoisa-Renéja Rideauja, http://www.linuxassembly.org/howto/Assembly-HOWTO.html, pa do daljših, kot sta "The Art of Assembly Language Programming" Randalla Hyda, http://webster.cs.ucr.edu/Page_asm/ArtOfAsm.html in "Programming from the Ground Up" Jonathana Bartletta, http://savannah.nongnu.org/projects/pgubook/, če omenimo le nekatere.