Object file (file .o) adalah hasil tahap assembling — file biner berformat ELF yang berisi instruksi mesin, tetapi belum siap dijalankan karena alamat simbol masih perlu diselesaikan oleh linker. Memahami struktur object file penting karena pesan error seperti undefined reference to 'foo' atau multiple definition of 'bar' berasal dari tahap linking yang memeriksa simbol-simbol di file .o. Pada chapter ini kita akan membedah sebuah object file dengan file, nm, dan objdump untuk melihat section, symbol table, dan relocation entry.

Membuat Object File #

Kode C berikut memiliki dua fungsi global (tambah, cetak), satu fungsi static (kali), satu variabel global (global_counter), dan satu variabel static (internal_state):

c
#include <stdio.h>

int tambah(int a, int b) {
    return a + b;
}

static int kali(int a, int b) {
    return a * b;
}

int global_counter = 42;
static int internal_state = 7;

void cetak(void) {
    printf("counter=%d state=%d jumlah=%d\n",
           global_counter, internal_state, kali(2, 3));
}

Kompilasi menjadi object file dengan flag -c:

bash
gcc -std=c11 -Wall -Wextra -c math_ops.c -o math_ops.o
file math_ops.o

Output yang Dihasilkan:

text
math_ops.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Kata kunci relocatable adalah penanda bahwa file ini adalah object file — alamat-alamat di dalamnya belum absolut dan perlu direlokasi oleh linker. Kebalikannya, executable akan dilabeli executable, dan shared library shared object.

Symbol Table dengan nm #

Perintah nm menampilkan symbol table sebuah object file atau executable. Symbol table adalah daftar semua nama (fungsi, variabel) yang didefinisikan atau dirujuk di file tersebut:

bash
nm math_ops.o

Output yang Dihasilkan:

text
0000000000000027 T cetak
0000000000000000 D global_counter
0000000000000004 d internal_state
0000000000000014 t kali
                 U printf
0000000000000000 T tambah

Mari kita telaah baris-baris output nm di atas:

  1. Baris 0000000000000027 T cetak: simbol cetak adalah fungsi global (huruf besar T = text section, huruf besar = global/external) pada offset 0x27 di section .text.
  2. Baris 0000000000000000 D global_counter: simbol global_counter adalah variabel global terinisialisasi (huruf D = data section, huruf besar = global) pada offset 0x0 di section .data.
  3. Baris 0000000000000004 d internal_state: simbol internal_state adalah variabel static (huruf kecil d = local data section) pada offset 0x4.
  4. Baris 0000000000000014 t kali: simbol kali adalah fungsi static (huruf kecil t = local text). Karena static, kali tidak terlihat dari object file lain — inilah mekanisme file-scope encapsulation di C.
  5. Baris U printf: simbol printf berlabel U (undefined) — dirujuk tetapi tidak didefinisikan di file ini. Linker harus mencarinya di file lain (misal libc.so).
  6. Baris 0000000000000000 T tambah: fungsi global tambah pada offset 0x0 di section .text.

Section dengan objdump -h #

Object file ELF dibagi menjadi beberapa section. Setiap section menyimpan kategori data tertentu. Perintah objdump -h menampilkan daftar section:

bash
objdump -h math_ops.o

Output yang Dihasilkan:

text
math_ops.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000061  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  000000a4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  000000ac  2**0
                  ALLOC
  3 .rodata       0000001f  0000000000000000  0000000000000000  000000b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000020  0000000000000000  0000000000000000  000000cf  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000ef  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000078  0000000000000000  0000000000000000  000000f0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

