Amikor a gcc generál BoF-ot a binárisba. Bele.

Adott egy függvény definíció meg egy gcc472/darwin@x86_64, -O0 és néhány ősz hajszál.


void * cli_set_variable(char *var, char *val)
{
     return 0x00ff00ff00ff00ff;
}

…

      void *ret = cli_set_variable(var, val);
      vp_log(ret);

Fordítás és futtatás után az alábbi log bejegyzést dobja a gép:


20140206 113043a [97002] [0000000111965000] [var:task.c:276:master_start_task()] ret: 0xff00ff

Némi debuggolás után:


0x000000010c17cf3b <master_start_task+1989>:	mov    $0x0,%eax
0x000000010c17cf40 <master_start_task+1994>:	callq  0x10c177e13 <cli_set_variable>
0x000000010c17cf45 <master_start_task+1999>:	cltq   
0x000000010c17cf47 <master_start_task+2001>:	mov    %rax,-0x48(%rbp)

CLTQ:


cltq sign extends %eax to %rax

És így lesz egy pointer előjel bővítve… KÖSZI SRÁCOK. Vajon hány ilyen gcc által generált BoF leledzhet szerte a világon mindenféle szolgáltatásba és egyéb szoftverbe kódoltan?

Hozzászólások

Mi ez a BoF? (ahhoz rövidnek tűnik, hogy a google válaszoljon)

Köszi. Más kérdés, hogy pointert ritkán szokás hexa konstansként visszaadni ;)

Valoban, de nem pelda nelkul allo esemeny. Legutobb iOS4 kamera kernel modul revengnel volt szuksegem OS fuggoen egy belepesi pont ilyesfele hasznalatara, mert egy adott allokacios fuggenyt maskepp nem lehetett elerni. De ez most csak test pattern.

---
pontscho / fresh!mindworkz

OK, ez most teszt: nem lehet, hogy a gcc a konstans miatt fordított hülyeséget? (találgatok, ezért kérdeztem, hogy volt-e valami konkrét előzménye)
Sajnos a mostani processzorok assembly-je távol áll tőlem (meg már azok is, amiket anno kívül-belül ismertem :( ), szóval ebből az egészből csak a C kódot fogtam fel, meg azt próbáltam, amit írtál. Az assembly részt akár kínai írásjelekkel is felvéshetted volna. ;)

Azért ezt a pár utasítást nem olyan nehéz felfogni, még nekem is ment. ;) (Igaz én azért pislogtam, mert a gcc assembly dialektusában az utolsó paraméter a cél és nem az első, én meg azt szoktam meg.) A mov az csak megy, a callq is, a cltq-t leírta Pontscho, más meg nincs. A %rax az a 64 bites A regiszter, az %eax ennek az alsó 32 bitje, az %rbp a stack bázis pointer. Kihagytam valamit?


Dump of assembler code for function cli_set_variable:
0x0000000100008ec3 <cli_set_variable+0>:	push   %rbp
0x0000000100008ec4 <cli_set_variable+1>:	mov    %rsp,%rbp
0x0000000100008ec7 <cli_set_variable+4>:	mov    %rdi,-0x8(%rbp)
0x0000000100008ecb <cli_set_variable+8>:	mov    %rsi,-0x10(%rbp)
0x0000000100008ecf <cli_set_variable+12>:	mov    $0xff00ff00ff00ff,%rax
0x0000000100008ed9 <cli_set_variable+22>:	pop    %rbp
0x0000000100008eda <cli_set_variable+23>:	retq   

Ezert nem fontos a returnre adott gcc warning.

---
pontscho / fresh!mindworkz

Ez nem bof, pont az ellenkezoje, raadaskent a castolas lemaradt amiert biztos sirt erosen a fordito.
Jah -> return (void*)0x00ff00ff00ff00ff;

Nem ertem miert gondoljak az emberek, hogy a fordito hulyeseget pofazik. Az a fordito pont azert warning-ol, mert faszsagot csinalsz, vagy ha nem faszsagot csinalsz, akkor szarul csinalod.

// Happy debugging, suckers
#define true (rand() > 10)


0x0000000100008ecf <cli_set_variable+12>:	mov    $0xff00ff00ff00ff,%rax

Emiatt a sor miatt erdektelen az, h mit rinyal a return-re a compiler. A problemam _nem_ ebbol fakadt es _nem_ itt volt. A mov magasrol tesz arra, h milyen elojele van vagy nincs egy adott konstansnak, a regiszter szinten nem foglalkozik vele. Az elojel csak az adat ertelmezesetol fuggo kerdes.

