Vaše gcc ve výchozím nastavení vytváří spustitelné soubory PIE (32bitové absolutní adresy již nejsou v x86-64 Linuxu povoleny?).
Nejsem si jistý proč, ale když to uděláte, linker automaticky nevyřeší call puts
do call [email protected]
. Stále existuje puts
Vygenerován záznam PLT, ale call
tam nejde.
Za běhu se dynamický linker pokusí vyřešit puts
přímo na symbol libc tohoto jména a opravte call rel32
. Symbol je však vzdálen více než +-2^31, takže dostáváme varování před přetečením R_X86_64_PC32
přemístění. Nízkých 32 bitů cílové adresy je správných, ale horních bitů nikoliv. (Takže vaše call
skočí na špatnou adresu).
Váš kód mi funguje, pokud sestavuji s gcc -no-pie -fno-pie call-lib.c libcall.o
. -no-pie
je kritická část:je to možnost linkeru. Váš příkaz YASM se nemusí měnit.
Při vytváření tradičního spustitelného souboru závislého na poloze linker otočí puts
symbol pro cíl volání do [email protected]
pro vás, protože propojujeme dynamický spustitelný soubor (místo statického propojení knihovny libc s gcc -static -fno-pie
, v takovém případě call
mohl jít přímo na funkci libc.)
Každopádně to je důvod, proč gcc vysílá call [email protected]
(syntaxe GAS) při kompilaci s -fpie
(výchozí na ploše, ale ne výchozí na https://godbolt.org/), ale jen call puts
při kompilaci s -fno-pie
.
Viz Co znamená @plt zde? pro více informací o PLT a také Omlouváme se za stav dynamických knihoven v Linuxu před několika lety. (Moderní gcc -fno-plt
je jako jeden z nápadů v tom blogovém příspěvku.)
BTW, přesnější/specifičtější prototyp by umožnil gcc vyhnout se vynulování EAX před voláním foo
:
extern void foo();
v C znamená extern void foo(...);
Můžete to deklarovat jako extern void foo(void);
, což je ()
znamená v C++. C++ nepovoluje deklarace funkcí, které ponechávají argumenty nespecifikované.
vylepšení asm
Můžete také zadat message
v section .rodata
(data pouze pro čtení, propojená jako součást textového segmentu).
Nepotřebujete rám zásobníku, jen něco, co zarovná zásobník o 16 před voláním. Figurína push rax
udělá to.
Nebo můžeme zavolat puts
skákáním místo volání, se stejnou pozicí zásobníku jako při vstupu do této funkce. Toto funguje s nebo bez PIE. Stačí nahradit call
s jmp
, pokud RSP ukazuje na vaši vlastní zpáteční adresu.
Pokud chcete vytvořit spustitelné soubory PIE (nebo sdílené knihovny), máte dvě možnosti
call puts wrt ..plt
- explicitně volat přes PLT.call [rel puts wrt ..got]
- explicitně proveďte nepřímé volání prostřednictvím položky GOT, jako je-fno-plt
gcc styl code-gen. (Použití režimu adresování relativního RIP k dosažení GOT, protorel
klíčové slovo).
WRT =S úctou. Návod NASM dokumentuje wrt ..plt
a viz také část 7.9.3:Speciální symboly a WRT.
Normálně byste použili default rel
v horní části souboru, takže můžete skutečně použít call [puts wrt ..got]
a přesto získat režim adresování relativní k RIP. V kódu PIE nebo PIC nemůžete použít 32bitový režim absolutního adresování.
call [puts wrt ..got]
sestaví do paměti nepřímého volání pomocí ukazatele funkce, které dynamické propojení je uloženo v GOT. (Včasná vazba, ne líné dynamické propojení.)
Dokumenty NASM ..got
pro získání adresy proměnných v sekci 9.2.3. Funkce v (ostatních) knihovnách jsou identické:namísto přímého volání získáte ukazatel z GOT, protože offset není konstanta linkového času a nemusí se vejít do 32bitů.
YASM také přijímá call [puts wrt ..GOTPCREL]
, jako je syntaxe AT&T call *[email protected](%rip)
, ale NASM nikoli.
; 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
V pozici-závislá spustitelný, můžete použít mov edi, message
místo RIP-relativní LEA. Má menší velikost kódu a může běžet na více prováděcích portech na většině CPU.
Ve spustitelném souboru bez PIE můžete také použít call puts
nebo jmp puts
a nechte linker, aby to vyřešil, pokud nechcete efektivnější dynamické propojení ve stylu no-plt. Ale pokud se rozhodnete pro statické propojení libc, myslím, že toto je jediný způsob, jak získáte přímý jmp na funkci libc.
(Myslím, že možnost statického propojení pro non-PIE je proč ld
je ochoten automaticky generovat PLT stub pro non-PIE, ale ne pro PIE nebo sdílené knihovny. Vyžaduje, abyste řekli, co máte na mysli, když propojujete sdílené objekty ELF.)
Pokud jste použili call puts
v PIE (call rel32
), mohlo by to fungovat, pouze pokud byste staticky propojili implementaci puts
nezávislou na pozici do vašeho PIE, takže celá věc byl jeden spustitelný soubor, který se za běhu načetl na náhodnou adresu (obvyklým mechanismem dynamického linkeru), ale jednoduše nebyl závislý na libc.so.6
0xe8
po operačním kódu následuje podepsaný offset, který se aplikuje na PC (který do té doby postoupil k další instrukci) pro výpočet cíle větve. Proto objdump
interpretuje cíl větve jako 0x671
.
YASM vykresluje nuly, protože pravděpodobně přemístil tento offset, což je způsob, jakým žádá zavaděč, aby naplnil správný offset pro puts
během načítání. Zavaděč při výpočtu přemístění naráží na přetečení, což může znamenat, že puts
je ve větším offsetu od vašeho hovoru, než může být reprezentováno 32bitovým offsetem se znaménkem. Zavaděč proto tuto instrukci neopraví a dojde k havárii.
66c: e8 00 00 00 00
zobrazuje neobsazenou adresu. Pokud se podíváte do tabulky přemístění, měli byste vidět přemístění na 0x66d
. Není neobvyklé, že assembler vyplní adresy/offsety relokacemi jako všechny nuly.
Tato stránka naznačuje, že YASM má WRT
direktiva, která může řídit použití .got
, .plt
, atd.
Podle S9.2.5 v dokumentaci NASM to vypadá, že můžete použít CALL puts WRT ..plt
(za předpokladu, že YASM má stejnou syntaxi).