Nástroj objdump: švýcarský nožík pro vývojáře

24. 1. 2023
Doba čtení: 33 minut

Sdílet

 Autor: Depositphotos
Jednou z velmi užitečných utilit v balíčku GNU Binutils je objdump. Ta umožňuje získávat důležité informace jak z objektových souborů, tak i z dynamicky linkovaných knihoven i ze spustitelných (nativních) souborů.

Obsah

1. Nástroj objdump: švýcarský nožík pro vývojáře

2. První seznámení s objdumpem

3. Aplikace typu „Hello World“ naprogramovaná v assembleru

4. Zjištění podrobnějších informací o objektovém souboru hello_world.o

5. Zobrazení obsahu všech sekcí v objektovém kódu

6. Zpětný překlad (disassembling) z objektového souboru

7. Zjištění podrobnějších informací o nativním spustitelném souboru a.out

8. Zpětný překlad (disassembling) ze souboru a.out

9. Zobrazení obsahu sekcí ve formě nestrukturovaných dat

10. Zdrojový kód příkladu naprogramovaného v jazyku C, jenž závisí na dynamické knihovně

11. Zjištění základních informací o výsledném spustitelném souboru

12. Zjištění, které dynamické knihovny program využívá

13. Přečtení verze překladače

14. Disassembling vybrané funkce

15. Vylepšení vyhledání funkce pro disassembling, vizualizace lokálních podmíněných skoků a smyček

16. Deklarace většího množství prázdných sekcí s různými atributy

17. Výpis všech sekcí z objektového souboru i z výsledného binárního souboru

18. Deklarace většího množství sekcí obsahujících statická data

19. Opětovný výpis všech sekcí z objektového souboru i z výsledného binárního souboru

20. Odkazy na Internetu

1. Nástroj objdump: švýcarský nožík pro vývojáře

Jedním z velmi užitečných nástrojů, které jsou součástí balíčku GNU Binutils, je i nástroj nazvaný objdump. Tento nástroj umožňuje získávat důležité informace jak z objektových souborů (tedy ze souborů s koncovkou .o, které tvoří vstup pro linker), tak i z dynamicky linkovaných knihoven (.so) i ze spustitelných (nativních) souborů. Nástroj objdump lze použít pro čtení a zobrazení informací ze souborů ve formátu ELF určených jak pro Linux, tak i pro další operační systémy (*BSD, Solaris, …), a to včetně Androidu. Dnes si některé možnosti objdumpu ukážeme na praktických příkladech; prozatím však vynecháme práci s dynamicky linkovanými knihovnami.

Při popisu možností nástroje objdump se nepřímo seznámíme i s dalšími utilitami. Především se jedná o linker a taktéž o utility readelf, strings, nm a ldd. Taktéž se, opět nepřímo, setkáme s problematikou sekcí (segmentů) definovaných v hlavičkách souborů ELF. Většina překladačů používá standardní sekce pro programový kód, data určená pouze pro čtení (konstanty), oblast inicializovaných dat a konečně oblast dat neinicializovaných (zde je uložena pouze délka sekce). Tyto sekce mají standardizovaná jména: .text, .rodata, .data a .bss. Ovšem nic nám nebrání ve vytvoření sekcí dalších.

2. První seznámení s objdumpem

Utilita objdump je použitelná k mnoha účelům popsaným v navazujících kapitolách. Její univerzálnost je ostatně patrná i při pohledu na množství parametrů a přepínačů, které lze zadávat z příkazového řádku:

$ objdump
 
Usage: objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
      --disassemble=</gcsym>/gc  Display assembler contents from
  -S, --source             Intermix source code with disassembly
      --source-comment[=</gctxt>/gc] Prefix lines of source code with
  -s, --full-contents      Display the full contents of all sections requested
  -g, --debugging          Display debug information in object file
  -e, --debugging-tags     Display debug information using ctags style
  -G, --stabs              Display (in raw form) any STABS info in the file
  -W[lLiaprmfFsoRtUuTgAckK] or
  --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
          =frames-interp,=str,=loc,=Ranges,=pubtypes,
          =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
          =addr,=cu_index,=links,=follow-links]
                           Display DWARF info in the file
  --ctf=SECTION            Display CTF info from SECTION
  -t, --syms               Display the contents of the symbol table(s)
  -T, --dynamic-syms       Display the contents of the dynamic symbol table
  -r, --reloc              Display the relocation entries in the file
  -R, --dynamic-reloc      Display the dynamic relocation entries in the file
  @</gcfile>/gc                  Read options from
  -v, --version            Display this program's version number
  -i, --info               List object formats and architectures supported
  -H, --help               Display this information

Nástroj objdump je možné použít i pro disassembling strojového kódu, i když pro tento účel existují i sofistikovanější a interaktivní nástroje. To, které procesorové architektury a formáty jsou pro nainstalovanou verzi objdumpu dostupné, lze zjistit tímto příkazem:

$ objdump -i

Výstup zobrazený pro variantu objdumpu nainstalovanou na autorově počítači s architekturou x86–64:

BFD header file version (GNU Binutils for Ubuntu) 2.34
elf64-x86-64
 (header little endian, data little endian)
  i386
elf32-i386
 (header little endian, data little endian)
  i386
elf32-iamcu
 (header little endian, data little endian)
  iamcu
elf32-x86-64
 (header little endian, data little endian)
  i386
pei-i386
 (header little endian, data little endian)
  i386
pei-x86-64
 (header little endian, data little endian)
  i386
elf64-l1om
 (header little endian, data little endian)
  l1om
elf64-k1om
 (header little endian, data little endian)
  k1om
elf64-little
 (header little endian, data little endian)
  i386
  l1om
  k1om
  iamcu
elf64-big
 (header big endian, data big endian)
  i386
  l1om
  k1om
  iamcu
elf32-little
 (header little endian, data little endian)
  i386
  l1om
  k1om
  iamcu
elf32-big
 (header big endian, data big endian)
  i386
  l1om
  k1om
  iamcu
pe-x86-64
 (header little endian, data little endian)
  i386
pe-bigobj-x86-64
 (header little endian, data little endian)
  i386
pe-i386
 (header little endian, data little endian)
  i386
srec
 (header endianness unknown, data endianness unknown)
  i386
  l1om
  k1om
  iamcu
symbolsrec
 (header endianness unknown, data endianness unknown)
  i386
  l1om
  k1om
  iamcu
verilog
 (header endianness unknown, data endianness unknown)
  i386
  l1om
  k1om
  iamcu
tekhex
 (header endianness unknown, data endianness unknown)
  i386
  l1om
  k1om
  iamcu
binary
 (header endianness unknown, data endianness unknown)
  i386
  l1om
  k1om
  iamcu