BoF-nak meg azert BoF, mert egyeb mas cim (eredetileg egy lefoglalt memoria terulet kerult returnre) elojellel kiegeszitve tok random helyre mutato pointer lesz belole.

---
pontscho / fresh!mindworkz

Bocsánat, a

void*

milyen típus is pontosan?

Nem arról szól a dolog -- persze, tévedhetek is -- hogy a definíciónál még nem lehet tudni, hogy milyen típust is ad vissza a függvény, viszont a függvény meghívásánál illene erről egy cast-olással nyilatkozni?

G.

============================================
"Share what you know. Learn what you don't."

Ne haragudj, k.rég volt amikor C-t és egyéb, 3. generációs nyelveket tanulgattam, ezért is nem mertem leírni, de ahogy elnézem, mások is valami hasonló nézeten vannak: van egy függvényed, ami visszaad valamilyen típust. Van a returnben egy konstans értéked. Ilyenkor a fordító azt csinálja, hogy kitalál a konstans értékhez egy általa feltételezett típust, azt konvertálja a függvényed visszatérési értékeként megjelölt típusra és így adja vissza a returnbe írt értéket. (feltéve, hogy a mai fordítók is nagyjából úgy működnek e téren, mint húsz évvel ezelőtti társaik)

Végeredményben ez a szokásos: "a számítógép nem a kívánságaid, hanem az utasításaid szerint működik" esetének tűnik.
De összeszedem magam, hogy megpróbáljak valami assembly listát kipréselni a gcc-ből (remélem, még lehet :D) és megnézem, hogy különböző variációkra hogy reagál.
Régen imádtam ilyenekkel szórakozni, sajnos nagyon kiestem belőle. :(

Leginkabb azert rugoztok ezen a jelentektelen aprosagon, mert nem igazan ertitek, h hogyan mukodik a cdecl calling convention x86-on.
Egyszeru adat tipusok visszateresi ertekei igy alakulnak x86_64-en:


[unsigned] char : al
[unsigned] short : ax
[unsigned] int : eax
[unsigned] long: rax
float : st(0)
double : st(0)
void * : rax
akarmi * : rax

A topic inditoban leirt fuggveny x86_64 asm kodja innentol annyi, h

mov $0xff00ff00ff00ff,%rax

. Igy tok mindegy mit mire kellene castolni, attol meg minden esetben az elobbi asm kodra fog lefordulni a kod. Ha unsigned long lenne a fuggveny visszateresi ertekenek tipusa akkor is, ha long lenne, akkor is, ha int *, akkor is, ha void *, akkor is. A fuggveny visszateresi erteke 64 bites szamok eseten (ami lehet egy konstans, egy pointer, barmi) a rax-ban lesz megtalalhato, az ebben talalhato szam ertelmezese csak a kod tovabbi reszetol fugg. Ami viszont a fuggveny jo es normalis mukodese utan szepen a cltq-nak koszonhetoen ugy lett kezelve, mintha egy szimpla int lett volna a visszateresi ertek tipusa egy pointer helyett, teljesen fuggetlenul attol, h en ott egy allokalt memoriaterulet cimet vagy egy konstanst adok vissza.

---
pontscho / fresh!mindworkz

Vagyis hulye vagyok en, hulye a fordito is mikor szol hogy integert adsz pointerkent (makes pointer from integer without a cast), es hulye mindenki mas is.
Ertem en, rendben:)
Az meg hogy erre forditotta, nem meglepo, mivel ez meg csak nem is kiszervezett konstans, nyugodtan optimalizalhatja forditas idoben

// Happy debugging, suckers
#define true (rand() > 10)

Nem értem mit nem értesz, még én is értem. :) Ha a castolás (hiánya) miatt van ott a cltq, akkor annak a függvény asm kódjában kellene szerepelnie, még a ret előtt. Miután megvolt a ret, akármi is van a %rax-ban, azt onnantól kezdve void *-ként kell kezelni, függetlenül attól, hogy inttel lett inicializálva. Azonban a gcc ügyesen berakott egy cltq-t a callq után, mintha a visszatérési érték int lenne, pedig nem.

Amikor arról beszéltél, hogy a callq után a kapott adat egy int, akkor feltételeztem úgy gondolod, hogy a fordító megjegyezte, hogy milyen típussal lett inicializálva az adat. Oké, ez nem teljesen az, mint ami pl. C++ esetén történik, de olyasmi.