Section penting yang perlu kita pahami:

  • .text (97 byte): instruksi mesin kode program. Sifatnya READONLY, CODE — tidak bisa ditulis saat runtime.
  • .data (8 byte): variabel global terinisialisasi (global_counter 4 byte + internal_state 4 byte = 8 byte). Sifatnya DATA — bisa dibaca dan ditulis.
  • .bss (0 byte): variabel global tidak terinisialisasi (misal int x; di luar fungsi). Ukuran 0 di file karena BSS dialokasikan saat runtime oleh OS, menghemat ukuran binary.
  • .rodata (31 byte): literal string format "counter=%d state=%d jumlah=%d\n". Sifatnya READONLY — menulis di section ini memicu segfault.
  • .eh_frame (120 byte): tabel exception handling untuk stack unwinding — dipakai oleh runtime saat melempar sinyal atau menjalankan longjmp.

Disassembly dengan objdump -d #

Perintah objdump -d melakukan disassembly pada section .text, menampilkan instruksi mesin beserta kode heksadesimalnya:

bash
objdump -d math_ops.o | head -25

Output yang Dihasilkan:

text
math_ops.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <tambah>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	89 7d fc             	mov    %edi,-0x4(%rbp)
   7:	89 75 f8             	mov    %esi,-0x8(%rbp)
   a:	8b 55 fc             	mov    -0x4(%rbp),%edx
   d:	8b 45 f8             	mov    -0x8(%rbp),%eax
  10:	01 d0                	add    %edx,%eax
  12:	5d                   	pop    %rbp
  13:	c3                   	ret

0000000000000014 <kali>:
  14:	55                   	push   %rbp
  15:	48 89 e5             	mov    %rsp,%rbp
  18:	89 7d fc             	mov    %edi,-0x4(%rbp)
  1b:	89 75 f8             	mov    -0x8(%rbp),%esi
  1e:	8b 45 fc             	mov    -0x4(%rbp),%eax
  21:	0f af 45 f8          	imul   -0x8(%rbp),%eax
  25:	5d                   	pop    %rbp
  26:	c3                   	ret

Perhatikan kolom paling kiri (0:, 1:, 4:, dst.) — angka-angka tersebut adalah offset byte di section .text, bukan alamat absolut. Karena ini adalah object file relocatable, alamat 0x0 hanyanya offset sementara yang akan direlokasi saat linking. Instruksi push %rbp; mov %rsp,%rbp adalah prolog fungsi standar System V AMD64 ABI, dan pop %rbp; ret adalah epilog.

Relocation dengan readelf -r #

Object file berisi relocation entry — catatan untuk linker tentang lokasi mana di kode mesin yang perlu diisi dengan alamat simbol saat linking. Perintah readelf -r menampilkan daftar tersebut:

bash
readelf -r math_ops.o

Output yang Dihasilkan:

text
Relocation section '.rela.text' at offset 0x2b8 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000003e  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
000000000044  000800000002 R_X86_64_PC32     0000000000000000 global_counter - 4
00000000004d  000600000002 R_X86_64_PC32     0000000000000000 .rodata - 4
00000000005a  000a00000004 R_X86_64_PLT32    0000000000000000 printf - 4

Relocation section '.rela.eh_frame' at offset 0x318 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
000000000040  000200000002 R_X86_64_PC32     0000000000000000 .text + 14
000000000060  000200000002 R_X86_64_PC32     0000000000000000 .text + 27

Mari kita telaah relocation utama di section .text:

  1. Baris pertama (0x3e → .data + 0): pada offset 0x3e di section .text (di dalam fungsi cetak), linker harus menulis alamat section .data (untuk membaca internal_state).
  2. Baris kedua (0x44 → global_counter - 4): pada offset 0x44, linker harus menulis alamat simbol global_counter minus 4 byte. Addend -4 muncul karena encoding instruksi mov 0x0(%rip),%edx menghitung alamat relatif terhadap akhir instruksi, bukan awalnya.
  3. Baris ketiga (0x4d → .rodata - 4): alamat format string di .rodata (untuk argumen pertama printf) akan diisi pada offset 0x4d.
  4. Baris keempat (0x5a → printf - 4): pemanggilan printf di-encode sebagai call printf@PLT. Tipe R_X86_64_PLT32 menandakan ini adalah panggilan melalui Procedure Linkage Table — mekanisme lazy binding untuk shared library.