ihex
 (header endianness unknown, data endianness unknown)
  i386
  l1om
  k1om
  iamcu
plugin
 (header little endian, data little endian)
 
         elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 pei-i386 pei-x86-64
    i386 elf64-x86-64 elf32-i386 ----------- elf32-x86-64 pei-i386 pei-x86-64
    l1om ------------ ---------- ----------- ------------ -------- ----------
    k1om ------------ ---------- ----------- ------------ -------- ----------
   iamcu ------------ ---------- elf32-iamcu ------------ -------- ----------
 
         elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big
    i386 ---------- ---------- elf64-little elf64-big elf32-little elf32-big
    l1om elf64-l1om ---------- elf64-little elf64-big elf32-little elf32-big
    k1om ---------- elf64-k1om elf64-little elf64-big elf32-little elf32-big
   iamcu ---------- ---------- elf64-little elf64-big elf32-little elf32-big
 
         pe-x86-64 pe-bigobj-x86-64 pe-i386 srec symbolsrec verilog tekhex
    i386 pe-x86-64 pe-bigobj-x86-64 pe-i386 srec symbolsrec verilog tekhex
    l1om --------- ---------------- ------- srec symbolsrec verilog tekhex
    k1om --------- ---------------- ------- srec symbolsrec verilog tekhex
   iamcu --------- ---------------- ------- srec symbolsrec verilog tekhex
 
         binary ihex plugin
    i386 binary ihex ------
    l1om binary ihex ------
    k1om binary ihex ------
   iamcu binary ihex ------

Relativně novou vlastností objdumpu je možnost vizualizace cílů lokálních skoků. Například pro tento zdrojový špagetový kód s několika lokálními skoky ..

_start:
        nop
        jmp   .L1
 
.L4:
        nop
        int   0x80                   # volani Linuxoveho kernelu
 
.L2:
        mov   ebx, 1                 # exit code = 1
        nop
        jmp   .L3
 
.L1:
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        nop
        jmp   .L2
 
.L3:    dec    ebx                   # zmena exit code 
        nop
        jmp   .L4

…je možné zobrazit tuto strukturu se šipkami mířícími na cíle skoků:

jumps.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 </gc_start>/gc:
   0:                90                         nop
   1:         /----- eb 0b                      jmp    e </gcsys_exit+0xd>/gc
   3:   /-----|----> 90                         nop
   4:   |     |      cd 80                      int    0x80
   6:   |  /--|----> bb 01 00 00 00             mov    ebx,0x1
   b:   |  |  |      90                         nop
   c:   |  |  |  /-- eb 08                      jmp    16 </gcsys_exit+0x15>/gc
   e:   |  |  \--|-> b8 01 00 00 00             mov    eax,0x1
  13:   |  |     |   90                         nop
  14:   |  \-----|-- eb f0                      jmp    6 </gcsys_exit+0x5>/gc
  16:   |        \-> ff cb                      dec    ebx
  18:   |            90                         nop
  19:   \----------- eb e8                      jmp    3 </gcsys_exit+0x2>/gc
Poznámka: navíc je možné dosáhnout i obarveného výstupu:

3. Aplikace typu „Hello World“ naprogramovaná v assembleru

V navazujících kapitolách budeme zkoumat objektový soubor a taktéž výsledný nativní soubor vygenerovaný assemblerem resp. linkerem ze zdrojového kódu klasického demonstračního příkladu typu „Hello World“. Námi použitá varianta je naprogramována v assembleru mikroprocesorů x86 a v jejím zápisu je použita syntaxe používaná firmou Intel a nikoli AT&T syntaxe. Zdrojový kód tohoto příkladu naleznete na adrese https://github.com/tisnik/pre­sentations/blob/master/as­sembler/06_gas_intel_hello_wor­ld/hello_world.s:

# asmsyntax=as
 
# Jednoducha aplikace typu "Hello world!" naprogramovana
# v assembleru GNU as - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
 
.intel_syntax noprefix
 
 
# Linux kernel system call table
sys_exit=1
sys_write=4
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
hello_lbl:
        .string "Hello World!\n"     # string, ktery JE ukoncen nulou
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, 1                 # standardni vystup
        mov   ecx, offset hello_lbl  # adresa retezce, ktery se ma vytisknout
        mov   edx, 13                # pocet znaku, ktere se maji vytisknout
        int   0x80                   # volani Linuxoveho kernelu
 
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

Tento demonstrační příklad nejprve přeložíme assemblerem do objektového kódu hello_world.o a následně linkerem vytvoříme spustitelný soubor a.out (lze samozřejmě zvolit jiné jméno, ale proč nezůstat u klasiky):

$ as hello_world.s -o hello_world.o
$ ld -s hello_world.o
Poznámka: překlad i spuštění je možný jak na 32bitové platformě x86, tak i na 64bitové platformě x86–64.

4. Zjištění podrobnějších informací o objektovém souboru hello_world.o

V této kapitole si ukážeme, jakým způsobem je možné získat podrobnější informace o objektovém souboru hello_world.o, jenž vznikl překladem výše uvedeného zdrojového souboru hello_world.s assemblerem, konkrétně GNU Assemblerem. Pro získání informací o objektovém souboru pochopitelně použijeme nástroj objdump.

Nejprve si necháme zobrazit hlavičku objektového souboru. Pro tento účel se používá přepínač -f. Povšimněte si, že se především zobrazí použitý formát (zde konkrétně ELF neboli Executable and Linkable Format, což je dnes na Linuxu standard), cílová architektura (x86–64) a příznaky určující, že soubor má relokační tabulku (bitová maska 0×01) a tabulku symbolů (bitová maska 0×10), příznakové slovo má tedy hodnotu 0×01+0×10=0×11. A konečně se vypíše počáteční adresa, k jejímuž významu se ještě vrátíme:

$ objdump -f hello_world.o
 
hello_world.o:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x0000000000000000

Z předchozího výpisu je zřejmé, že objektový soubor obsahuje i takzvanou tabulku symbolů. Její obsah si můžeme zobrazit, a to použitím přepínače -t:

$ objdump -t hello_world.o
 
hello_world.o:     file format elf64-x86-64
 
SYMBOL TABLE:
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000001 l       *ABS*  0000000000000000 sys_exit
0000000000000004 l       *ABS*  0000000000000000 sys_write
0000000000000000 l       .data  0000000000000000 hello_lbl
0000000000000000 g       .text  0000000000000000 _start

Každý symbol z tabulky symbolů je vypsán na samostatném řádku. Jednotlivé sloupce mají tento význam:

Sloupec Příklad Význam
1 0000000000000001 hodnota symbolu
2 l d sedm skupin s popisem symbolu, každá skupina reprezentovaná jedním znakem
3 .bss sekce (segment), v němž je symbol definován (*ABS* značí bez sekce)
4 0000000000000000 velikost symbolu nebo zarovnání
5 sys_exit jméno symbolu