Dehogynem típus, úgy hívják pointer.

Nem: a pointer az pointer, ami nem típus, a void pl típus, még ha tipizálatlan is. A pointer típus egy olyan pointer, aminek meg van határozva a típusa, pl char*.
Bovebben: http://en.wikipedia.org/wiki/C_data_types#Pointer_types

// Happy debugging, suckers
#define true (rand() > 10)

Tenyleg ennyire problemas a szovegertes ? :) Nem az a problemam, h beszol a compiler, h konstanst adok vissza egy pointernel es ezert warningol, amiben igaza is van (mellesleg ha lekasztolom void *-ra, akkor meg warning sincs), hanem h utana kurta el a tortenetet. Gondolom az is megvolt, h szandekosan -O0-lal forditottam.

Csak a kedvedert:


aeryn:asd pontscho$ cat main.c
void *basz(void)
{
	return 1;
}
aeryn:asd pontscho$ gcc -c -o main.o main.c -O0 -Wall
main.c: In function 'basz':
main.c:4: warning: return makes pointer from integer without a cast
aeryn:asd pontscho$ otool -tv main.o
main.o:
(__TEXT,__text) section
_basz:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	movq	$0x0000000000000001,%rax
000000000000000e	movq	%rax,0xf0(%rbp)
0000000000000012	movq	0xf0(%rbp),%rax
0000000000000016	movq	%rax,0xf8(%rbp)
000000000000001a	movq	0xf8(%rbp),%rax
000000000000001e	popq	%rbp
000000000000001f	ret
aeryn:asd pontscho$

---

aeryn:asd pontscho$ cat main.c
void *basz(void)
{
	return (void *)1;
}
aeryn:asd pontscho$ gcc -c -o main.o main.c -O0 -Wall
aeryn:asd pontscho$ otool -tv main.o
main.o:
(__TEXT,__text) section
_basz:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	movq	$0x0000000000000001,%rax
000000000000000e	movq	%rax,0xf0(%rbp)
0000000000000012	movq	0xf0(%rbp),%rax
0000000000000016	movq	%rax,0xf8(%rbp)
000000000000001a	movq	0xf8(%rbp),%rax
000000000000001e	popq	%rbp
000000000000001f	ret
aeryn:asd pontscho$

---

aeryn:asd pontscho$ cat main.c 

#include <stdlib.h>

void *basz(void)
{
	return malloc(1);
}

aeryn:asd pontscho$ gcc -c -o main.o main.c -O0 -Wall
aeryn:asd pontscho$ otool -tv main.o
main.o:
(__TEXT,__text) section
_basz:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	subq	$0x10,%rsp
0000000000000008	movq	$0x0000000000000001,%rax
0000000000000012	movq	%rax,%rdi
0000000000000015	callq	0x0000001a
000000000000001a	movq	%rax,0xf0(%rbp)
000000000000001e	movq	0xf0(%rbp),%rax
0000000000000022	movq	%rax,0xf8(%rbp)
0000000000000026	movq	0xf8(%rbp),%rax
000000000000002a	addq	$0x10,%rsp
000000000000002e	popq	%rbp
000000000000002f	ret
aeryn:asd pontscho$ 

Capisce? Szoval hagyjuk mar ezt a faszsagot es kezdjunk mar el gondolkodni is vegre.

---
pontscho / fresh!mindworkz

Vagyis hulye vagyok en, hulye a fordito is mikor szol hogy integert adsz pointerkent (makes pointer from integer without a cast), es hulye mindenki mas is.

Sot, mondok jobbat, "tortem" anno ugy darwin/arm-on kamera modult, h indirekt cimzessel, egy fuggveny pointernek beirtam egy konstanst es utana azt hivogatta utana a kod. :-)

makes pointer from integer without a cast

Lasd a keretes bejegyzest.

nyugodtan optimalizalhatja forditas idoben

Nem optimalizalta ki mar a BaT altal irtak es a -O0 miatt sem. :)

---
pontscho / fresh!mindworkz

Mondd, az a logbejegyzés nálad hogy keletkezik??
Debug program írja ki vagy te íratod ki printf-fel?
Utóbbi esetben milyen formátumot adtál meg?

Mint mondtam, rohadt rég foglalkoztam C-vel, akkor még nem volt 64 bit, sem "%32lx" formátum string... :)

