Menulis assembly sendiri melatih penguasaan instruksi, tetapi pekerjaan analisis pada umumnya dimulai dari assembly yang dihasilkan compiler atau dari executable yang sudah jadi. Topik ini membahas dua perkakas untuk keperluan tersebut, yaitu objdump yang membongkar kode mesin menjadi teks assembly pada komputer lokal dan Compiler Explorer yang melakukan hal yang sama secara daring. Pembahasan mencakup cara membaca keluaran objdump pada executable maupun berkas objek, opsi objdump yang paling sering digunakan, alur kerja Compiler Explorer, serta perbandingan keluaran compiler untuk satu fungsi kecil pada dua level optimasi.
Membaca executable dengan objdump#
objdump adalah disassembler dari paket binutils, yaitu perkakas yang membaca kode mesin lalu menampilkannya kembali sebagai teks assembly. Opsi -d membongkar seluruh section yang berisi kode, sedangkan opsi -M intel memilih notasi Intel sehingga keluarannya sejalan dengan notasi yang digunakan sepanjang jalur ini. Program kecil berikut menjadi bahan latihan untuk bagian ini.
; jumlah.asm
; Menjumlahkan dua konstanta lalu mengakhiri proses dengan hasilnya.
default rel
section .text
global _start
_start:
mov eax, 20
add eax, 22
mov edi, eax
mov eax, 60
syscallProgram di atas menjumlahkan dua konstanta, yaitu 20 dan 22, lalu mengakhiri proses dengan hasil penjumlahannya sebagai kode keluar. Setelah dirakit, ditautkan, dan dijalankan, kode keluar 42 pada keluaran berikut mengonfirmasi bahwa penjumlahan terjadi sebagaimana dimaksud.
nasm -f elf64 jumlah.asm -o jumlah.o
ld jumlah.o -o jumlah
./jumlah; echo "exit=$?"exit=42Perintah berikut membongkar executable yang baru dibangun dengan notasi Intel.
objdump -d -M intel jumlah
jumlah: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <_start>:
401000: b8 14 00 00 00 mov eax,0x14
401005: 83 c0 16 add eax,0x16
401008: 89 c7 mov edi,eax
40100a: b8 3c 00 00 00 mov eax,0x3c
40100f: 0f 05 syscallSetiap baris keluaran tersusun atas tiga bagian, yaitu alamat memori instruksi, byte kode mesin yang benar-benar tersimpan, dan mnemonic beserta operandnya. Pada baris pertama, byte b8 14 00 00 00 adalah instruksi mov eax,0x14, dan 0x14 merupakan nilai heksadesimal dari 20. Baris berikutnya menambahkan 0x16, yaitu 22, sehingga eax bernilai 42 ketika instruksi syscall dijalankan. Apabila kolom alamat, byte, dan mnemonic dibaca secara berdampingan, hubungan antara teks assembly dan representasi mesinnya terlihat tanpa alat tambahan.
Berkas objek dan opsi penyesuaian#
objdump juga dapat membongkar berkas objek, yaitu hasil perakitan yang alamatnya belum diselesaikan oleh linker. Perintah berikut membongkar jumlah.o dalam notasi AT&T bawaan, kemudian menampilkan isi section .text dengan opsi -s.
objdump -d jumlah.o
objdump -s -j .text jumlah
jumlah.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
0: b8 14 00 00 00 mov $0x14,%eax
5: 83 c0 16 add $0x16,%eax
8: 89 c7 mov %eax,%edi
a: b8 3c 00 00 00 mov $0x3c,%eax
f: 0f 05 syscall
jumlah: file format elf64-x86-64
Contents of section .text:
401000 b8140000 0083c016 89c7b83c 0000000f ...........<....
401010 05 . Alamat pada keluaran pertama dimulai dari nol karena berkas objek belum memuat alamat final, dan pemetaan alamat baru ditetapkan saat penautan. Notasi AT&T pada keluaran itu menempatkan operan sumber sebelum operan tujuannya, sehingga mov $0x14,%eax adalah instruksi yang sama dengan mov eax,0x14 pada notasi Intel. Perbandingan dua keluaran ini memperlihatkan bahwa objdump hanya mengubah cara menampilkan byte yang sama, bukan isi programnya.
Opsi lain yang sering digunakan adalah --disassemble=nama, yang membatasi bongkaran hanya pada simbol yang disebut. Opsi ini tersedia sejak binutils 2.32 dan berguna pada executable besar yang berisi ratusan fungsi, sebab keluarannya hanya memuat fungsi yang diminta. Opsi -s bekerja secara berbeda karena tidak membongkar instruksi, melainkan menampilkan isi section dalam heksadesimal beserta bentuk teksnya pada kolom kanan, sebagaimana terlihat pada byte b8140000 di keluaran di atas.
Compiler Explorer#
Compiler Explorer adalah situs daring di godbolt.org yang mengompilasi kode dan menampilkan assembly-nya berdampingan dengan kode sumbernya. Alur penggunaannya berurutan, yaitu memilih bahasa dan compiler pada menu bagian atas, misalnya x86-64 gcc, kemudian menulis kode pada panel kiri sehingga panel kanan langsung menampilkan assembly yang dihasilkan compiler pilihan itu. Saat kursor diarahkan ke satu baris sumber, baris assembly yang sesuai ditandai dengan warna yang sama, sehingga hubungan keduanya dapat dilacak. Flag compiler dapat diatur, beberapa compiler dapat dijalankan berdampingan untuk membandingkan keluarannya, dan hasil percobaan dapat dibagikan melalui tautan permanen. Karena situs ini merupakan layanan daring, contoh Compiler Explorer tidak dieksekusi di sandbox dan digantikan dengan objdump lokal pada program setara.
Berkas berikut adalah contoh fungsi kecil yang dapat ditempelkan ke Compiler Explorer untuk mengamati assembly hasil gcc.
#include <stdio.h>
int kuadrat(int x) {
return x * x;
}
int main(void) {
printf("%d\n", kuadrat(7));
return 0;
}49Perintah berikut melakukan hal yang sama secara lokal, yaitu mengompilasi berkas itu pada level -O0 dan -O2, menjalankan keduanya, lalu membongkar fungsi kuadrat dari masing-masing executable. Penyaring sed hanya membuang judul section dan baris kosong agar keluarannya pendek.
gcc -O0 kuadrat.c -o kuadrat-o0
gcc -O2 kuadrat.c -o kuadrat-o2
./kuadrat-o0
./kuadrat-o2
# buang judul section dan baris kosong agar tersisa fungsi kuadrat
objdump -d -M intel --disassemble=kuadrat kuadrat-o0 | sed '/Disassembly of section/d;/^$/d'
objdump -d -M intel --disassemble=kuadrat kuadrat-o2 | sed '/Disassembly of section/d;/^$/d'49
49
kuadrat-o0: file format elf64-x86-64
0000000000001139 <kuadrat>:
1139: 55 push rbp
113a: 48 89 e5 mov rbp,rsp
113d: 89 7d fc mov DWORD PTR [rbp-0x4],edi
1140: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
1143: 0f af c0 imul eax,eax
1146: 5d pop rbp
1147: c3 ret
kuadrat-o2: file format elf64-x86-64
0000000000001160 <kuadrat>:
1160: 0f af ff imul edi,edi
1163: 89 f8 mov eax,edi
1165: c3 retKedua program mencetak 49, tetapi assembly-nya berbeda jauh. Pada -O0, gcc menyusun kerangka stack lengkap dengan push rbp dan mov rbp,rsp, menyalin argumen ke stack, mengambilnya kembali ke eax, lalu mengalikannya melalui instruksi imul. Pada -O2, seluruh kerangka itu hilang dan tersisa tiga instruksi, yaitu imul edi,edi yang mengalikan argumen dengan dirinya sendiri, mov eax,edi yang menaruh hasil pada register balikan, dan ret. Pada fungsi main, gcc bahkan melipat perhitungan kuadrat saat kompilasi sehingga printf dipanggil dengan konstanta 49 yang sudah jadi. Perbedaan inilah yang membuat assembly hasil -O2 menuntut kebiasaan baca yang berbeda daripada assembly hasil -O0.
Kesalahan umum#
- Keluaran AT&T dibaca dengan kebiasaan Intel. Penyebabnya adalah opsi
-M intelyang ditiadakan, dan akibatnya operand terbaca terbalik, misalnyamov $0x14,%eaxdianggap memindahkaneaxke konstanta. Perbaikannya adalah menambahkan-M intelpada setiap perintah objdump yang dijalankan. - Alamat pada berkas objek dianggap alamat final. Nilai
0dan urutan alamat pada bongkaranjumlah.obergeser setelah penautan, sehingga alamat tersebut tidak boleh digunakan untuk breakpoint atau perhitungan manual. Perbaikannya adalah membongkar executable yang sudah ditautkan setiap kali alamat nyata dibutuhkan. --disassemble=namatidak menghasilkan instruksi apa pun. Penyebabnya adalah nama simbol yang salah tulis atau executable yang telah di-strip sehingga tabel simbolnya hilang. Nama yang benar diperiksa lebih dahulu melaluiobjdump -tataunm.
Ringkasan#
- objdump dengan opsi
-dmembongkar kode mesin executable maupun berkas objek menjadi teks assembly, dan-M intelmenampilkan notasi yang sejalan dengan jalur ini. - Alamat pada berkas objek belum final, sedangkan opsi
--disassemble=namamembatasi bongkaran pada satu simbol dan-smenampilkan isi section dalam heksadesimal. - Compiler Explorer menyediakan jalur daring yang sama, yaitu kode sumber ditulis langsung dan assembly hasil compiler nyata dibaca berdampingan dengan sumbernya.
- Level optimasi sangat memengaruhi bentuk assembly, karena
-O0mempertahankan kerangka stack sedangkan-O2menghilangkannya dan melipat perhitungan konstanta.
Referensi#
- objdump — GNU Binutils — Dokumentasi opsi objdump yang dipakai pada topik ini.
- Compiler Explorer — godbolt.org — Situs daring untuk menulis kode kecil dan membaca assembly yang dihasilkan compiler nyata.
- Optimize Options — GNU Compiler Collection
— Penjelasan level optimasi
-O0hingga-O3yang memengaruhi bentuk assembly.