Druhý sloupec je nejzajímavější. Je v něm zakódováno mnoho informací o daném symbolu, a to sedmi znaky (z nichž některé mohou být nahrazeny mezerou):

Skupina Znak Stručný popis
1 l,g,mezera,! lokální či globální symbol, popř. oboje
2 w,mezera označuje tzv. weak symbol
3 C,mezera konstruktor nebo běžný symbol
4 W,mezera warning nebo běžný symbol
5 I,mezera odkaz na jiný symbol (či běžný symbol)
6 d,D,mezera symbol pro ladění, dynamický symbol či běžný symbol
7 F,f,O jméno funkce, jméno souboru, běžný symbol

A konečně si ukažme, jak je možné přepínačem -x zobrazit všechny hlavičky v objektovém souboru. Jedná se vlastně o kombinaci přepínačů -f, -t, -a, -h a -p:

$ objdump -x hello_world.o
 
hello_world.o:     file format elf64-x86-64
hello_world.o
architecture: i386:x86-64, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x0000000000000000
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000022  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         0000000e  0000000000000000  0000000000000000  00000062  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000070  2**0
                  ALLOC
SYMBOL TABLE:
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000001 l       *ABS*  0000000000000000 sys_exit
0000000000000004 l       *ABS*  0000000000000000 sys_write
0000000000000000 l       .data  0000000000000000 hello_lbl
0000000000000000 g       .text  0000000000000000 _start
 
 
RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
000000000000000b R_X86_64_32       .data
Poznámka: významem části Sections se budeme zabývat v samostatné kapitole.

5. Zobrazení obsahu všech sekcí v objektovém kódu

V případě, že použijeme přepínač -s, můžeme si nechat zobrazit obsah všech sekcí (segmentů) nalezených v objektovém kódu – a to jak v hexadecimální podobě, tak i ve formě řetězce (ovšem jen pro ASCII znaky). Výsledek bude v našem případě vypadat následovně:

$ objdump -s hello_world.o
 
hello_world.o:     file format elf64-x86-64
 
Contents of section .text:
 0000 b8040000 00bb0100 0000b900 000000ba  ................
 0010 0d000000 cd80b801 000000bb 00000000  ................
 0020 cd80                                 ..
Contents of section .data:
 0000 48656c6c 6f20576f 726c6421 0a00      Hello World!..
Poznámka: formát výpisu je v tomto případě zřejmý z předchozího příkladu:
offset, obsah šestnácti bajtů vypsaný formou hexa číslic sdružených do slov, ASCII znaky zobrazené formou řetězce (popř. tečky pro ty znaky, které nepatří do ASCII).

6. Zpětný překlad (disassembling) z objektového souboru

Nástroj objdump dokáže zjistit i mnohé další užitečné informace z dat uložených v objektovém souboru. Mezi nejzajímavější vlastnost patří zpětný překlad (neboli disassembling), což se typicky týká obsahu sekce (segmentu) nazvané .text, v níž je uložen přeložený objektový kód. Podívejme se nyní na to, jak dobře nástroj objdump porozumí sekvenci instrukcí uložených v této sekci:

$ objdump -d hello_world.o
 
hello_world.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 <_start>:
   0:   b8 04 00 00 00          mov    $0x4,%eax
   5:   bb 01 00 00 00          mov    $0x1,%ebx
   a:   b9 00 00 00 00          mov    $0x0,%ecx
   f:   ba 0d 00 00 00          mov    $0xd,%edx
  14:   cd 80                   int    $0x80
  16:   b8 01 00 00 00          mov    $0x1,%eax
  1b:   bb 00 00 00 00          mov    $0x0,%ebx
  20:   cd 80                   int    $0x80

V porovnání se zdrojovým kódem ze čtvrté kapitoly je patrné, že ve výchozím nastavení použil nástroj objdump formát instrukcí (tedy mnemotechnická jména instrukcí) i operandů (pořadí, formát zápisu), který odpovídá syntaxi zavedenou společností AT&T. Pokud preferujete, podobně jako autor tohoto článku, spíše syntax používanou společností Intel, je nutné použít přepínač -M intel_mnemonic nebo jen -M intel (což je jiný přepínač, který však v našem konkrétním případě vytiskne stejný výsledek). Výsledek se již bude do značné míry podobat původnímu zdrojovému kódu:

$ objdump -d -M intel_mnemonic hello_world.o
 
hello_world.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 <_start>:
   0:   b8 04 00 00 00          mov    eax,0x4
   5:   bb 01 00 00 00          mov    ebx,0x1
   a:   b9 00 00 00 00          mov    ecx,0x0
   f:   ba 0d 00 00 00          mov    edx,0xd
  14:   cd 80                   int    0x80
  16:   b8 01 00 00 00          mov    eax,0x1
  1b:   bb 00 00 00 00          mov    ebx,0x0
  20:   cd 80                   int    0x80

Ve skutečnosti je výchozí formát výstupu jakýmsi kompromisem mezi syntaxí Intelu a AT&T. V případě AT&T by totiž instrukce měly obsahovat suffix, konkrétně jeden znak, z něhož lze vyčíst délku operandů. I tento trik objdump podporuje:

hello_world.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 <_start>:
   0:   b8 04 00 00 00          movl   $0x4,%eax
   5:   bb 01 00 00 00          movl   $0x1,%ebx
   a:   b9 00 00 00 00          movl   $0x0,%ecx
   f:   ba 0d 00 00 00          movl   $0xd,%edx
  14:   cd 80                   int    $0x80
  16:   b8 01 00 00 00          movl   $0x1,%eax
  1b:   bb 00 00 00 00          movl   $0x0,%ebx
  20:   cd 80                   int    $0x80
Poznámka: ne vždy ovšem bude disassembling úspěšný, protože se může stát, že objdump nenalezne začátky instrukcí. To se týká zejména těch platforem, kde mohou mít instrukce různou délku: x86, x86–64, ARM Thumb-2 atd.

7. Zjištění podrobnějších informací o nativním spustitelném souboru a.out

Prozatím jsme nástroj objdump používali pro zjištění informací z objektových souborů – ostatně právě kvůli této vlastnosti získal tento nástroj své jméno. Ovšem podobné informace je možné v případě potřeby získat i z výsledných spustitelných (nativních) souborů a.out (které samozřejmě mohou mít odlišné jméno a na Linuxu typicky nemají žádnou koncovku).

Poznámka: samotné a.out je starobylý název pocházející z originálního Unixu a znamená assembler output (což vlastně není pravda, protože se dnes jedná o výsledek činnosti linkeru). Pro větší zmatení čtenářů se navíc jméno a.out používá i pro pojmenování staršího formátu nativních binárních souborů. Tento formát je však formátem ELF překonán (i když má jednu výhodu – kratší délku souboru, i když starobylému COM konkurovat nemůže).

