(lib)ELF for dummies?

Fórumok

Sziasztok!

Egy/tobb ISA szimulatorhoz keresek egy egyszeru megkozelitest amivel *.elf fileokkal is tudnam etetni a rendszert. Neztem a `libelf-dev` csomagot, de ott az RTFM finoman szolva is foghijjas (lasd: `man elf_begin`). Talaltam meg ezt a leirast, most epp ezt nezem, hatha. Ami kell nekem az kb a kovetkezo: az objcopy -j ... -j ... -O binary xyz.elf xyz.bin-nek megfelelo funkcionalitas, illetve az `nm`-nek megfelelo symbol lookup. Persze ilyesmik is jol jonnek hogy az adott szegmens (.text, .rodata) mettol meddig tart es mettol meddig van benne valami. A legjobb lenne egy sima C library, mint a libelf, felteve hogy ez... erre valo. Amiben csak amiatt ketelkedek mert az `ldd` szerint ezek (objcopy, nm) nem linkelik magukhoz a libelf.so-t. 

Barkinek barmi otlet, ilyen getting started jelleggel? A fenti leirast elkezdem nezni, de ott nekem elsore aranytalanul sok a "creating" meg a "modifying" resz, ami itt biztos nem kell, kifejezett dump-olast meg vagy nem lattam ebben vagy vak vagyok. 

thx, A.

Hozzászólások

Szerkesztve: 2025. 08. 02., szo – 16:03

Minek neked a libelf? Miért nem írod meg magad? Kilistázni egy ELF tartalmát pofonegyszerű, csak pár struct lista, tényleg csak pár sor. A structokat megtalálod az /usr/include/elf.h fájlban.

Pár példa:
- POSIX-UEFI, ebben még a struct defek is megtalálhatók
- bootboot, ez meg a szekciókon meg a szimbólumokon iterál végig
- easyboot végignyálazza az ELF szegmenseket
- easyboot plgld, ez a legteljesebb, szegmensek, szekciók, szimbólumok, de még relokációs listára is van benne példa

Tényleg nem nagy kunszt. Ha csak annyi kell, hogy kidumpold, mint ahogy az nm teszi, az 10-20 sor, nem több (ha értelmezni meg ellenőrizni is akarod, na az már más kérdés).

    ehdr = (Elf64_Ehdr *)data;                    // ELF header
    shdr = (Elf64_Shdr *)(data + ehdr->e_shoff);  // section header
    // megkeressük a szimbólum táblát meg a string táblát
    for(i = 0; i < ehdr->e_shnum; i++) {
        s = ((Elf64_Shdr *)((uint8_t *)shdr + i * ehdr->e_shentsize));  // köv. section
        if(s->sh_type == SHT_STRTAB) { if(!strs) strs = s; } else
        if(s->sh_type == SHT_SYMTAB) { if(!syms) syms = s; }
    }
    // kilistázzuk a szimbólumokat
    for(i = 0; i < syms->sh_size; i += syms->sh_entsize) {
        sym = (Elf64_Sym *)(data + syms->sh_offset + i);
        printf("%08x %s\n", sym->st_value, (char*)data + strs->sh_offset + sym->st_name);
    }

Ennyi. Amire figyelni kell, hogy ne léptesd a struct pointert, hanem mindig a fejlécben szereplő "entsize" mezőket használd, hogy jövőbiztos meg multiplatform legyen.

Koszi! Ha csak ennyi az boven jo lehet ;)

Igy kiprobaltam gyorsan, annyi (talan nem is annyira apro?) kulonbseggel hogy 32 bites ELF-eken kell most dolgozzak (erosen embedded cuccok, 32 bit boven eleg). Szoval a tipusokat atirtam Elf32_*-ra, igy is hiba nelkul lefordul, es tenyleg ad egy nm-szeru kimenetet. Vagyis, vannak jo entry-k is, csak azt sejtem igy elsore hogy az a 

(char*)data + strs->sh_offset + sym->st_name

resz az nem feltetlen egy null-terminalt sztring ebben az esetben es/vagy nem jo helyre mutat. Ha ezt kiszedem ugy kb jot ad vissza, de a stringekre meg ki kell talalni valamit. Lehet hogy 32 bites esetben mashogy kell? Ez a fenti pelda teljesen logikusnak tunik, szoval fura lenne hogy 32 bites esetben mashogy kene csinalni mint 64-nel. 

Szerk: a nyers *.elf-ben valoban null-terminalt sztringek vannak. Ugyhogy itt inkabb az lehet hogy a fenti valami nem pont a nevre mutat. Lehet hogy az offset itt 32 biten megis mashogy van? 

Szerk-szerk: Oh, megvan: ket SHT_STRTAB is van ebben a *.elf-ben... es hat nem mindegy hogy melyiket hasznaljuk. Az elsonel zagyvasag jon ki, a masodikra tokeletes es pont azt adja ki ami nekem kell. 

