Tutorial Bikin Linux Kernel Module Sering Gagal? Ini Rahasia Kbuild yang Nggak Pernah Dijelaskan Tutorial Jadul

Tutorial Bikin Linux Kernel Module Sering Gagal? Ini Rahasia Kbuild yang Nggak Pernah Dijelaskan Tutorial Jadul

Oleh Reggi, 19 Sep 2026

Kamu pasti pernah mengalami momen ini. Kamu mengikuti sebuah tutorial online, menyalin 20 baris kode C sederhana untuk membuat Hello World di Linux Kernel, lalu mengetik perintah make. Hasilnya? Layar terminal kamu mendadak dibanjiri warna merah berisi deretan pesan error yang sama sekali tidak kamu pahami. Mencari panduan tentang How to write a Linux kernel module that actually builds di internet sering kali membawa kita ke artikel jebakan betmen buatan tahun 2014 yang kodenya sudah usang dan tidak relevan lagi dengan arsitektur kernel modern.

Lucunya lagi, ketika kode 22 baris tadi akhirnya berhasil di-compile, kamu akan mendapati berkas .ko hasil build berukuran sangat membengkak, bisa mencapai 106 KB. Padahal, begitu kamu buang informasi debug di dalamnya menggunakan strip, ukurannya menyusut drastis menjadi cuma 4,8 KB. Artinya apa? Hampir 95 persen dari apa yang dihasilkan oleh sistem build kamu sebenarnya bukanlah kode program yang kamu tulis. Lantas, ke mana sisanya dan kenapa sistem kernel membuat mekanismenya serumit ini? Mari kita bongkar tuntas realitanya.

Kode Minimalis 22 Baris: Tebak Mana yang Beneran Bekerja?

Untuk membuktikan bagaimana sebuah Loadable Kernel Module (LKM) bekerja, mari kita lihat struktur kode C paling minimal di bawah ini:

c
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Chris Roy"); MODULE_DESCRIPTION("A minimal loadable kernel module"); MODULE_VERSION("0.1"); static int __init hello_init(void) { pr_info("hello: loaded, module at %pS\n", hello_init); return 0; } static void __exit hello_exit(void) { pr_info("hello: unloaded\n"); } module_init(hello_init); module_exit(hello_exit);

Secara kasat mata, kode ini terlihat sederhana. Namun, dari 22 baris di atas, hanya ada empat komponen yang beneran melakukan pekerjaan berat di tingkat sistem.

module_init dan module_exit adalah fondasi utamanya. Jangan cari fungsi main() di sini karena modul kernel tidak punya single entry point yang berjalan lalu berhenti. Modul kernel bekerja dengan cara menempelkan hook pada kejadian tertentu saat dipasang (insmod) dan dilepas (rmmod).

Makro __init memberi tahu kernel bahwa fungsi tersebut hanya dipanggil sekali saat booting atau loading, sehingga memori yang dipakai bisa langsung dibebaskan setelahnya. Itulah alasan kenapa kamu sering melihat pesan "Freeing unused kernel memory" pada boot log. Sementara itu, __exit menandakan fungsi tersebut hanya dibutuhkan jika modul kamu memang diizinkan untuk di-unload.

Deklarasi MODULE_LICENSE("GPL") sama sekali bukan urusan formalitas administratif belaka. Kernel memeriksa lisensi ini secara ketat saat proses loading. Jika kamu mengosongkan makro ini atau memasukkan lisensi non-GPL, kernel akan langsung mengunci akses modul kamu ke simbol-simbol krusial berlabel EXPORT_SYMBOL_GPL. Kernel bahkan akan menandai dirinya sendiri dalam kondisi tainted.

Terakhir, fungsi pr_info adalah bentuk modern dari printk(KERN_INFO ...). Fungsi ini tidak mencetak karakter ke terminal kamu, melainkan menulisnya ke kernel ring buffer. Ini jebakan klasik yang sering bikin pemula bingung karena terminal mereka tetap sepi saat modul dijalankan.

Makefile Kernel yang Aneh dan Rahasia Kbuild

Coba perhatikan berkas Makefile yang biasa dipakai untuk membuat modul kernel berikut:

makefile
obj-m += hello.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Kelihatannya seperti Makefile biasa, padahal sebenarnya bukan. Variabel obj-m bukanlah variabel Make buatan kamu, melainkan deklarasi khusus yang dibaca oleh kbuild, yaitu sistem build internal milik Linux Kernel.