Vraťme se však k souboru a.out, který vznikl překladem a slinkováním zdrojového kódu ze čtvrté kapitoly. Nejdříve si necháme zobrazit hlavičku tohoto souboru:

$ objdump -f a.out
 
a.out:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x0000000000401000
Poznámka: povšimněte si nyní již reálné počáteční adresy, která se odlišuje od nulové adresy pro objektové soubory.

Zobrazit si můžeme i souhrnné základní informace o zkoumaném souboru. Opět použijeme přepínač, který již známe – -x:

$ objdump -x a.out
 
a.out:     file format elf64-x86-64
a.out
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x0000000000401000
 
Program Header:
    LOAD off    0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**12
         filesz 0x00000000000000e8 memsz 0x00000000000000e8 flags r--
    LOAD off    0x0000000000001000 vaddr 0x0000000000401000 paddr 0x0000000000401000 align 2**12
         filesz 0x0000000000000022 memsz 0x0000000000000022 flags r-x
    LOAD off    0x0000000000002000 vaddr 0x0000000000402000 paddr 0x0000000000402000 align 2**12
         filesz 0x000000000000000e memsz 0x000000000000000e flags rw-
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000022  0000000000401000  0000000000401000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         0000000e  0000000000402000  0000000000402000  00002000  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
no symbols
Poznámka: významem části Sections se budeme, jak jsme si již ostatně řekli, zabývat v samostatné kapitole. Zajímavé taktéž je, že tabulka symbolů je prázdná – ve výsledném programu tedy nejsou žádné externě viditelné symboly.

8. Zpětný překlad (disassembling) ze souboru a.out

Vzhledem k tomu, že sekce .text obsahuje i ve výsledných spustitelných souborech sekvenci strojových instrukcí, můžeme si velmi snadno zobrazit disassemblovaný obsah této sekce. K tomuto účelu použijeme již známý přepínač -d. Ve výchozím nastavení se přitom používá syntaxe podle firmy AT&T, což je ostatně vidět na výpisu níže (operandy instrukcí jsou v pořadí zdroj, cíl, u jmen registrů se používají procenta atd.):

$ objdump -d a.out
 
a.out:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000401000 <.text>:
  401000:       b8 04 00 00 00          mov    $0x4,%eax
  401005:       bb 01 00 00 00          mov    $0x1,%ebx
  40100a:       b9 00 20 40 00          mov    $0x402000,%ecx
  40100f:       ba 0d 00 00 00          mov    $0xd,%edx
  401014:       cd 80                   int    $0x80
  401016:       b8 01 00 00 00          mov    $0x1,%eax
  40101b:       bb 00 00 00 00          mov    $0x0,%ebx
  401020:       cd 80                   int    $0x80

Opět pochopitelně můžeme přepínačem -M zvolit odlišnou syntaxi, zde konkrétně syntaxi používanou společností Intel:

$ objdump -M intel -d a.out
 
a.out:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000401000 <.text>:
  401000:       b8 04 00 00 00          mov    eax,0x4
  401005:       bb 01 00 00 00          mov    ebx,0x1
  40100a:       b9 00 20 40 00          mov    ecx,0x402000
  40100f:       ba 0d 00 00 00          mov    edx,0xd
  401014:       cd 80                   int    0x80
  401016:       b8 01 00 00 00          mov    eax,0x1
  40101b:       bb 00 00 00 00          mov    ebx,0x0
  401020:       cd 80                   int    0x80

9. Zobrazení obsahu sekcí ve formě nestrukturovaných dat

Přepínačem -s si můžeme nechat vypsat obsah sekcí nebo vybrané sekce ve formě nestrukturovaných dat. Nástroj objdump v tomto případě vypisuje data podobně, jako hexa prohlížeče: na každém řádku je počáteční offset, následuje obsah šestnácti bajtů ve formě hexadecimálního výpisu a tytéž data, ovšem interpretovaná jako ASCII text (ostatní znaky se zobrazí formou tečky):

$ objdump -s a.out
 
a.out:     file format elf64-x86-64
 
Contents of section .text:
 401000 b8040000 00bb0100 0000b900 204000ba  ............ @..
 401010 0d000000 cd80b801 000000bb 00000000  ................
 401020 cd80                                 ..
Contents of section .data:
 402000 48656c6c 6f20576f 726c6421 0a00      Hello World!..

Většinou mám smysl tímto způsobem zobrazit obsah sekcí .data nebo .rodata, tedy takto:

$ objdump -s -j.data a.out
 
a.out:     file format elf64-x86-64
 
Contents of section .data:
 402000 48656c6c 6f20576f 726c6421 0a00      Hello World!..

10. Zdrojový kód příkladu naprogramovaného v jazyku C, jenž závisí na dynamické knihovně

V dalších kapitolách budeme zkoumat objektový kód i výsledný spustitelný soubor, který vznikne překladem a slinkováním jednoduchého programu napsaného v jazyce C. Povšimněte si, že v tomto programu používáme funkce definované v hlavičkovém souboru math.h a implementované v knihovně libm.so, kterou je nutné slinkovat explicitně:

#include <stdio.h>
#include <math.h>
 
double compute_sin(double x)
{
    return sin(x);
}
 
int main(void)
{
    double x;

    for (x = 0; x < M_PI/2.0; x+=M_PI/40.0) {
        double y = compute_sin(x);
        printf("%f\t%f\n", x, y);
    }
}

Překlad do objektového souboru zajistí příkaz:

$ gcc -c test.c

Překlad a slinkování do výsledného binárního souboru můžeme provést jediným příkazem:

$ gcc test.c -lm

11. Zjištění základních informací o výsledném spustitelném souboru

Spustitelný soubor vzniklý překladem z céčka již obsahuje o mnoho víc informací, než tomu bylo při překladu z assembleru. Pro jeho analýzu můžeme kromě objdump použít i další nástroje. Užitečné základní informace o souboru získáme například nástrojem readelf:

$ readelf -h a.out
 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1080
  Start of program headers:          64 (bytes into file)
  Start of section headers:          14792 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30

Předchozí výpis obsahuje i informace o tom, kde lze nalézt začátky hlaviček i sekcí atd. Vypišme si tedy nejdříve všechny hlavičky, a to včetně privátních hlaviček. Použijeme přitom přepínač -p:

$ objdump -p a.out
 
