Gcc Anda membuat PIE yang dapat dieksekusi secara default (alamat absolut 32-bit tidak lagi diizinkan di x86-64 Linux?).
Saya tidak yakin mengapa, tetapi ketika melakukan itu linker tidak secara otomatis menyelesaikan call puts
ke call [email protected]
. Masih ada puts
Entri PLT dihasilkan, tetapi call
tidak pergi ke sana.
Saat runtime, penaut dinamis mencoba menyelesaikan puts
langsung ke simbol libc dari nama itu dan perbaiki call rel32
. Tapi simbolnya berjarak lebih dari +-2^31, jadi kita mendapat peringatan tentang meluapnya R_X86_64_PC32
relokasi. 32 bit rendah dari alamat target benar, tetapi bit atas tidak. (Jadi call
Anda melompat ke alamat yang buruk).
Kode Anda berfungsi untuk saya jika saya membuat dengan gcc -no-pie -fno-pie call-lib.c libcall.o
. -no-pie
adalah bagian penting:ini adalah opsi penghubung. Perintah YASM Anda tidak perlu diubah.
Saat membuat executable yang bergantung pada posisi tradisional, linker mengubah puts
simbol untuk target panggilan ke [email protected]
untuk Anda, karena kami menautkan file dinamis yang dapat dieksekusi (alih-alih menautkan libc secara statis dengan gcc -static -fno-pie
, dalam hal ini call
bisa langsung ke fungsi libc.)
Bagaimanapun, inilah mengapa gcc memancarkan call [email protected]
(sintaks GAS) saat mengompilasi dengan -fpie
(default di desktop Anda, tetapi bukan default di https://godbolt.org/), tetapi hanya call puts
saat mengkompilasi dengan -fno-pie
.
Lihat Apa arti @plt di sini? untuk lebih lanjut tentang PLT, dan juga Maaf keadaan perpustakaan dinamis di Linux dari beberapa tahun yang lalu. (gcc -fno-plt
modern seperti salah satu ide di entri blog itu.)
BTW, prototipe yang lebih akurat/spesifik akan membuat gcc menghindari zeroing EAX sebelum memanggil foo
:
extern void foo();
dalam C berarti extern void foo(...);
Anda dapat mendeklarasikannya sebagai extern void foo(void);
, yaitu ()
berarti dalam C++. C++ tidak mengizinkan deklarasi fungsi yang membiarkan argumen tidak ditentukan.
peningkatan asm
Anda juga dapat memasukkan message
di section .rodata
(data hanya baca, ditautkan sebagai bagian dari segmen teks).
Anda tidak memerlukan bingkai tumpukan, hanya sesuatu untuk menyelaraskan tumpukan dengan 16 sebelum panggilan. Sebuah boneka push rax
akan melakukannya.
Atau kita dapat melakukan tail-call puts
dengan melompat untuk itu alih-alih memanggilnya, dengan posisi tumpukan yang sama seperti saat masuk ke fungsi ini. Ini bekerja dengan atau tanpa PIE. Cukup ganti call
dengan jmp
, selama RSP mengarah ke alamat pengirim Anda sendiri.
Jika Anda ingin membuat PIE dapat dieksekusi (atau pustaka bersama), Anda memiliki dua opsi
call puts wrt ..plt
- secara eksplisit menelepon melalui PLT.call [rel puts wrt ..got]
- secara eksplisit melakukan panggilan tidak langsung melalui entri GOT, seperti-fno-plt
gcc gaya kode-gen. (Menggunakan mode pengalamatan RIP-relatif untuk mencapai GOT, karenanyarel
kata kunci).
WRT =Dengan Hormat Kepada. Manual NASM mendokumentasikan wrt ..plt
, dan lihat juga bagian 7.9.3:simbol khusus dan WRT.
Biasanya Anda akan menggunakan default rel
di bagian atas file Anda sehingga Anda benar-benar dapat menggunakan call [puts wrt ..got]
dan masih mendapatkan mode pengalamatan RIP-relatif. Anda tidak dapat menggunakan mode pengalamatan absolut 32-bit dalam kode PIE atau PIC.
call [puts wrt ..got]
merakit ke panggilan tidak langsung memori menggunakan penunjuk fungsi yang tautan dinamis disimpan di GOT. (Pengikatan awal, bukan penautan dinamis lambat.)
Dokumen NASM ..got
untuk mendapatkan alamat variabel di bagian 9.2.3. Fungsi di pustaka (lainnya) identik:Anda mendapatkan penunjuk dari GOT alih-alih memanggil secara langsung, karena offset bukanlah konstanta waktu tautan dan mungkin tidak muat dalam 32 bit.
YASM juga menerima call [puts wrt ..GOTPCREL]
, seperti sintaks AT&T call *[email protected](%rip)
, tetapi NASM tidak.
; don't use BITS 64. You *want* an error if you try to assemble this into a 32-bit .o
default rel ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional
section .rodata ; .rodata is best for constants, not .data
message:
db 'foo() called', 0
section .text
global foo
foo:
sub rsp, 8 ; align the stack by 16
; PIE with PLT
lea rdi, [rel message] ; needed for PIE
call puts WRT ..plt ; tailcall puts
;or
; PIE with -fno-plt style code, skips the PLT indirection
lea rdi, [rel message]
call [rel puts wrt ..got]
;or
; non-PIE
mov edi, message ; more efficient, but only works in non-PIE / non-PIC
call puts ; linker will rewrite it into call [email protected]
add rsp,8 ; remove the padding
ret
Dalam posisi-bergantung dapat dieksekusi, Anda dapat menggunakan mov edi, message
alih-alih LEA yang relatif RIP. Ukuran kodenya lebih kecil dan dapat berjalan di lebih banyak port eksekusi pada sebagian besar CPU.
Dalam executable non-PIE, Anda juga dapat menggunakan call puts
atau jmp puts
dan biarkan linker menyelesaikannya, kecuali jika Anda menginginkan penautan dinamis gaya no-plt yang lebih efisien. Namun jika Anda memilih untuk menautkan libc secara statis, menurut saya ini adalah satu-satunya cara agar Anda mendapatkan jmp langsung ke fungsi libc.
(Menurut saya kemungkinan penautan statis untuk non-PIE adalah mengapa ld
bersedia membuat stub PLT secara otomatis untuk non-PIE, tetapi tidak untuk PIE atau pustaka bersama. Ini mengharuskan Anda untuk mengatakan apa yang Anda maksud saat menautkan objek bersama ELF.)
Jika Anda memang menggunakan call puts
dalam PIE (call rel32
), ini hanya dapat berfungsi jika Anda secara statis menautkan implementasi puts
yang tidak bergantung pada posisi ke dalam PIE Anda, jadi semuanya adalah satu yang dapat dieksekusi yang akan dimuat di alamat acak saat runtime (dengan mekanisme tautan dinamis biasa), tetapi tidak memiliki ketergantungan pada libc.so.6
0xe8
opcode diikuti oleh offset yang ditandatangani untuk diterapkan ke PC (yang telah maju ke instruksi berikutnya pada saat itu) untuk menghitung target cabang. Oleh karena itu objdump
menafsirkan target cabang sebagai 0x671
.
YASM merender nol karena kemungkinan telah menempatkan relokasi pada offset itu, yaitu cara meminta loader untuk mengisi offset yang benar untuk puts
selama pemuatan. Loader mengalami luapan saat menghitung relokasi, yang mungkin menunjukkan bahwa puts
berada pada offset yang lebih jauh dari panggilan Anda daripada yang dapat direpresentasikan dalam offset bertanda 32-bit. Karenanya loader gagal memperbaiki instruksi ini, dan Anda mengalami kerusakan.
66c: e8 00 00 00 00
menunjukkan alamat yang tidak berpenghuni. Jika Anda melihat tabel relokasi Anda, Anda akan melihat relokasi di 0x66d
. Tidak jarang assembler mengisi alamat/offset dengan relokasi karena semuanya nol.
Halaman ini menunjukkan bahwa YASM memiliki WRT
direktif yang dapat mengontrol penggunaan .got
, .plt
, dll.
Per S9.2.5 pada dokumentasi NASM, sepertinya Anda dapat menggunakan CALL puts WRT ..plt
(menganggap YASM memiliki sintaks yang sama).