Instruksi make -C akan memindahkan direktori eksekusi ke dalam folder kernel headers dan menjalankan sistem build resmi milik kernel di sana. Parameter M=$(PWD) berfungsi memberi tahu kernel, "Hei, kode sumber modul yang mau di-compile ada di folder luar sebelah sini." Jadi, Makefile yang kamu buat sebenarnya cuma wrapper tipis yang menyerahkan seluruh pekerjaan ke sistem build kernel.

Inilah penyebab utama kenapa proses compilation modul kernel sering gagal dengan pesan error yang aneh-aneh. Kamu tidak sedang mengompilasi kode terhadap headers biasa seperti libc. Kamu sedang menjalankan mesin kompilasi kernel pada berkas kamu sendiri dengan seluruh aturan dan flag ketat milik mereka.

Sebelum mulai melakukan eksperimen, pastikan paket headers kernel dan compiler sudah terpasang di sistem kamu.

bash
# Ubuntu / Debian sudo apt install build-essential linux-headers-$(uname -r) # Fedora sudo dnf install kernel-devel # Arch Linux sudo pacman -S linux-headers

Selalu verifikasi ketersediaan headers tersebut dengan memeriksa symlink di direktori sistem:

bash
ls -d /lib/modules/$(uname -r)/build

Jika jalur symlink ini putus atau tidak ditemukan, proses build dijamin langsung gagal total. Selain itu, fitur keamanan seperti Secure Boot dan Kernel Lockdown bisa menolak modul kamu untuk di-load meskipun proses kompilasi sukses 100%. Kamu bisa mengecek status lockdown menggunakan perintah berikut:

bash
cat /sys/kernel/security/lockdown

Apa yang Terjadi di Balik Layar Saat Kamu Ketik 'make'?

Jika kamu menjalankan perintah make dan mengamati output terminal dengan cermat, kamu akan melihat lima tahapan eksekusi yang terjadi secara berurutan:

text
CC [M] /path/to/hello.o MODPOST /path/to/Module.symvers CC [M] /path/to/hello.mod.o LD [M] /path/to/hello.ko BTF [M] /path/to/hello.ko

Dari lima langkah di atas, hanya langkah pertama (CC [M]) yang murni mengompilasi kode C kamu. Langkah yang paling krusial justru terjadi pada MODPOST.

Di tahap MODPOST, sistem akan memindai berkas object kamu untuk mencari simbol-simbol yang dipanggil tapi tidak didefinisikan di dalam kode. Simbol tersebut kemudian dicocokkan dengan tabel simbol tereksplorasi (exported symbols) milik kernel. Jika kamu memanggil fungsi yang tidak disediakan oleh kernel, proses build akan langsung dihentikan saat itu juga. MODPOST juga menghasilkan berkas perantara bernama hello.mod.c yang berisi meta-data perekat.

Langkah LD [M] kemudian menggabungkan object utama dengan berkas perekat tersebut menjadi satu kesatuan berkas .ko (Kernel Object).

Tahapan BuildNama Berkas / ProsesFungsi dan Output Utama
CC [M]hello.oMengompilasi berkas C utama menjadi object file.
MODPOSTModule.symversMemeriksa kompatibilitas simbol & membuat kode perekat (.mod.c).
CC [M]hello.mod.oMengompilasi kode perekat meta-data hasil MODPOST.
LD [M]hello.koLinker menggabungkan seluruh object menjadi berkas modul final.
BTF [M]Type informationMenempelkan data tipe untuk tracing (opsional, butuh vmlinux).

Jika diukur, ukuran berkas .ko saat awal di-build dan setelah dibersihkan (stripped) memiliki perbedaan angka yang sangat kontras:

bash
# Mengecek metadata ELF section .modinfo objcopy -O binary --only-section=.modinfo hello.ko /dev/stdout | tr '\0' '\n' # Membuang DWARF debug information cp hello.ko /tmp/ && strip --strip-debug /tmp/hello.ko && ls -l /tmp/hello.ko

Proses strip ini memangkas ukuran berkas dari 106 KB menjadi di bawah 5 KB. Sisa puluhan kilobyte yang hilang itu hanyalah data debug DWARF yang berguna untuk alat analisis seperti gdb saat terjadi kernel panic. Saat modul di-load ke memori runtime, kernel hanya akan memuat bagian utama berukuran kecil tersebut.

Hello World Kamu Ternyata Punya 3 Dependency Tersembunyi

Bahkan untuk modul sederhana yang cuma mencetak satu baris kalimat, kode kamu sebenarnya sudah bergantung pada infrastruktur besar yang dipaksakan oleh kernel. Mari kita periksa simbol apa saja yang dibutuhkan berkas object kamu menggunakan perintah nm:

bash
nm hello.o