a.out:     file format elf64-x86-64
 
Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
         filesz 0x00000000000002d8 memsz 0x00000000000002d8 flags r--
  INTERP off    0x0000000000000318 vaddr 0x0000000000000318 paddr 0x0000000000000318 align 2**0
         filesz 0x000000000000001c memsz 0x000000000000001c flags r--
    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x0000000000000660 memsz 0x0000000000000660 flags r--
    LOAD off    0x0000000000001000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12
         filesz 0x0000000000000295 memsz 0x0000000000000295 flags r-x
    LOAD off    0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**12
         filesz 0x0000000000000198 memsz 0x0000000000000198 flags r--
    LOAD off    0x0000000000002da0 vaddr 0x0000000000003da0 paddr 0x0000000000003da0 align 2**12
         filesz 0x0000000000000270 memsz 0x0000000000000278 flags rw-
 DYNAMIC off    0x0000000000002db0 vaddr 0x0000000000003db0 paddr 0x0000000000003db0 align 2**3
         filesz 0x0000000000000200 memsz 0x0000000000000200 flags rw-
    NOTE off    0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3
         filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
    NOTE off    0x0000000000000358 vaddr 0x0000000000000358 paddr 0x0000000000000358 align 2**2
         filesz 0x0000000000000044 memsz 0x0000000000000044 flags r--
0x6474e553 off    0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3
         filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
EH_FRAME off    0x0000000000002020 vaddr 0x0000000000002020 paddr 0x0000000000002020 align 2**2
         filesz 0x000000000000004c memsz 0x000000000000004c flags r--
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
         filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
   RELRO off    0x0000000000002da0 vaddr 0x0000000000003da0 paddr 0x0000000000003da0 align 2**0
         filesz 0x0000000000000260 memsz 0x0000000000000260 flags r--
 
Dynamic Section:
  NEEDED               libm.so.6
  NEEDED               libc.so.6
  INIT                 0x0000000000001000
  FINI                 0x0000000000001288
  INIT_ARRAY           0x0000000000003da0
  INIT_ARRAYSZ         0x0000000000000008
  FINI_ARRAY           0x0000000000003da8
  FINI_ARRAYSZ         0x0000000000000008
  GNU_HASH             0x00000000000003a0
  STRTAB               0x0000000000000488
  SYMTAB               0x00000000000003c8
  STRSZ                0x0000000000000092
  SYMENT               0x0000000000000018
  DEBUG                0x0000000000000000
  PLTGOT               0x0000000000003fb0
  PLTRELSZ             0x0000000000000030
  PLTREL               0x0000000000000007
  JMPREL               0x0000000000000630
  RELA                 0x0000000000000570
  RELASZ               0x00000000000000c0
  RELAENT              0x0000000000000018
  FLAGS                0x0000000000000008
  FLAGS_1              0x0000000008000001
  VERNEED              0x0000000000000530
  VERNEEDNUM           0x0000000000000002
  VERSYM               0x000000000000051a
  RELACOUNT            0x0000000000000003
 
Version References:
  required from libm.so.6:
    0x09691a75 0x00 03 GLIBC_2.2.5
  required from libc.so.6:
    0x09691a75 0x00 02 GLIBC_2.2.5

12. Zjištění, které dynamické knihovny program využívá

V případě, že je zapotřebí zjistit, které dynamické knihovny program využívá (tedy načítá po svém spuštění), se pro tento účel typicky používá nástroj ldd:

$ ldd a.out

Ten v našem konkrétním případě vypisuje čtyři knihovny:

        linux-vdso.so.1 (0x00007fff88572000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f58652b1000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f58650bf000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f586541d000)

Pro podobný účel můžeme použít i nástroj objdump, jenž ovšem vypisuje informace o sdílených knihovnách uložených přímo ve strukturách souborů ELF:

$ objdump -p a.out | grep NEEDED
 
  NEEDED               libm.so.6
  NEEDED               libc.so.6

Proč se však oba výpisy od sebe odlišují? V případě objdumpu se skutečně vypisují informace uložené v souboru ELF (tedy objektovém souboru nebo spustitelném souboru), ovšem ldd navíc zjišťuje i tranzitivní závislosti obou dynamických knihoven, tj. nejdříve zjistí stejné informace jako objdump a posléze si interně spustí další dotazy:

$ ldd /lib/x86_64-linux-gnu/libm.so.6
        linux-vdso.so.1 (0x00007ffdcd98f000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fed71c7a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fed71fd3000)
 
$ ldd /lib/x86_64-linux-gnu/libc.so.6
        /lib64/ld-linux-x86-64.so.2 (0x00007fc61926e000)
        linux-vdso.so.1 (0x00007ffdc73f5000)

13. Přečtení verze překladače

Překladač GCC C, ale i mnohé další překladače, jsou ve výsledném binárním spustitelném souboru „podepsány“. Konkrétně verzi překladače GCC C je možné na příkazové řádce zjistit tímto příkazem:

$ gcc --version
 
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Naprosto tytéž údaje, které jsou v předchozím výstupu zobrazeny na prvním řádku, nalezneme v sekci nazvané .comment získané a vypsané nástrojem objdump:

$ objdump -s --section .comment a.out
 
a.out:     file format elf64-x86-64
 
Contents of section .comment:
 0000 4743433a 20285562 756e7475 20392e34  GCC: (Ubuntu 9.4
 0010 2e302d31 7562756e 7475317e 32302e30  .0-1ubuntu1~20.0
 0020 342e3129 20392e34 2e3000             4.1) 9.4.0.

Stejné údaje lze (i když mnohdy s poněkud nepřesným výsledkem) zjistit nástrojem strings

$ strings a.out | grep ^GCC
 
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

Podobně můžeme namísto objdump či strings použít příkaz readelf:

$ readelf -p .comment a.out
 
String dump of section '.comment':
  [     0]  GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

A konečně mnohem přesnější informace zjistíme po zadání přepínače -n nástroji readelf:

$ readelf -n a.out
 
Displaying notes found in: .note.gnu.property
  Owner                Data size        Description
  GNU                  0x00000010       NT_GNU_PROPERTY_TYPE_0
      Properties: x86 feature: IBT, SHSTK
 
Displaying notes found in: .note.gnu.build-id
  Owner                Data size        Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 43d1b44a3766e664647e1079689a207655f84806
 
Displaying notes found in: .note.ABI-tag
  Owner                Data size        Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

14. Disassembling vybrané funkce

Poměrně často se setkáme s požadavkem na zobrazení instrukcí, které vznikly překladem určité konkrétní funkce, například funkce main atd. Jedno z často doporučovaných řešení spočívá ve filtraci výstupu objdumpu, například nástrojem awk. Toto řešení je sice skutečně často citované, ale není nejlepší:

$ objdump -d -M intel a.out | awk -v RS= '/^[[:xdigit:]]+ <main>/'
 