update: Ja, hogy volt valami crash is... akkor nem szóltam, csak nem értem. Ugyanis nálam még normálisan működik (látszólag).

O egy void* fgv.-t definialt, ami egy tipizalatlan Pointer. Ebben az esetben egy pointerrel kell visszaterni minden esetben (es itt most lenyegtelen hogy az a pointer egy int, char tipusu, vagy tipizalatlan void). Ezzel szemben, egy fixen hakolt integer-el ter vissza (a 0 integer, 0.0f float, a 0x0 meg egy hexakent definialt integer). Itt egyertelmu, hogy nem a gcc a hulye, leven szolt is neki, hogy nem szokas integerel pointerkent visszaterni

// Happy debugging, suckers
#define true (rand() > 10)

Nem! Te voltal az aki egy integer-t castoltal egy pointer-re, a gcc ezert sirt. A szam az mindig szam lesz gcc-ben, a 0x meg bazira szam reprezentalas hexadecimalisan, ami meg mindig integer, raadaskent signed.

Raadaskent pont ezert nem a GCC csinalja az invalid pointert, hanem te.

// Happy debugging, suckers
#define true (rand() > 10)

Vitadonto otlet: a clang minek forditja? ;)

Clangom nincs, de van visual studio. :)

Kód:


#include <stdio.h>

void * cli_set_variable(char *var, char *val)
{
	return 0x00ff00ff00ff00ff;
}

int main(void)
{
	void *ret = cli_set_variable(NULL, NULL);

	printf("%p", ret);
	return 0;
}

Asm (aláhúzva a függvény és annak meghívása):


; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.21005.1 

include listing.inc

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

_DATA	SEGMENT
$SG3059	DB	'%p', 00H
_DATA	ENDS
PUBLIC	cli_set_variable
PUBLIC	main
EXTRN	printf:PROC
pdata	SEGMENT
$pdata$main DD	imagerel $LN3
	DD	imagerel $LN3+42
	DD	imagerel $unwind$main
pdata	ENDS
xdata	SEGMENT
$unwind$main DD	010401H
	DD	06204H
xdata	ENDS
; Function compile flags: /Odtp
_TEXT	SEGMENT
ret$ = 32
main	PROC
; File c:\users\bat\temp\test.c
; Line 9
$LN3:
	sub	rsp, 56					; 00000038H
; Line 10
	xor	edx, edx
	xor	ecx, ecx
	call	cli_set_variable
	mov	QWORD PTR ret$[rsp], rax
; Line 12
	mov	rdx, QWORD PTR ret$[rsp]
	lea	rcx, OFFSET FLAT:$SG3059
	call	printf
; Line 13
	xor	eax, eax
; Line 14
	add	rsp, 56					; 00000038H
	ret	0
main	ENDP
_TEXT	ENDS
; Function compile flags: /Odtp
_TEXT	SEGMENT
var$ = 8
val$ = 16
cli_set_variable PROC
; File c:\users\bat\temp\test.c
; Line 4
	mov	QWORD PTR [rsp+16], rdx
	mov	QWORD PTR [rsp+8], rcx
; Line 5
	mov	rax, 71777214294589695			; 00ff00ff00ff00ffH
; Line 6
	ret	0
cli_set_variable ENDP
_TEXT	ENDS
END

Eredmény:


C:\Users\BaT\Temp>cl test.c
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test.c
test.c(5) : warning C4047: 'return' : 'void *' differs in levels of indirection from '__int64'
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

C:\Users\BaT\Temp>test.exe
00FF00FF00FF00FF
C:\Users\BaT\Temp>

Deklaráltad a cli_set_variable függvényt a hívás helye előtt? A generált kód alapján nem úgy néz ki...

Szerintem is ez lesz a gond: nem volt prototípus, 4-byte-os integert alaptértelmezett a fordító, azt terjesztette ki pointerré (azért előjelkiterjesztéssel, mert a programok 64-bites módban a címtartomány legalját és legtetejét szokták használni: lásd itt az ábrát)

Egyébként még a fv-ben is kitennék egy-két L-betűt:

return 0x00ff00ff00ff00ffLL;

sőt:

return (void *)0x00ff00ff00ff00ffLL;

PS: Mondjuk az is igaz, hogy jobb helyen addig nem is foglalkoznak egy ilyen "hibával", amig a fordítás nem warningmentes (nem, nem a warningok elnyomására gondolok, hanem a -W -Wall -Wextra -pedantic uniójára)

Mi volt végül a hiba oké? Csak nem PEBKAC?