Kamu akan melihat tiga simbol berstatus U (undefined), yang berarti simbol tersebut wajib disediakan oleh kernel saat modul dimasukkan:

  1. pr_info (atau printk): Fungsi standar untuk menulis pesan ke sistem log kernel.
  2. __fentry__: Hook pelacak fungsi (function tracing) yang disisipkan oleh compiler di setiap awal fungsi. Fitur ini memungkinkan alat seperti ftrace menginspeksi eksekusi kode tanpa perlu kompilasi ulang.
  3. __x86_return_thunk: Mitigasi celah keamanan Spectre yang menggantikan instruksi ret biasa agar terhindar dari eksekusi spekulatif yang berbahaya.

Dua dari tiga simbol di atas adalah aturan ketat infrastruktur yang diterapkan oleh sistem. MODPOST bertugas memastikan ketiga simbol tersebut benar-benar ada di kernel sebelum mengizinkan proses linking selesai.

Kenapa Kernel Nolak Modul Kamu? Sihir 'vermagic' dan Lockdown

Pernah mengalami situasi di mana modul sukses di-compile, tapi saat di-load muncul pesan Invalid module format? Penyebab utamanya terletak pada baris vermagic.

bash
modinfo ./hello.ko | grep vermagic # Output contoh: 5.15.0-190-generic SMP mod_unload modversions

Kernel Linux akan membandingkan string vermagic dari berkas .ko dengan string milik dirinya sendiri sebelum mengizinkan modul masuk. String ini mencatat versi rilis kernel, apakah kernel mendukung SMP (Symmetric Multiprocessing), apakah fitur pendorong pelepasan modul diaktifkan, hingga konfigurasi versi simbol (modversions).

Tidak ada jaminan stabilitas ABI (Application Binary Interface) di dalam internal Linux Kernel. Struktur data internal bisa berubah drastis antar rilis versi. Jika modul yang di-compile untuk struktur A dipaksakan berjalan di kernel struktur B, hasilnya adalah kerusakan memori (memory corruption). Penolakan load ini adalah cara kernel melindungi dirinya agar sistem tidak runtuh.

Hal ini juga yang menjadi alasan mendasar kenapa utilitas seperti DKMS (Dynamic Kernel Module Support) diciptakan. Perangkat lunak pihak ketiga seperti VirtualBox, ZFS, atau driver NVIDIA menggunakan DKMS untuk menyimpan kode sumber dan mengompilasi ulang modul kernel secara otomatis setiap kali kamu melakukan pembaruan versi kernel sistem.

Mengirim Parameter Saat Load Time

Modul yang perilakunya kaku tentu kurang berguna. Kamu bisa memanfaatkan makro module_param untuk membuka variabel agar nilainya dapat diubah saat modul dimuat.

c
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("A module that takes parameters"); static char *who = "world"; static int times = 1; module_param(who, charp, 0444); MODULE_PARM_DESC(who, "who to greet"); module_param(times, int, 0644); MODULE_PARM_DESC(times, "how many times to greet"); static int __init param_init(void) { int i; for (i = 0; i < times; i++) { pr_info("param: hello, %s\n", who); } return 0; } static void __exit param_exit(void) { pr_info("param: unloaded\n"); } module_init(param_init); module_exit(param_exit);

Izin file 0444 membuat parameter dapat dibaca melalui direktori /sys/module/<nama_modul>/parameters/. Jika kamu memberi izin 0644, pimpinan akses root bisa mengubah nilai parameter secara langsung saat modul sedang berjalan di memori. Namun, hati-hati karena mengubah nilai variabel hidup tanpa proteksi locking bisa memicu masalah kondisi balapan (race condition).

Kamu bisa memasukkan parameter tersebut secara langsung saat mengeksekusi insmod:

bash
sudo insmod ./param.ko who=kernel times=3

Untuk melihat pesan hasil eksekusi yang dikirim oleh pr_info, gunakan perintah dmesg atau journalctl:

bash
sudo dmesg | tail -n 10 # atau sudo journalctl -k | tail -n 10

Peringatan penting: Jika program userspace kamu mengalami bug, dampaknya paling jauh hanya membuat aplikasi tersebut crash. Tapi jika kode modul kernel kamu memiliki bug, seluruh sistem operasi bisa mengalami kernel panic seketika atau merusak struktur sistem berkas (filesystem). Selalu lakukan pengujian di dalam Mesin Virtual (Virtual Machine) sebelum memberanikan diri menjalankannya di mesin utama.

4 Error Paling Bikin Emosi dan Cara Ngerapihinnya