000000000000118a :
    118a:       f3 0f 1e fa             endbr64
    118e:       55                      push   rbp
    118f:       48 89 e5                mov    rbp,rsp
    1192:       48 83 ec 10             sub    rsp,0x10
    1196:       66 0f ef c0             pxor   xmm0,xmm0
    119a:       f2 0f 11 45 f0          movsd  QWORD PTR [rbp-0x10],xmm0
    119f:       eb 50                   jmp    11f1 </gcmain+0x67>/gc
    11a1:       48 8b 45 f0             mov    rax,QWORD PTR [rbp-0x10]
    11a5:       66 48 0f 6e c0          movq   xmm0,rax
    11aa:       e8 ba ff ff ff          call   1169 </gccompute_sin>/gc
    11af:       66 48 0f 7e c0          movq   rax,xmm0
    11b4:       48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
    11b8:       f2 0f 10 45 f8          movsd  xmm0,QWORD PTR [rbp-0x8]
    11bd:       48 8b 45 f0             mov    rax,QWORD PTR [rbp-0x10]
    11c1:       66 0f 28 c8             movapd xmm1,xmm0
    11c5:       66 48 0f 6e c0          movq   xmm0,rax
    11ca:       48 8d 3d 37 0e 00 00    lea    rdi,[rip+0xe37]        # 2008 </gc_IO_stdin_used+0x8>/gc
    11d1:       b8 02 00 00 00          mov    eax,0x2
    11d6:       e8 85 fe ff ff          call   1060 </gcprintf@plt>/gc
    11db:       f2 0f 10 4d f0          movsd  xmm1,QWORD PTR [rbp-0x10]
    11e0:       f2 0f 10 05 28 0e 00    movsd  xmm0,QWORD PTR [rip+0xe28]        # 2010 </gc_IO_stdin_used+0x10>/gc
    11e7:       00
    11e8:       f2 0f 58 c1             addsd  xmm0,xmm1
    11ec:       f2 0f 11 45 f0          movsd  QWORD PTR [rbp-0x10],xmm0
    11f1:       f2 0f 10 05 1f 0e 00    movsd  xmm0,QWORD PTR [rip+0xe1f]        # 2018 </gc_IO_stdin_used+0x18>/gc
    11f8:       00
    11f9:       66 0f 2f 45 f0          comisd xmm0,QWORD PTR [rbp-0x10]
    11fe:       77 a1                   ja     11a1 </gcmain+0x17>/gc
    1200:       b8 00 00 00 00          mov    eax,0x0
    1205:       c9                      leave
    1206:       c3                      ret
    1207:       66 0f 1f 84 00 00 00    nop    WORD PTR [rax+rax*1+0x0]
    120e:       00 00

Podobně si můžeme zobrazit instrukce tvořící tělo funkce compute_sin:

$ objdump -d -M intel a.out | awk -v RS= '/^[[:xdigit:]]+ <compute_sin>/'
 
0000000000001169 </gccompute_sin>/gc:
    1169:       f3 0f 1e fa             endbr64
    116d:       55                      push   rbp
    116e:       48 89 e5                mov    rbp,rsp
    1171:       48 83 ec 10             sub    rsp,0x10
    1175:       f2 0f 11 45 f8          movsd  QWORD PTR [rbp-0x8],xmm0
    117a:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
    117e:       66 48 0f 6e c0          movq   xmm0,rax
    1183:       e8 e8 fe ff ff          call   1070 </gcsin@plt>/gc
    1188:       c9                      leave
    1189:       c3                      ret

15. Vylepšení vyhledání funkce pro disassembling, vizualizace lokálních podmíněných skoků a smyček

Ve skutečnosti za nás může vyhledání konkrétní funkce (resp. obecně symbolu) provést přímo nástroj objdump, a to následovně:

$ objdump -d -M intel --disassemble=compute_sin a.out
 
a.out:     file format elf64-x86-64
 
 
Disassembly of section .init:
 
Disassembly of section .plt:
 
Disassembly of section .plt.got:
 
Disassembly of section .plt.sec:
 
Disassembly of section .text:
 
0000000000001169 <compute_sin>:
    1169:       f3 0f 1e fa             endbr64
    116d:       55                      push   rbp
    116e:       48 89 e5                mov    rbp,rsp
    1171:       48 83 ec 10             sub    rsp,0x10
    1175:       f2 0f 11 45 f8          movsd  QWORD PTR [rbp-0x8],xmm0
    117a:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
    117e:       66 48 0f 6e c0          movq   xmm0,rax
    1183:       e8 e8 fe ff ff          call   1070 <sin@plt>
    1188:       c9                      leave
    1189:       c3                      ret
 
Disassembly of section .fini:

Pokud z nějakého důvodu překáží výpis hlaviček ostatních sekcí, můžeme zkombinovat vyhledání s filtrací podle sekce (zde konkrétně podle standardní sekce .text):

$ objdump -d -M intel -j.text --disassemble=compute_sin a.out
 
a.out:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000001169 <compute_sin>:
    1169:       f3 0f 1e fa             endbr64
    116d:       55                      push   rbp
    116e:       48 89 e5                mov    rbp,rsp
    1171:       48 83 ec 10             sub    rsp,0x10
    1175:       f2 0f 11 45 f8          movsd  QWORD PTR [rbp-0x8],xmm0
    117a:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
    117e:       66 48 0f 6e c0          movq   xmm0,rax
    1183:       e8 e8 fe ff ff          call   1070 <sin@plt>
    1188:       c9                      leave
    1189:       c3                      ret

