Begini caranya:
.file "test.c"
Nama file sumber asli (digunakan oleh debugger).
.section .rodata
.LC0:
.string "Hello world!"
String yang diakhiri dengan nol disertakan dalam bagian ".rodata" ("ro" berarti "hanya baca":aplikasi akan dapat membaca data, tetapi upaya apa pun untuk menulis ke dalamnya akan memicu pengecualian).
.text
Sekarang kita menulis sesuatu ke bagian ".text", yang merupakan tempat kode.
.globl main
.type main, @function
main:
Kami mendefinisikan fungsi yang disebut "utama" dan terlihat secara global (file objek lain akan dapat menjalankannya).
leal 4(%esp), %ecx
Kita simpan di register %ecx
nilai 4+%esp
(%esp
adalah penunjuk tumpukan).
andl $-16, %esp
%esp
sedikit dimodifikasi sehingga menjadi kelipatan 16. Untuk beberapa tipe data (format floating-point sesuai dengan double
C dan long double
), kinerja lebih baik ketika akses memori berada di alamat yang kelipatan 16. Ini tidak terlalu diperlukan di sini, tetapi bila digunakan tanpa bendera pengoptimalan (-O2
...), kompiler cenderung menghasilkan cukup banyak kode umum yang tidak berguna (yaitu kode yang mungkin berguna dalam beberapa kasus tetapi tidak di sini).
pushl -4(%ecx)
Yang ini agak aneh:pada saat itu, kata di alamat -4(%ecx)
adalah kata yang berada di atas tumpukan sebelum andl
. Kode mengambil kata itu (yang seharusnya menjadi alamat pengirim) dan mendorongnya lagi. Jenis ini mengemulasi apa yang akan diperoleh dengan panggilan dari fungsi yang memiliki tumpukan selaras 16 byte. Dugaan saya adalah push
ini adalah sisa dari urutan penyalinan argumen. Karena fungsi telah menyesuaikan penunjuk tumpukan, fungsi harus menyalin argumen fungsi, yang dapat diakses melalui nilai lama penunjuk tumpukan. Di sini, tidak ada argumen, kecuali alamat pengembalian fungsi. Perhatikan bahwa kata ini tidak akan digunakan (sekali lagi, ini adalah kode tanpa pengoptimalan).
pushl %ebp
movl %esp, %ebp
Ini adalah prolog fungsi standar:kita menyimpan %ebp
(karena kita akan memodifikasinya), lalu atur %ebp
untuk menunjuk ke bingkai tumpukan. Setelah itu, %ebp
akan digunakan untuk mengakses argumen fungsi, membuat %esp
bebas lagi. (Ya, tidak ada argumen, jadi ini tidak berguna untuk fungsi tersebut.)
pushl %ecx
Kami menyimpan %ecx
(kita akan membutuhkannya saat keluar dari fungsi, untuk mengembalikan %esp
pada nilai sebelum andl
).
subl $20, %esp
Kami mencadangkan 32 byte pada tumpukan (ingat bahwa tumpukan bertambah "turun"). Ruang itu akan digunakan untuk menyimpan argumen ke printf()
(itu berlebihan, karena ada satu argumen, yang akan menggunakan 4 byte [itu adalah pointer]).
movl $.LC0, (%esp)
call printf
Kami "mendorong" argumen ke printf()
(yakni kami memastikan bahwa %esp
menunjuk ke sebuah kata yang mengandung argumen, di sini $.LC0
, yang merupakan alamat string konstanta di bagian rodata). Kemudian kita panggil printf()
.
addl $20, %esp
Ketika printf()
kembali, kami menghapus ruang yang dialokasikan untuk argumen. addl
ini membatalkan apa yang subl
di atas.
popl %ecx
Kami memulihkan %ecx
(didorong ke atas); printf()
mungkin telah memodifikasinya (konvensi panggilan menjelaskan register mana yang dapat dimodifikasi oleh suatu fungsi tanpa memulihkannya saat keluar; %ecx
adalah salah satu register tersebut).
popl %ebp
Epilog fungsi:ini memulihkan %ebp
(sesuai dengan pushl %ebp
di atas).
leal -4(%ecx), %esp
Kami memulihkan %esp
ke nilai awalnya. Efek dari opcode ini adalah untuk menyimpan di %esp
nilai %ecx-4
. %ecx
diatur dalam opcode fungsi pertama. Ini membatalkan semua perubahan pada %esp
, termasuk andl
.
ret
Fungsi keluar.
.size main, .-main
Ini mengatur ukuran main()
fungsi:kapan saja selama perakitan, ".
" adalah alias untuk "alamat tempat kami menambahkan sesuatu saat ini". Jika instruksi lain ditambahkan di sini, itu akan menuju ke alamat yang ditentukan oleh ".
". Jadi, ".-main
", di sini, adalah ukuran yang tepat dari kode fungsi main()
. .size
direktif menginstruksikan assembler untuk menulis informasi itu di file objek.
.ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
GCC sangat suka meninggalkan jejak aksinya. String ini berakhir sebagai semacam komentar di file objek. Penaut akan menghapusnya.
.section .note.GNU-stack,"",@progbits
Bagian khusus tempat GCC menulis bahwa kode dapat mengakomodasi tumpukan yang tidak dapat dieksekusi. Ini adalah kasus normal. Tumpukan yang dapat dieksekusi diperlukan untuk beberapa penggunaan khusus (bukan standar C). Pada prosesor modern, kernel dapat membuat tumpukan yang tidak dapat dieksekusi (tumpukan yang memicu pengecualian jika seseorang mencoba mengeksekusi sebagai kode beberapa data yang ada di tumpukan); ini dipandang oleh sebagian orang sebagai "fitur keamanan" karena meletakkan kode pada stack adalah cara yang umum untuk mengeksploitasi buffer overflows. Dengan bagian ini, file yang dapat dieksekusi akan ditandai sebagai "kompatibel dengan tumpukan yang tidak dapat dieksekusi" yang akan dengan senang hati disediakan oleh kernel.
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
instruksi ini tidak dapat dibandingkan dengan program c Anda, instruksi ini selalu dijalankan di awal setiap fungsi (tetapi tergantung pada kompiler/platform)
movl $.LC0, (%esp)
call printf
blok ini sesuai dengan panggilan printf() Anda. instruksi pertama menempatkan argumennya pada tumpukan (penunjuk ke "hello world") lalu memanggil fungsi tersebut.
addl $20, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
instruksi ini berlawanan dengan blok pertama, mereka semacam barang manipulasi tumpukan. selalu dieksekusi juga
Berikut beberapa tambahan untuk @Thomas Pornin
jawabannya.
.LC0
konstanta lokal, misalnya string literal..LFB0
awal fungsi lokal,.LFE0
akhiran fungsi lokal,
Akhiran dari label ini adalah angka, dan dimulai dari 0.
Ini adalah konvensi assembler gcc.