1 poin oleh GN⁺ 2025-10-26 | 1 komentar | Bagikan ke WhatsApp
  • Sebelum program dijalankan, analisis teknis ini menelusuri proses kernel membuat dan menginisialisasi proses melalui system call execve
  • Panggilan ini meneruskan path berkas eksekusi, argumen, dan variabel lingkungan, lalu kernel berdasarkan itu memuat berkas eksekusi berformat ELF
  • Berkas ELF mencakup kode, data, simbol, informasi dynamic linking, dan lainnya, lalu kernel menafsirkannya untuk melakukan memory mapping dan inisialisasi stack
  • Setelah itu kernel menyerahkan kontrol ke entry point _start, dan fungsi main yang didefinisikan pengguna baru dipanggil setelah runtime tiap bahasa selesai diinisialisasi
  • Proses ini menunjukkan struktur kolaborasi antara sistem operasi, compiler, dan runtime, serta penting untuk memahami bagaimana eksekusi program berlangsung di level sistem

Titik awal eksekusi program: pemanggilan execve

  • Di Linux, eksekusi program dimulai melalui system call execve
    • Dalam bentuk execve(const char *filename, char *const argv[], char *const envp[]), ia meneruskan nama berkas eksekusi, daftar argumen, dan daftar variabel lingkungan
    • Melalui ini, kernel menentukan program apa yang akan dijalankan dan dalam lingkungan seperti apa
  • Dalam bahasa tingkat tinggi, panggilan ini dibungkus oleh API eksekusi proses milik standard library
    • Contoh: std::process::Command di Rust secara internal memanggil execve
    • Ia menjalankan proses yang mirip dengan pencarian PATH di shell, mengubah nama perintah menjadi path lengkap
  • Untuk skrip yang memiliki shebang (#!), kernel menjalankan program menggunakan interpreter yang ditentukan
    • Contoh: #!/usr/bin/python3 → dijalankan oleh interpreter Python

ELF: struktur berkas eksekusi

  • Berkas eksekusi di Linux mengikuti format ELF (Executable and Linkable Format)
    • ELF adalah format berkas eksekusi standar yang memuat kode, data, simbol, informasi relokasi, dan sebagainya
    • OS lain memakai format terpisah seperti Mach-O (macOS) dan PE (Windows)
  • Header ELF memuat informasi tentang struktur berkas dan tata letak memori
    • Contoh item: ELF Magic, Class, Entry point address, Program headers, Section headers
    • Entry point address adalah alamat instruksi yang pertama kali akan dijalankan program
  • Dalam contoh header ELF, berkas tersebut adalah berkas eksekusi ELF32 untuk arsitektur RISC-V, dengan alamat 0x10358 ditetapkan sebagai entry point

Komponen internal ELF

  • Berkas ELF tersusun dari beberapa section
    • .text: kode yang dieksekusi
    • .data: variabel global yang sudah diinisialisasi
    • .bss: variabel global yang belum diinisialisasi
    • .plt: tabel untuk pemanggilan shared library
    • .symtab, .strtab: tabel simbol dan string
  • PLT (Procedure Linkage Table) mendukung pemanggilan fungsi dari shared library
    • Contoh: printf, malloc dari libc
    • Section PT_INTERP pada ELF menentukan dynamic linker (interpreter)
  • Kernel membaca ELF untuk menempatkan section yang dapat dimuat ke memori, dan bila perlu menerapkan fitur keamanan seperti ASLR dan NX bit

Tabel simbol dan runtime link

  • Symbol table (symtab) pada ELF memuat informasi alamat fungsi dan variabel
    • Contoh: ada entri seperti _start, main, __libc_start_main
    • Bahkan program “Hello, World!” sederhana pun bisa memuat lebih dari 2300 simbol
  • Sebagian besar berasal dari standard library dan kode inisialisasi runtime
    • Karena implementasi libc seperti musl atau glibc ikut terhubung
  • Setelah memuat setiap section ELF, kernel menyerahkan kontrol ke interpreter (dynamic linker)
    • Interpreter menangani relocation, address randomization (ASLR), pengaturan hak eksekusi (NX bit), dan sebagainya

Proses inisialisasi stack

  • Sebelum program dijalankan, kernel harus menyusun stack secara langsung
    • Stack digunakan untuk variabel lokal, frame pemanggilan fungsi, penerusan argumen, dan lain-lain
  • argv, envp yang diteruskan saat pemanggilan execve disimpan di stack
    • Program mengakses argumen command line dan variabel lingkungan melalui ini
  • Kernel juga menyertakan auxiliary vector ELF (auxv) di stack
    • Berisi sekitar 30 item seperti ukuran halaman, metadata ELF, dan informasi sistem
    • Contoh: AT_PAGESZ menentukan ukuran halaman memori, misalnya 4KiB
  • Dalam contoh emulator RISC-V, stack pointer (sp) dimulai dari alamat tinggi lalu argumen, variabel lingkungan, dan auxiliary vector ditumpuk dalam urutan terbalik

Entry point dan fungsi _start

  • Entry point ELF ditetapkan ke alamat fungsi _start
    • _start adalah kode ruang pengguna pertama yang menerima kontrol dari kernel
  • Sebagian besar bahasa melakukan inisialisasi runtime di _start lalu memanggil main
    • Contoh: std::rt::lang_start di Rust, __libc_start_main di C
  • Dalam contoh Rust, atribut #![no_std], #![no_main] memungkinkan pendefinisian _start secara langsung tanpa runtime
    • Di dalam _start, argc, argv, envp dibaca dari stack lalu pointer main dipanggil
  • Runtime tiap bahasa menjalankan pekerjaan inisialisasi khusus bahasa seperti konstruktor global, thread-local storage, dan exception handling

Alur lengkap hingga pemanggilan main()

  • Seluruh proses dapat diringkas sebagai berikut
    1. Pemanggilan execve → kernel memuat berkas ELF
    2. Interpretasi ELF → pemetaan section kode/data, penentuan interpreter
    3. Penyusunan stack → penyimpanan argumen, variabel lingkungan, auxiliary vector
    4. Eksekusi entry point _start
    5. Setelah inisialisasi runtime, main() dipanggil
  • Rangkaian proses ini menunjukkan struktur kolaborasi antara kernel sistem operasi, format ELF, dan runtime bahasa
  • Kernel Linux yang sesungguhnya juga mencakup logika internal tambahan seperti address space, process table, dan manajemen grup, tetapi tulisan ini menjelaskan alur inti pada tahap sebelumnya

Kesimpulan dan koreksi

  • Proses eksekusi sebelum main() adalah gabungan antara inisialisasi level kernel dan penyiapan runtime
  • Bahkan program “Hello, World!” yang sederhana pun dijalankan setelah melewati struktur ELF yang kompleks dan inisialisasi runtime
  • Pada versi awal tulisan, sebagian logika pemuatan section dikaitkan ke kernel, tetapi kemudian dikoreksi bahwa itu sebenarnya peran interpreter ELF
  • Analisis ini berguna sebagai bahan dasar untuk memahami system programming, compiler, dan arsitektur OS

1 komentar

 
GN⁺ 2025-10-26
Komentar Hacker News
  • Menjelaskan proses dynamic linking pada berkas ELF
    Kernel memetakan segmen PT_LOAD dari ELF, memuat dynamic linker (ld.so) yang ditentukan oleh PT_INTERP, lalu menyerahkan kendali kepadanya
    Setelah itu dynamic linker melakukan relocation pada dirinya sendiri dan memuat shared object yang diperlukan dengan mmap/mprotect
    Struktur ini dianalogikan mirip dengan mekanisme shebang(#!) pada skrip

    • Kernel sama sekali tidak peduli pada informasi section, dan hanya menangani segmen PT_LOAD
      Ia membagikan pengalaman pernah bingung saat mencoba menyisipkan berkas arbitrer ke ELF dengan objcopy tetapi kernel tidak memuatnya
      Pada akhirnya ia membuat sendiri alat patch tabel program header, dan katanya fitur ini juga ditambahkan ke linker mold
      Tulisan terkait: Self-contained Lone Lisp Applications
    • Penulis mengakui sebelumnya sempat mengunggah isi yang salah edit dan berkata akan memperbaikinya
    • Ia mengatakan selalu penasaran mengapa di Linux tidak ada lebih banyak loader yang beragam, padahal loader berjalan di user space
  • Mengatakan pernah bereksperimen mengemas seluruh kode sebelum main() atau tanpa main()
    Tulisan terkait: Packing a codebase into a single function

    • Setelah dibaca, ternyata menarik karena lebih sederhana dari dugaan dan tidak rapuh
      Ia bercanda bahwa semua fungsi tinggal diubah menjadi bentuk main(100+n, ...)
  • Jika tertarik pada topik ini, ia menyarankan melihat cpu.land yang ia buat
    Isinya membahas multitasking dan proses pemuatan kode, bukan layout memori

    • Ada yang menyampaikan terima kasih karena sangat menyukai cpu.land
  • Ia penasaran seberapa banyak proyek C yang menghindari standard library dan hanya memanggil Linux syscall secara langsung
    Menurutnya menulis kode seperti ini jauh lebih menyenangkan

    • Ada yang berpendapat bahwa memakai syscall langsung justru tidak efisien
      Fitur seperti ALSA dan DRM punya banyak keuntungan jika diakses lewat system library, bukan syscall kernel
      Dijelaskan bahwa pendekatan ini lebih baik dari pendekatan ala Windows dalam hal portabilitas dan kemudahan pemeliharaan
    • Ditambahkan bahwa di Windows, jika hanya memakai Win32 API maka tidak perlu me-link C runtime
    • Ia juga mengatakan dulu pernah membuat proyek liblinux untuk menulis program hanya dengan syscall
      Sekarang proyek itu dihentikan karena header nolibc milik Linux sudah bagus,
      tetapi saat ini ia sedang mengembangkan bahasa interpreter Lisp berbasis syscall
      Katanya ini perjalanan yang sangat menarik sebagai eksperimen membangun Linux user space langsung dari system call
    • Ia ingin tetap menjaga portabilitas, tetapi file descriptor terlalu praktis untuk ditinggalkan
    • Ditambahkan bahwa banyak kode driver memang benar-benar hanya memakai syscall
  • Dijelaskan bahwa interpreter ELF (ld.so) menangani seluruh proses pemuatan setelah memetakan segmen ELF awal
    execve memetakan segmen PT_LOAD dan mengisi aux vector di stack,
    lalu melompat ke entry point milik interpreter ELF
    Kernel tidak mengetahui apa pun tentang PLT/GOT

  • Sebagai orang yang mengajar topik ini di universitas, ia mengatakan mahasiswa sering bingung karena diagram memori
    Buku teks menggambar alamat yang lebih tinggi di bagian atas, tetapi pada proses Linux nyata,
    alamat rendah ada di atas dan alamat tinggi ada di bawah
    Jika melihat /proc/<pid>/maps, semakin menggulir ke bawah maka alamat makin besar
    Jadi, ungkapan “heap tumbuh ke atas (stack tumbuh ke bawah)” hanyalah arah secara numerik,
    sedangkan secara visual justru kebalikannya
    Ia mengusulkan agar digambar seperti IDE, yaitu alamat makin besar saat turun ke bawah, karena jauh lebih intuitif

    • Stack tetap memang tumbuh dengan stack pointer berkurang, jadi ungkapan “tumbuh ke bawah” masih benar
      Hanya saja, ia mengusulkan visualisasinya lebih alami jika dibuat secara horizontal
    • Ada yang mengenang bahwa dulu ia juga mengalami kebingungan yang sama, dan penulisan alamat little-endian juga membingungkan
    • Ada pula yang membantah bahwa jika memikirkan arah tumpukan benda nyata, ungkapan “stack tumbuh ke bawah” memang tidak intuitif
  • Ia mengatakan suka melakukan eksperimen seperti ini dengan mikrokontroler PIC16 lama
    Menurutnya menyenangkan bisa menangani sendiri stack pointer, timer, pengaturan variabel, dan sebagainya

  • Ada yang membagikan pengalaman terkait shebang(#!)
    Sebuah aplikasi Java menampilkan error bahwa skrip eksekusi tidak ditemukan,
    tetapi masalah sebenarnya adalah path shebang pada skrip yang salah
    Di lokal skrip itu berjalan baik, tetapi di server remote path interpreter-nya berbeda sehingga menimbulkan masalah

    • Ia menambahkan bahwa ini bukan masalah khusus Java, melainkan bisa terjadi pada semua program yang menghasilkan error ENOENT
      Ia menyarankan menjalankannya dengan strace agar bisa langsung melihat syscall mana yang memunculkan error
    • Ia membagikan tulisan yang menganalisis struktur shebang: What the #! means
    • Ditambahkan bahwa agar kernel mendukung shebang, perlu pengaturan CONFIG_BINFMT_SCRIPT=y
  • Saat debugging, ia mengatakan selalu bingung kapan urutan relocation pada biner utama diterapkan
    Apakah sebelum atau sesudah linker menyelesaikan simbolnya sendiri terasa seperti ilmu hitam

  • Ditunjukkan bahwa tautan pada bagian “lang_start function (defined here)” di dalam Markdown rusak