A konečně přepínačem –visualize-jumps či –visualize-jumps=color je možné si nechat zobrazit cíle lokálních skoků v rámci jedné funkce:

  31:             f2 0f 11 45 f0        movsd  QWORD PTR [rbp-0x10],xmm0
  36:      /----- eb 61                 jmp    99 </gcmain+0x78>/gc
  38:   /--|----> 48 8b 45 f0           mov    rax,QWORD PTR [rbp-0x10]
  3c:   |  |      66 48 0f 6e c0        movq   xmm0,rax
  41:   |  |  /-- e8 00 00 00 00        call   46 </gcmain+0x25>/gc   42: R_X86_64_PLT32      compute_sin-0x4
  46:   |  |  \-> 66 48 0f 7e c0        movq   rax,xmm0
  4b:   |  |      48 89 45 f8           mov    QWORD PTR [rbp-0x8],rax
  4f:   |  |      48 8d 3d 00 00 00 00  lea    rdi,[rip+0x0]        # 56 </gcmain+0x35>/gc    52: R_X86_64_PC32       .rodata-0x4
  56:   |  |      b8 00 00 00 00        mov    eax,0x0
  5b:   |  |  /-- e8 00 00 00 00        call   60 </gcmain+0x3f>/gc   5c: R_X86_64_PLT32      printf-0x4
  60:   |  |  \-> f2 0f 10 45 f8        movsd  xmm0,QWORD PTR [rbp-0x8]
  65:   |  |      48 8b 45 f0           mov    rax,QWORD PTR [rbp-0x10]
  69:   |  |      66 0f 28 c8           movapd xmm1,xmm0
  6d:   |  |      66 48 0f 6e c0        movq   xmm0,rax
  72:   |  |      48 8d 3d 00 00 00 00  lea    rdi,[rip+0x0]        # 79 </gcmain+0x58>/gc    75: R_X86_64_PC32       .rodata+0x15
  79:   |  |      b8 02 00 00 00        mov    eax,0x2
  7e:   |  |  /-- e8 00 00 00 00        call   83 </gcmain+0x62>/gc   7f: R_X86_64_PLT32      printf-0x4
  83:   |  |  \-> f2 0f 10 4d f0        movsd  xmm1,QWORD PTR [rbp-0x10]
  88:   |  |      f2 0f 10 05 00 00 00 00       movsd  xmm0,QWORD PTR [rip+0x0]        # 90 </gcmain+0x6f>/gc 8c: R_X86_64_PC32       .rodata+0x1c
  90:   |  |      f2 0f 58 c1           addsd  xmm0,xmm1
  94:   |  |      f2 0f 11 45 f0        movsd  QWORD PTR [rbp-0x10],xmm0
  99:   |  \----> f2 0f 10 05 00 00 00 00       movsd  xmm0,QWORD PTR [rip+0x0]        # a1 </gcmain+0x80> 9d: R_X86_64_PC32       .rodata+0x24
  a1:   |         66 0f 2f 45 f0        comisd xmm0,QWORD PTR [rbp-0x10]
  a6:   \-------- 77 90                 ja     38 </gcmain+0x17>/gc
  a8:             b8 00 00 00 00        mov    eax,0x0
Poznámka: poněkud paradoxně nebude zvýraznění skoků (resp. jejich cílů) plně funkční u programů psaných v assembleru, resp. přesněji řečeno bude funkční za předpokladu, že se použijí lokální návěští. Ty začínají znaky .L (viz též příklad ze druhé kapitoly). Ostatní návěští vlastně kód „rozdělí“ do více logických celků, mezi nimiž se již šipky nekreslí.

16. Deklarace většího množství prázdných sekcí s různými atributy

V dalším textu se pokusíme analyzovat objektový soubor (a taktéž výsledný binární soubor vytvořený linkerem), v němž je definováno větší množství sekcí (segmentů). U jednotlivých sekcí jsou uvedeny i jejich atributy, tj. zda daná sekce obsahuje kód nebo data, zda je možné do sekce zapisovat či z ní jenom číst atd. Povšimněte si způsobu zápisu jmen sekcí s tečkou na začátku i způsobu specifikace atributů realizovaného formou řetězce. Tento zdrojový kód naleznete na adrese https://github.com/tisnik/pre­sentations/blob/master/as­sembler/multiple_sections1/sec­tions1.s:

# asmsyntax=as
 
.intel_syntax noprefix
 
 
# Linux kernel system call table
sys_exit=1
sys_write=4
 
#-----------------------------------------------------------------------------
 
# ruzne sekce (segmenty) se specifickymi atributy
 
.section .section_a
.section .section_b,"x"
.section .section_c,"a"
.section .section_d,"l"
.section .section_e,"w"
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
hello_lbl:
        .string "Hello World!\n"     # string, ktery JE ukoncen nulou
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, 1                 # standardni vystup
        mov   ecx, offset hello_lbl  # adresa retezce, ktery se ma vytisknout
        mov   edx, 13                # pocet znaku, ktere se maji vytisknout
        int   0x80                   # volani Linuxoveho kernelu
 
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

17. Výpis všech sekcí z objektového souboru i z výsledného binárního souboru

V prvním kroku provedeme překlad výše uvedeného zdrojového textu do objektového souboru a ve druhém kroku necháme linker vytvořit spustitelný nativní soubor a.out:

$ as sections1.s -o sections1.o
$ ld -s sections1.o

V objektovém souboru by se měly nacházet deklarace všech sekcí, a to včetně našich pěti sekcí. To ostatně jednoduše zjistíme nástrojem objdump. Povšimněte si, že tyto sekce mají nulovou velikost a různě nastavené atributy (READONLY, CODE, ALLOC, CONTENTS, DATA atd.):

$ objdump -h sections1.o
 
sections1.o:     file format elf64-x86-64
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000022  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         0000000e  0000000000000000  0000000000000000  00000062  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000070  2**0
                  ALLOC
  3 .section_a    00000000  0000000000000000  0000000000000000  00000070  2**0
                  CONTENTS, READONLY
  4 .section_b    00000000  0000000000000000  0000000000000000  00000070  2**0
                  CONTENTS, READONLY, CODE
  5 .section_c    00000000  0000000000000000  0000000000000000  00000070  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .section_d    00000000  0000000000000000  0000000000000000  00000070  2**0
                  CONTENTS, READONLY
  7 .section_e    00000000  0000000000000000  0000000000000000  00000070  2**0
                  CONTENTS

Naproti tomu se ve spustitelném souboru a.out nachází pouze sekce s nenulovou délkou. Opět si to velmi snadno ověříme nástrojem objdump:

$ objdump -h a.out
 
a.out:     file format elf64-x86-64
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000022  0000000000401000  0000000000401000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         0000000e  0000000000402000  0000000000402000  00002000  2**0
                  CONTENTS, ALLOC, LOAD, DATA

18. Deklarace většího množství sekcí obsahujících statická data

Zdrojový kód ze šestnácté kapitoly nyní nepatrně upravíme, a to konkrétně takovým způsobem, že do každé nově definované sekce bude přidán řetězec, tedy statická data. Výsledný zdrojový kód je dostupný na adrese https://github.com/tisnik/pre­sentations/blob/master/as­sembler/multiple_sections2/sec­tions2.s:

# asmsyntax=as
 
.intel_syntax noprefix
 
 
# Linux kernel system call table
sys_exit=1
sys_write=4
 
#-----------------------------------------------------------------------------
 
# ruzne sekce (segmenty) se specifickymi atributy
 
.section .section_a
       .string "SECTION A"
 
.section .section_b,"x"
       .string "SECTION B"
 
.section .section_c,"a"
       .string "SECTION C"
 
.section .section_d,"l"
       .string "SECTION D"
 
.section .section_e,"w"
       .string "SECTION E"
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
hello_lbl:
        .string "Hello World!\n"     # string, ktery JE ukoncen nulou
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, 1                 # standardni vystup
        mov   ecx, offset hello_lbl  # adresa retezce, ktery se ma vytisknout
        mov   edx, 13                # pocet znaku, ktere se maji vytisknout
        int   0x80                   # volani Linuxoveho kernelu
 
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