Berikut adalah empat jenis pesan kesalahan yang paling sering membuat pengembang frustrasi beserta akar masalah yang sebenarnya:

  1. No rule to make target 'modules'. Stop. Akar masalah: Berkas kernel headers belum terpasang atau jalur symlink di /lib/modules/$(uname -r)/build terputus. Solusi: Pasang paket headers yang cocok persis dengan rilis uname -r kamu.
  2. ERROR: modpost: "some_function" [hello.ko] undefined! Akar masalah: Kamu memanggil fungsi yang tidak diekspor oleh kernel, atau fungsi tersebut berlisensi GPL sedangkan modul kamu tidak mendeklarasikan MODULE_LICENSE("GPL"). Kamu bisa mengecek ketersediaan simbol di dalam kernel menggunakan perintah grep nama_fungsi /proc/kallsyms.
  3. insmod: ERROR: could not insert module: Invalid module format Akar masalah: String vermagic pada modul tidak cocok dengan kernel yang sedang berjalan. Lakukan clean dan kompilasi ulang menggunakan headers kernel yang aktif.
  4. insmod: ERROR: could not insert module: Operation not permitted Akar masalah: Penolakan dari mekanisme keamanan hardware atau kernel, seperti Secure Boot yang menolak modul tanpa tanda tangan digital (unsigned module) atau status lockdown dalam mode confidentiality.

Sebagai catatan tambahan, setiap kali kamu memuat modul buatan sendiri di luar pohon kode resmi (out-of-tree module), kernel akan menandai dirinya dengan status taint. Kamu bisa mengecek nilai bitmask penanda ini melalui berkas sistem:

bash
cat /proc/sys/kernel/tainted

Jika bernilai 4096, artinya bit ke-12 (TAINT_OOT_MODULE) telah aktif. Pengembang resmi kernel Linux biasanya akan meminta kamu mereproduksi ulang bug pada kernel yang bersih (untainted) sebelum mereka mau memeriksa laporan masalah kamu.

Kenapa Tutorial dari Tahun 2014 Pasti Bikin Pusing?

Jika kamu mencoba mengikuti panduan pembuatan modul dari artikel-artikel lama, hampir dipastikan prosesnya akan kandas. Penyebab utamanya adalah banyaknya perubahan standar API di internal kernel selama beberapa tahun terakhir:

  • Sintaks printk(KERN_INFO "...") jadul kini digantikan oleh pembungkus yang lebih bersih seperti pr_info(...).
  • Penggunaan nama fungsi konvensional init_module() dan cleanup_module() sudah ditinggalkan. Standar modern mewajibkan pendaftaran fungsi menggunakan makro module_init() dan module_exit().
  • Deklarasi MODULE_LICENSE yang dulunya bersifat opsional, kini menjadi komponen wajib yang mengontrol izin akses ke simbol-simbol penting.
  • Tipe data parameter makro module_param versi lama seperti byte atau short mengalami perombakan sintaks. Penulisan pointer karakter yang dulu dieja charp pada beberapa tutorial lama sering ditulis menggunakan format yang salah.
  • Jalur lokasi headers internal telah dipisah secara struktural. Penggunaan sertakan berkas seperti <linux/moduleparam.h> secara terpisah kini sudah tidak diperlukan lagi karena sudah tercakup secara otomatis di dalam <linux/module.h>.

Langkah Selanjutnya dan Tingkat Kekuasaan Kernel

Sekarang kamu sudah paham bagaimana cara membuat Linux Kernel Module yang beneran bisa di-build, membaca struktur internalnya, hingga membedah penyebab kegagalan proses kompilasinya.

Dari titik ini, kamu bisa mencoba melangkah lebih jauh. Kamu bisa bereksperimen dengan membuat virtual file di direktori /proc untuk membaca dan menulis data interaktif, atau memanfaatkan fitur tracing internal menggunakan ftrace tanpa perlu menginstal alat tambahan:

bash
sudo sh -c 'echo hello_init > /sys/kernel/tracing/set_ftrace_filter' sudo sh -c 'echo function > /sys/kernel/tracing/current_tracer' sudo cat /sys/kernel/tracing/trace

Satu hal mendasar yang perlu diingat: sebuah modul kernel yang berhasil dimuat akan berjalan dengan tingkat wewenang tertinggi yang setara dengan kernel itu sendiri. Ia bisa membaca seluruh area memori, mengganti instruksi fungsi apa pun, dan mengabaikan batasan sistem keamanan userspace. Begitu kode modul kamu dieksekusi, tidak ada lagi lapisan pelindung di atasnya yang bisa membatasi geraknya. Itulah alasan utama kenapa kernel Linux menjaga pintu masuknya dengan sangat ketat menggunakan fitur tanda tangan digital dan lockdown.

Referensi


Sedang Ramai Dibaca