Topik ini menempatkan assembly dalam peta pekerjaan nyata rekayasa perangkat lunak. Bagian awal menjelaskan kedudukan assembly sebagai notasi instruksi mesin, bagian tengah menelusuri jenis pekerjaan yang masih menuntut kemampuan membacanya, dan bagian akhir mengukur batasannya melalui satu demonstrasi disassembly terhadap program C. Setelah topik ini dibaca, keputusan mengenai kapan assembly digunakan dapat diambil berdasarkan alasan teknis, bukan kebiasaan.
Assembly sebagai notasi instruksi mesin#
Assembly adalah notasi teks yang dipetakan hampir satu banding satu ke instruksi mesin sebuah arsitektur prosesor (instruction set architecture). Setiap barisnya menyatakan satu instruksi yang benar-benar dijalankan prosesor, misalnya penyalinan data antar register atau penjumlahan dua nilai. Register adalah penyimpanan kecil berkecepatan tinggi di dalam prosesor. Penerjemah notasi ini disebut assembler, dan inilah yang membedakan assembly dari compiler bahasa tingkat tinggi.
C berada pada kedudukan yang berbeda. Satu baris C dapat menjadi puluhan instruksi setelah compiler melakukan pemilihan instruksi, penjadwalan, dan optimasi, sedangkan satu baris assembly tetap satu instruksi. Karena kesetaraan yang erat itu, assembly sering dianggap lapisan paling transparan untuk melihat perilaku prosesor, sekaligus lapisan yang paling detail untuk ditulis.
Perlu digarisbawahi bahwa assembly bukan satu bahasa tunggal. Setiap arsitektur prosesor memiliki notasinya sendiri, sehingga program x86-64 tidak terbaca oleh prosesor RISC-V dan sebaliknya. Diagram berikut memperlihatkan jenjang representasi dari kode sumber C sampai kode mesin beserta perkakas yang berpindah di antara jenjang-jenjang itu.
Pekerjaan yang masih menuntut assembly#
Kebutuhan nyata terhadap assembly kini jarang berbentuk penulisan seluruh aplikasi. Pekerjaan berikut paling sering menuntut kemampuan membaca dan sesekali menulisnya.
- Rutin performa kritis. Primitif kriptografi, inti perhitungan SIMD, dan potongan hot path yang ditunjuk profiler kerap ditulis atau diperbaiki langsung di level assembly karena di situ selisih beberapa instruksi terasa pada seluruh sistem.
- Debugging tanpa kode sumber. Apabila binary pihak ketiga berhenti secara paksa atau simbol debug tidak tersedia, debugger hanya menampilkan instruksi mesin dan register, sehingga kemampuan membaca assembly menjadi satu-satunya jalan untuk memahami keadaan program.
- Reverse engineering, yaitu pembedahan program tanpa kode sumbernya. Analisis malware, audit keamanan, dan tantangan keamanan digital menyelidiki program yang sumbernya tidak tersedia, sehingga hasil disassembly menjadi bahan kerja utamanya.
- Audit keluaran compiler. Untuk kode yang paling kritis, hasil kompilasi diperiksa instruksinya agar compiler benar-benar menghasilkan pemetaan dan penggunaan register yang diharapkan, khususnya pada perulangan panas.
Empat pekerjaan itu menuntut keahlian yang sama, yaitu membaca program dari sisi prosesor ketika tampilan kode sumber tidak ada atau tidak mencukupi. Jalur ini melatih kebiasaan membaca tersebut melalui debugger dan disassembly pada setiap konsep.
Mengamati compiler melalui disassembly#
Cara paling langsung untuk melihat pekerjaan compiler adalah melakukan disassembly pada hasil kompilasinya. Program C berikut digunakan sepanjang bagian ini dan hanya berisi fungsi penjumlahan sederhana beserta pemanggilnya.
#include <stdio.h>
long tambah(long a, long b) {
return a + b;
}
int main(void) {
printf("tambah(40, 2) = %ld\n", tambah(40, 2));
return 0;
}tambah(40, 2) = 42Kompilasi tanpa optimasi dengan opsi -O0 membuat compiler mempertahankan struktur sumber apa adanya. Perintah berikut mengompilasi program lalu menampilkan isi fungsi tambah dengan objdump.
gcc -O0 -o tambah tambah.c
objdump -d --no-show-raw-insn tambah | grep -A 9 '<tambah>:'0000000000001139 <tambah>:
1139: push %rbp
113a: mov %rsp,%rbp
113d: mov %rdi,-0x8(%rbp)
1141: mov %rsi,-0x10(%rbp)
1145: mov -0x8(%rbp),%rdx
1149: mov -0x10(%rbp),%rax
114d: add %rdx,%rax
1150: pop %rbp
1151: retFungsi tambah tampil sebagai sembilan instruksi. Dua instruksi pertama dan dua instruksi terakhir merupakan prolog dan epilog fungsi, yaitu pembentukan dan pembongkaran stack frame pada stack, area memori untuk data sementara pemanggilan fungsi. Argumen a dan b tiba pada register rdi dan rsi, disalin sementara ke memori, lalu dijumlahkan oleh add, dan hasilnya dikembalikan melalui rax. Nama register untuk sementara diterima apa adanya karena peran masing-masing akan sering muncul sepanjang jalur ini.
Kompilasi ulang dengan -O2 mengubah bentuknya sepenuhnya. Perintah berikut menempatkan hasilnya pada berkas keluaran yang berbeda agar kedua hasil tetap dapat dibandingkan.
gcc -O2 -o tambah-cepat tambah.c
objdump -d --no-show-raw-insn tambah-cepat | grep -A 2 '<tambah>:'0000000000001160 <tambah>:
1160: lea (%rdi,%rsi,1),%rax
1164: retSeluruh fungsi kini hanya dua instruksi. Instruksi lea menjumlahkan kedua argumen langsung di register tanpa menyentuh memori, kemudian ret mengembalikan hasilnya. Compiler mengenali bahwa penyimpanan sementara ke memori tidak diperlukan dan membuangnya. Transformasi semacam ini diterapkan compiler secara konsisten pada ribuan fungsi, dan inilah alasan kode tulisan tangan jarang mengalahkan hasil compiler untuk kasus umum.
Batasan assembly#
Batasan pertama adalah produktivitas. Satu baris assembly hanya mengerjakan satu instruksi, tidak ada tipe data, dan konvensi pemanggilan fungsi dijaga secara manual, sehingga program yang sama hampir selalu lebih panjang dan lebih rawan error daripada versi C-nya.
Batasan kedua adalah portabilitas. Program assembly terikat pada arsitektur prosesornya, sedangkan kode C cukup dikompilasi ulang untuk target baru. Rutin yang ditulis untuk x86-64 tidak berguna pada prosesor ARM tanpa penulisan ulang total.
Batasan ketiga bersifat teknis, yaitu untuk kode biasa compiler modern sering menghasilkan kode yang setara atau lebih baik. Demonstrasi sebelumnya memperlihatkan fungsi yang menyusut dari sembilan instruksi menjadi dua. Keunggulan tulisan tangan masih ada pada domain sempit yang sulit dikenali compiler, misalnya rutin kriptografi yang harus tahan terhadap serangan side channel, tetapi klaim semacam itu perlu dibuktikan dengan pengukuran, bukan asumsi.
Karena tiga batasan itu, penulisan assembly dalam pekerjaan nyata biasanya terbatas pada lapisan terbawah, seperti kode boot, potongan kernel, dan primitif kecil yang keunggulannya terukur jelas. Sisanya lebih efektif dikerjakan dalam bahasa tingkat tinggi dengan assembly sebagai alat baca.
Ringkasan#
- Assembly memetakan instruksi mesin hampir satu banding satu sehingga menjadi tampilan paling detail dari perilaku prosesor.
- Pekerjaan yang menuntut assembly adalah rutin performa kritis, debugging tanpa kode sumber, reverse engineering, dan audit keluaran compiler.
- Fungsi C yang sembilan instruksinya pada
-O0menyusut menjadi dua instruksi pada-O2merupakan contoh konkret keunggulan compiler modern. - Produktivitas dan portabilitas menjadi batasan utama, sehingga penulisan assembly manual terbatas pada domain yang keunggulannya terukur.
Referensi#
- objdump(1) — Linux man-pages — Perintah disassembly yang dipakai untuk mengamati hasil kompilasi.
- GNU Binutils documentation — Sourceware — Dokumentasi resmi binutils yang mencakup objdump, as, dan ld.
- Intel 64 and IA-32 Architectures Software Developer Manuals — Intel — Dokumen resmi arsitektur x86-64 yang menjadi rujukan instruksi pada jalur ini.