19. Opětovný výpis všech sekcí z objektového souboru i z výsledného binárního souboru

Opět si po překladu a slinkování zdrojového kódu příkazy:

$ as sections2.s -o sections2.o
$ ld -s sections2.o

vypišme všechny sekce, které nástroj objdump nalezne v objektovém kódu. U každé sekce se vypisuje její velikost, virtuální i fyzická adresa, zarovnání a atributy sekce (segmentu):

$ objdump -h sections2.o
 
sections2.o:     file format elf64-x86-64
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000022  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         0000000e  0000000000000000  0000000000000000  00000062  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000070  2**0
                  ALLOC
  3 .section_a    0000000a  0000000000000000  0000000000000000  00000070  2**0
                  CONTENTS, READONLY
  4 .section_b    0000000a  0000000000000000  0000000000000000  0000007a  2**0
                  CONTENTS, READONLY, CODE
  5 .section_c    0000000a  0000000000000000  0000000000000000  00000084  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .section_d    0000000a  0000000000000000  0000000000000000  0000008e  2**0
                  CONTENTS, READONLY
  7 .section_e    0000000a  0000000000000000  0000000000000000  00000098  2**0
                  CONTENTS

Vidíme, že jsou vypsány všechny sekce a námi vytvořené sekce již nejsou prázdné (mají délku 0×a=10 bajtů).

Neprázdné sekce jsou zachovány i ve výsledném nativním spustitelném souboru, o čemž se můžeme snadno přesvědčit:

ict ve školství 24

$ objdump -h a.out
 
a.out:     file format elf64-x86-64
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000022  0000000000401000  0000000000401000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .section_c    0000000a  0000000000402000  0000000000402000  00002000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         0000000e  000000000040300a  000000000040300a  0000200a  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  3 .section_a    0000000a  0000000000000000  0000000000000000  00002018  2**0
                  CONTENTS, READONLY
  4 .section_b    0000000a  0000000000000000  0000000000000000  00002022  2**0
                  CONTENTS, READONLY, CODE
  5 .section_d    0000000a  0000000000000000  0000000000000000  0000202c  2**0
                  CONTENTS, READONLY
  6 .section_e    0000000a  0000000000000000  0000000000000000  00002036  2**0
                  CONTENTS

Názvy sekcí nalezneme i v tabulce symbolů:

$ objdump -t sections2.o
 
sections2.o:     file format elf64-x86-64
 
SYMBOL TABLE:
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000001 l       *ABS*  0000000000000000 sys_exit
0000000000000004 l       *ABS*  0000000000000000 sys_write
0000000000000000 l    d  .section_a     0000000000000000 .section_a
0000000000000000 l    d  .section_b     0000000000000000 .section_b
0000000000000000 l    d  .section_c     0000000000000000 .section_c
0000000000000000 l    d  .section_d     0000000000000000 .section_d
0000000000000000 l    d  .section_e     0000000000000000 .section_e
0000000000000000 l       .data  0000000000000000 hello_lbl
0000000000000000 g       .text  0000000000000000 _start

A konečně si ukažme výpis obsahu jednotlivých sekcí. I zde nám pomůže nástroj objdump:

$ objdump -s sections2.o
 
sections2.o:     file format elf64-x86-64
 
Contents of section .text:
 0000 b8040000 00bb0100 0000b900 000000ba  ................
 0010 0d000000 cd80b801 000000bb 00000000  ................
 0020 cd80                                 ..
Contents of section .data:
 0000 48656c6c 6f20576f 726c6421 0a00      Hello World!..
Contents of section .section_a:
 0000 53454354 494f4e20 4100               SECTION A.
Contents of section .section_b:
 0000 53454354 494f4e20 4200               SECTION B.
Contents of section .section_c:
 0000 53454354 494f4e20 4300               SECTION C.
Contents of section .section_d:
 0000 53454354 494f4e20 4400               SECTION D.
Contents of section .section_e:
 0000 53454354 494f4e20 4500               SECTION E.
Poznámka: z výpisu to sice není příliš patrné, ale například SECTION A. je řetězec.

20. Odkazy na Internetu

  1. Objdump Command in Linux with Examples
    https://www.geeksforgeeks.org/objdump-command-in-linux-with-examples/
  2. Linux objdump Command Explained for Beginners (7 Examples)
    https://www.howtoforge.com/linux-objdump-command/
  3. objdump – Unix, Linux Command
    https://www.tutorialspoin­t.com/unix_commands/objdum­p.htm
  4. objdump (Wikipedia)
    https://en.wikipedia.org/wiki/Objdump
  5. Linux Objdump Command Examples (Disassemble a Binary File)
    https://www.thegeekstuff.com/2012/09/ob­jdump-examples/
  6. How to use the ObjDump tool with x86
    https://resources.infosecin­stitute.com/topic/how-to-use-the-objdump-tool-with-x86/
  7. 10+ objdump Command Examples in Linux
    https://www.sanfoundry.com/objdump-command-usage-examples-in-linux/
  8. objdump(1) – Linux manual page
    https://www.man7.org/linux/man-pages/man1/objdump.1.html
  9. Object file (Wikipedia)
    https://en.wikipedia.org/wi­ki/Object_file
  10. Executable and Linkable Format
    https://en.wikipedia.org/wi­ki/Executable_and_Linkable_For­mat
  11. Basic Linker Script Concepts
    https://www.zeuthen.desy.de/dv/do­cumentation/unixguide/infoh­tml/binutils/docs/ld/Basic-Script-Concepts.html#Basic-Script-Concepts
  12. virtual and physical addresses of sections in elf files
    https://stackoverflow.com/qu­estions/6218384/virtual-and-physical-addresses-of-sections-in-elf-files
  13. Executable and Linkable Format
    https://en.wikipedia.org/wi­ki/Executable_and_Linkable_For­mat
  14. ELF 101: a Linux executable walkthrough
    https://upload.wikimedia.or­g/wikipedia/commons/e/e4/EL­F_Executable_and_Linkable_For­mat_diagram_by_Ange_Alber­tini.png
  15. ELF (Executable and Linkable Format)
    https://wiki.osdev.org/ELF
  16. elf(5) — Linux manual page
    https://www.man7.org/linux/man-pages/man5/elf.5.html
  17. What does each column of objdump's Symbol table mean?
    https://stackoverflow.com/qu­estions/6666805/what-does-each-column-of-objdumps-symbol-table-mean
  18. Category:book:X86 Assembly
    https://en.wikibooks.org/wi­ki/Category:Book:X86_Assem­bly
  19. x86 Assembly/GNU assembly syntax
    https://en.wikibooks.org/wi­ki/X86_Assembly/GNU_assem­bly_syntax
  20. Symbol Table
    https://wiki.osdev.org/Symbol_Table

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.