kulonbseggel hogy 32 bites ELF-eken kell most dolgozzak [...] Szoval a tipusokat atirtam Elf32_*-ra

Igen, tényleg csak ennyi.

Oh, megvan: ket SHT_STRTAB is van ebben a *.elf-ben... es hat nem mindegy hogy melyiket hasznaljuk.

Ja, igen, lehet több sztringtábla is, tipikusan ilyenkor van egy, amiben a szekciónevek vannak (ehdr->e_shstrndx indexű szekcióban), meg egy másik csak a szimbólumok neveivel. De ez csak konvenció kérdése, lehet egyben is a kettő, az ELF megengedi.

Ha tutibiztosra akarsz menni, akkor a szekció típuskódja helyett a szekciónevet kell nézni:

    // szekciónevek sztringtáblája
    strt = (Elf32_Shdr *)((uint8_t *)shdr + ehdr->e_shstrndx * ehdr->e_shentsize);
    shstr = (char*)data + strt->sh_offset;
    // nem típuskódot, hanem szekcióneveket nézünk
    for(i = 0; i < ehdr->e_shnum; i++) {
        s = ((Elf32_Shdr *)((uint8_t *)shdr + i * ehdr->e_shentsize));  // köv. section
        if(!memcmp(shstr + shdr->sh_name, ".strtab", 8)) strs = s; else
        if(!memcmp(shstr + shdr->sh_name, ".symtab", 8)) syms = s;
    }
    // a többi már ugyanaz

A szimbólumneveket tartalmazó sztringtábla neve mindig ".strtab" és ilyenből mindig csak egy van. Ha a nevet nézed, akkor működik úgy is, ha csak egy sztringtábla van, akkor is, ha kettő, meg akkor is, ha a másodikban vannak a szekciónevek (ezt sem köti meg az ELF, elvileg bármi lehet a sorrend).

Szuper! :) Mindjart (vagyis kicsit kesobb) megnezem jobban. 

Szerk: most neztem meg. Picit finomhangolni kellett a forrason, de lehet hogy csak az en hulyesegem miatt. Lenyeg a lenyeg hogy ez igy most jonak tunik:

if ( s->sh_type == SHT_STRTAB && memcmp(shstr + s->sh_name, ".strtab", 8) ==0 )
 {      strs = s;
 }
else if ( s->sh_type == SHT_SYMTAB && memcmp(shstr + s->sh_name, ".symtab", 8) == 0 )
 {      syms = s;
 }

Es akkor a tobbi valtozatlan, ahogy mondod. Szepen megy a kod az osszes kritikus/erdekes architektura (msp430, avr, riscv32/rv32eac, armv6-m) toolchain-jei altal generalt *.elf-ekre is meg regebbi x86-os binarisokra is amit talaltam elfekvovben, valtoztatas nelkul. Kiprobalom majd a 32/64 bites megkulonboztetest... van par riscv64-es implementaciom is, azokra sem baj ha megy majd :)

Kiprobalom majd a 32/64 bites megkulonboztetest

Az meg

    if(ehdr->e_ident[EI_CLASS] == ELFCLASS64) {
        // 64 bites, Elf64_* struktok
    } else {
        // 32 bites, Elf32_* struktok
    }

Ja, és ha mindenféle architektúrára akarod használni, akkor elfordulhat, hogy big-endian számokba is belefutsz. Ezt

    if(ehdr->e_ident[EI_DATA] == ELFDATA2LSB) {
        // little endian (x86, arm, risc, stb.)
    } else {
        // big endian (ősrégi procik, pl. M68K)
    }

kóddal lehet egységesen kezelni. Elvileg, 20 éve láttam utoljára nem little endian masinát, ELF big endian fájlt meg talán még sose volt a kezeim között, ha jól emlékszem (utoljára Sparc-on használtam olyant, de azokat nem kellett soha feldolgoznom, csak futtattam őket).

Biztos tudod, de azért leírom, ha valaki weben keresve találna ide, hogy ha little endianra fordítod a programod (valószínű) és egy big endian ELF-be botlanál, akkor meg kell hívni a megfelelő méretű __builtin_bswap16(), __builtin_bswap32() vagy __builtin_bswap64() függvényt az ELF struktok mezőire. Elvileg libc függvények, de én jobb szeretem a fordító beépített verzióit használni, hatékonyabb kódot generál hozzá.
Vagy, ha azt akarod, a programod portolható legyen és működjön big endian vasra lefordítva is, akkor érdemes inkább az endian.h függvényeit használni (ezek a wrapperek fordításkor döntik el, hogy kell-e a bswap() vagy sem).

Illetve meg debuggolas kozben annyi kijott hogy az elso SHT_STRTAB neve az `.shstrtab`

Igen, általában, de ne vegyél arra mérget, hogy mindig az első lesz. Az ehdr->e_shstrndx index a biztos.