Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Objet appelant d'une fonction

12 réponses
Avatar
Rémi Moyen
Bonjour,

Je me pose la question de savoir si il est possible, depuis une
fonction quelconque, de retrouver l'adresse de l'objet qui a appel=E9
cette fonction. Par exemple, dans le code suivant :

void bar() {
// ... some magic code ...
}

class A {
public:
void foo() {bar();}
};

int main(int argc, char** argv) {
A a;
a.foo();
return 0;
}

je voudrais que bar() soit capable de m'=E9crire un truc du genre
"caller address: 0x1234abcd", o=F9 0x1234abcd est l'adresse de 'a' (i.e.
ce que me sortirait un "cout << this << endl;" depuis n'importe quelle
fonction de A).

Une contrainte forte (sinon la solution est triviale !), c'est que je
ne veux surtout pas modifier la signature de bar(), ni imposer
d'autres contraintes (ou alors le moins possible ?) sur A.

Bon, avant d'en dire plus, quelques pr=E9cisions :
- d'abord, c'est pas un vrai cas d'usage, c'est principalement pour
satisfaire ma curiosit=E9 (pas la peine de me dire que c'est du code pas
propre et tout, quoi :-) ) ;
- ensuite, j'utilise g++/Linux et une solution sp=E9cifique =E0 g++ (ce
qui me semble in=E9vitable, au moins sur la piste o=F9 je suis parti de
manipulation de la pile d'appel...) m'ira tr=E8s bien ;
- finalement, je suis bien conscient que rien ne me dit que bar() est
appel=E9e depuis un objet et qu'il y a sans doute plein d'autres cas
plus compliqu=E9s, mais pour la discussion on dira que =E7a n'est pas pire
que mon exemple...

J'ai trouv=E9 les fonctions backtrace(), backtrace_symbols() de g++ qui
me permettent d'inspecter la pile d'appels, ce qui est d=E9j=E0 =E7a. En
m'inspirant, par exemple, du code ici :
http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-=
my-gcc-c-app-crashes
j'arrive facilement =E0 faire en sorte que bar() =E9crive "caller:
A::foo()".

Je me dis que je devrais pouvoir aller plus loin et exploiter
l'adresse de retour qui est fournie par backtrace() (ce qui doit =EAtre
la m=EAme que __builtin_return_address, je suppose ?) pour essayer de
retrouver le pointeur "this", vu qu'il doit =EAtre quelque part dans les
arguments de A::foo() (en dernier avec g++, je crois - mais vu que
A::foo() n'a pas d'arguments, =E7a doit aussi =EAtre le premier ici...).

Je n'y connais pas grand chose en compilation, assembleur... et j'ai
beaucoup de mal =E0 suivre ce que j'arrive =E0 trouver sur internet =E0
propos de esp, esb et autres registres, mais il me semble bien
qu'autour de la __builtin_return_address ou de la
__builtin_frame_address, il doit y avoir l'adresse des param=E8tres de
la fonction, non ?

Malheureusement, j'ai essay=E9 d'afficher la valeur de 'this', ainsi que
les valeurs du pointeur fourni par backtrace() plus ou moins de 0 =E0
100, pour la frame courante, la frame parente, en d=E9r=E9f=E9ren=E7ant deu=
x
fois (si le pointeur indique l'adresse d'un registre qui lui-m=EAme
contient l'adresse de 'this' ?)... Mais toutes les tortures auxquelles
j'ai pu penser n'ont jamais r=E9ussies =E0 faire avouer =E0 ce pointeur la
valeur de 'this'. Zut, peste et crotte, me voila bien emb=EAt=E9 !

Donc, est-ce que quelqu'un =E0 une id=E9e de si, et comment, je peux
exploiter cette adresse pour retrouver les arguments d'une fonction
quelconque dans la pile, et en particulier 'this' ? Ou est-ce que
quelqu'un =E0 une id=E9e de comment faire ce que je demande (si c'est
possible...) ?

Merci d'avance !
--
R=E9mi Moyen

10 réponses

1 2
Avatar
Rémi Moyen
On Nov 1, 3:23 pm, Rémi Moyen wrote:

Je me pose la question de savoir si il est possible, depuis une
fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
cette fonction.



Je vois que ma question soulève un enthousiasme indescriptible :-)

Bon, je n'ai pas trouvé la réponse, mais quelqu'un m'a indiqué
getcontext() comme une piste possible. Apparemment, le programme
suivant :
http://alephnull.com/backtrace.html (code source téléchargeable en
remplaçant html par c)
fait plus ou moins ce que je veux, en utilisant getcontext(). Mais
d'une part je n'arrive pas à tester ce programme (il est écrit pour
une architecture 32 bits et je suis sur une machine 64 bits, ajouter -
m32 à la compilation marche mais plante à l'execution), et d'autre
part je ne comprends pas les détails du code.

Tant pis, j'irais voir ailleurs si je trouve d'autres pistes... ou pas
(je ne vais pas non plus y passer ma vie).
--
Rémi
Avatar
ZarkXe
Bonjour Rémi,

Pour résoudre ton problème, tu a besoin de te plonger dans de
l'assembleur s'il n’existe aucune libraire (Je n'est pas rechercher).

La solution que je te propose est adaptable en C/C++ et peu importe
l'architecture. Il suffit de modifier le code assembleur.

Pour les explication suivant, je vais utiliser le compilateur g++ et une
architecture x86-64.

Dans les deux solution proposée, l’adresse retour sera dans le registre
rsi. Ceci me permet de l’afficher avec printf.

0x00. La première solution :


------------------------------

C'est une solution simple mais qui n'est pas aplicable dans tout les cas
de figure sur tout quand il y a des variable local. Car le nombre
d’argument empiler peut variée.

-----------------------%<-----------------------------------------------
#include <cstdio>

using namespace std;

void bar() {
__asm__("popq %rdi");
__asm__("popq %rsi");
__asm__("pushq %rsi");
__asm__("pushq %rdi");
__asm__("subq $0x5, %rsi");

printf("%xn");
// ... some magic code ...
}

class A {
public:
void foo() {bar();}
};

int main(int argc, char** argv) {
A a;
a.foo();
return 0;
}
-----------------------%<-----------------------------------------------


0x01 La seconde solution


--------------------------

C'est une solution générale qui peut s'impliquer même si la pile bouge.
Typiquement lors d’ajout des variables local qui est tout le temps le
cas. Cette solution repose sur le mécanisme de sauvegarde du haut de la
pile dans %rbp. Donc à partir de %rbp on peut calculer ou ce trouve
l'adresse de retour.

-----------------------%<-----------------------------------------------
#include <cstdio>

using namespace std;

void bar() {
__asm__("movq 0x8(%rbp), %rsi");
__asm__("subq $0x5, %rsi");
printf("%xn");
// ... some magic code ...
}

class A {
public:
void foo() {bar();}
};

int main(int argc, char** argv) {
A a;
a.foo();
return 0;
}
-----------------------%<-----------------------------------------------

Nous avons vu que dans les deux cas de figure, je parlais qu'on avait
l'adresse de retour dans le RSI. A partire de l'adresse de retour nous
pouvons en déduire l'adresse de l'appelant. Pour cela il suffit explorer
le code assembleur.

0x02 Extraire du code assembleur.



La fonction _Z3barv correspond a la fonction bar().
La fonction _ZN1A3fooEv correspond a la fonction foo().

-----------------------%<-----------------------------------------------
0000000000400554 <_Z3barv>:
400554: 55 push %rbp
400555: 48 89 e5 mov %rsp,%rbp
400558: 48 83 ec 10 sub $0x10,%rsp
40055c: b8 00 00 00 00 mov $0x0,%eax
400561: 48 89 45 f8 mov %rax,-0x8(%rbp)
400565: 48 8b 75 08 mov 0x8(%rbp),%rsi
400569: 48 83 ee 05 sub $0x5,%rsi
40056d: bf ac 06 40 00 mov $0x4006ac,%edi
400572: b8 00 00 00 00 mov $0x0,%eax
400577: e8 cc fe ff ff callq 400448
40057c: c9 leaveq
40057d: c3 retq

000000000040057e <main>:
40057e: 55 push %rbp
40057f: 48 89 e5 mov %rsp,%rbp
400582: 48 83 ec 20 sub $0x20,%rsp
400586: 89 7d ec mov %edi,-0x14(%rbp)
400589: 48 89 75 e0 mov %rsi,-0x20(%rbp)
40058d: 48 8d 45 ff lea -0x1(%rbp),%rax
400591: 48 89 c7 mov %rax,%rdi
400594: e8 07 00 00 00 callq 4005a0 <_ZN1A3fooEv>
400599: b8 00 00 00 00 mov $0x0,%eax
40059e: c9 leaveq
40059f: c3 retq

00000000004005a0 <_ZN1A3fooEv>:
4005a0: 55 push %rbp
4005a1: 48 89 e5 mov %rsp,%rbp
4005a4: 48 83 ec 10 sub $0x10,%rsp
4005a8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4005ac: e8 a3 ff ff ff callq 400554 <_Z3barv>
4005b1: c9 leaveq
4005b2: c3 retq
-----------------------%<-----------------------------------------------

Cordialement,
ZarkXe

On 11/04/2011 05:45 PM, Rémi Moyen wrote:
On Nov 1, 3:23 pm, Rémi Moyen wrote:

Je me pose la question de savoir si il est possible, depuis une
fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
cette fonction.



Je vois que ma question soulève un enthousiasme indescriptible :-)

Bon, je n'ai pas trouvé la réponse, mais quelqu'un m'a indiqué
getcontext() comme une piste possible. Apparemment, le programme
suivant :
http://alephnull.com/backtrace.html (code source téléchargeable en
remplaçant html par c)
fait plus ou moins ce que je veux, en utilisant getcontext(). Mais
d'une part je n'arrive pas à tester ce programme (il est écrit pour
une architecture 32 bits et je suis sur une machine 64 bits, ajouter -
m32 à la compilation marche mais plante à l'execution), et d'autre
part je ne comprends pas les détails du code.

Tant pis, j'irais voir ailleurs si je trouve d'autres pistes... ou pas
(je ne vais pas non plus y passer ma vie).
--
Rémi
Avatar
Alain Ketterlin
ZarkXe writes:

La solution que je te propose est adaptable en C/C++ et peu importe
l'architecture. Il suffit de modifier le code assembleur.



:-)

0x00. La première solution :


------------------------------
C'est une solution simple mais qui n'est pas aplicable dans tout les cas
de figure sur tout quand il y a des variable local. Car le nombre
d’argument empiler peut variée.



Il peut même ne pas y en avoir du tout... surtout en 64 bits. Voir
l'ABI SysV.

[...]
0x01 La seconde solution


--------------------------
[...] Cette solution repose sur le mécanisme de sauvegarde du haut de
la pile dans %rbp.



Qui n'est en général pas fait, sauf cas particulier.

Compile ton code avec -O1 (ou n'importe quoi sauf le -O0 par defaut) et
tes programmes ne fonctionnent plus.

Je me pose la question de savoir si il est possible, depuis une
fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
cette fonction.







Je ne suis pas sûr de ce que tu entends par objet, mais tu peux regard er
tu coté de backtrace() dans la glibc.

Typiquement, c'est le genre de choses pour lesquelles il n'existe pas de
solution générale (comme le précise le man de getcontext()). Sans l'aide
du compilateur, tu as peu d'espoir d'obtenir quelque chose d'utile.

-- Alain.
Avatar
Rémi Moyen
On Nov 5, 1:21 pm, ZarkXe wrote:
Bonjour Rémi,

Pour résoudre ton problème, tu a besoin de te plonger dans de
l'assembleur s'il n’existe aucune libraire (Je n'est pas rechercher).



Argh. Je n'ai jamais appris l'assembleur, sauf sur le tas et de
manière très très partielle... Bon, merci quand même pour ta répo nse,
j'essaie de décortiquer tout ça à tête reposée. Je reviendrais pe ut-
être poser des questions...
--
Rémi
Avatar
Rémi Moyen
> [...] Cette solution repose sur le mécanisme de sauvegarde du haut de
> la pile dans %rbp.

Qui n'est en général pas fait, sauf cas particulier.

Compile ton code avec -O1 (ou n'importe quoi sauf le -O0 par defaut) et
tes programmes ne fonctionnent plus.



Je vais sans doute dire une bêtise, mais j'avais vaguement eu
l'impression que backtrace() et/ou get_context() permettaient
justement de récupérer cette addresse de manière relativement robuste
(avec g++, en tout cas) ?

>>> Je me pose la question de savoir si il est possible, depuis une
>>> fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
>>> cette fonction.

Je ne suis pas sûr de ce que tu entends par objet, mais tu peux regarde r
tu coté de backtrace() dans la glibc.



Ben c'est ce que j'avais essayé, justement (cf. mon premier message),
mais autant je n'ai aucun problème à récupérer les fonctions appel ées,
autant je n'arrive absolument pas à récupérer les arguments de ces
fonctions.

(et dans le cas qui m'intéresse, en C++, il me semble que l'objet --
le pointeur 'this', disons -- dont une méthode est appelée est passé
en interne en argument de la fonction, donc arriver à récupérer tous
les arguments m'avancerait déjà pas mal)

Typiquement, c'est le genre de choses pour lesquelles il n'existe pas de
solution générale (comme le précise le man de getcontext()). Sans l 'aide
du compilateur, tu as peu d'espoir d'obtenir quelque chose d'utile.



Déjà, comme je le disais plus haut, je ne cherche pas vraiment quelque
chose d'utile. C'est plus de la curiosité qu'autre chose. Et sinon, je
pense que je suis perdu dans les (quelques) docs que j'arrive à
trouver vu que je ne capte pas grand chose dès que quelqu'un parle
d'assembleur...

Enfin bon, merci pour tes remarques !
--
Rémi
Avatar
Alain Ketterlin
Rémi Moyen writes:

[...]
Compile ton code avec -O1 (ou n'importe quoi sauf le -O0 par defaut) et
tes programmes ne fonctionnent plus.



Je vais sans doute dire une bêtise, mais j'avais vaguement eu
l'impression que backtrace() et/ou get_context() permettaient
justement de récupérer cette addresse de manière relativem ent robuste
(avec g++, en tout cas) ?



Oui et non. En fait, retrouver où sur la pile est la position de
l'adresse de retour de l'appelante repose sur des conventions. Voilà ce
que dit la doc de backtrace() :

| These functions make some assumptions about how a function's return
| address is stored on the stack. Note the following:
| * Omission of the frame pointers (as implied by any of gcc(1)'s
| nonzero optimization levels) may cause these assumptions to be vio⠀
| lated.
| * Inlined functions do not have stack frames.
| * Tail-call optimization causes one stack frame to replace another.

Donc, ce n'est possible que dans des cas bien contrôlés.

Je ne suis pas sûr de ce que tu entends par objet, mais tu peux reg arder
tu coté de backtrace() dans la glibc.



Ben c'est ce que j'avais essayé, justement (cf. mon premier message),
mais autant je n'ai aucun problème à récupérer les fo nctions appelées,
autant je n'arrive absolument pas à récupérer les argument s de ces
fonctions.



Désolé, j'ai du rater le début de la conversation.

Pour les arguments, oublie. Selon le compilo, l'ABI, etc. ils seront
placés soit dans des registres, soit dans la pile (soit dans les deux).

(et dans le cas qui m'intéresse, en C++, il me semble que l'objet --
le pointeur 'this', disons -- dont une méthode est appelée est passé
en interne en argument de la fonction, donc arriver à récupà ©rer tous
les arguments m'avancerait déjà pas mal)



Oui, this est un argument comme un autre, et comme c'est un pointeur, il
peut finir dans un registre. Mais ce qu'il devient après... Typiquemen t,
si m.f() appelle n.g(), l'adresse de m est passée dans un registre à  
f(), mais elle est ensuite sauvegardée sur la pile (si nécessaire)
puisque le registre doit servir à passer l'adresse de n à g(). Et c.

Déjà, comme je le disais plus haut, je ne cherche pas vraiment quelque
chose d'utile. C'est plus de la curiosité qu'autre chose. Et sinon, je
pense que je suis perdu dans les (quelques) docs que j'arrive à
trouver vu que je ne capte pas grand chose dès que quelqu'un parle
d'assembleur...



J'aime bien "Smashing The Stack For Fun And Profit", à
http://insecure.org/stf/smashstack.html

-- Alain.
Avatar
Marc
Rémi Moyen wrote:

Je me pose la question de savoir si il est possible, depuis une
fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
cette fonction.



Hmm, utiliser le débuggueur est-il autorisé ?
Avatar
Pascal J. Bourguignon
Marc writes:

Rémi Moyen wrote:

Je me pose la question de savoir si il est possible, depuis une
fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
cette fonction.



Hmm, utiliser le débuggueur est-il autorisé ?



En effet, il sera surement plus facile d'implémenter la fonctionalité
voulue, si on a un exécutable compilé avec les options et informations
de deboguage. cf. eg. http://dwarfstd.org/doc/dwarf-2.0.0.pdf

Dans le cas d'un exécutable compilé sans option de déboguage, ou pire,
avec des optimizations activée, le problème peut être insoluble, car le
compilateur peut générer le code de telle manière que l'information
voulue aie été détruite.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
Rémi Moyen
On Nov 7, 11:24 pm, Marc wrote:
Rémi Moyen  wrote:
> Je me pose la question de savoir si il est possible, depuis une
> fonction quelconque, de retrouver l'adresse de l'objet qui a appelé
> cette fonction.

Hmm, utiliser le débuggueur est-il autorisé ?



Ben en fait je me demandais comment faire sans débuggueur, en me
disant que justement, si le débuggueur y arrive, c'est que c'est
faisable.

Mais il est clair (je l'ai déjà dit) que je ne m'attends pas à une
solution standard, portable ou même robuste. Disons que je voulais
savoir si c'était quelque chose de faisable relativement facilement,
juste pour voir comment ça marche, ou si c'était impossible à faire
sans tomber dans l'usine à gaz (typiquement ce que font les
débuggueurs...). En gros, est-ce qu'avec une version de g++, une
machine particulière, un jeu d'options de compilations bien choisies,
je pouvais récupérer un pointeur via backtrace ou un truc de ce genre,
faire une paire de cast et obtenir quelque chose.

Les diverses réponses me montrent que ça n'est pas si simple, ok.
J'oublie, ma curiosité est plus ou moins satisfaite.
--
Rémi
Avatar
Rémi Moyen
On Nov 7, 6:04 pm, Alain Ketterlin
wrote:

Pour les arguments, oublie. Selon le compilo, l'ABI, etc. ils seront
placés soit dans des registres, soit dans la pile (soit dans les deux).



Bah c'est sûr que je n'aurais jamais un truc générique ni même
robuste. Mais pour un compilateur, un jeu d'options, est-ce qu'il n'y
a pas une certaine logique ? Je parlais aussi de 'this' parce que
d'une part c'est celui qui m'a gratouillé le cerveau en premier, mais
aussi d'autre part parce que j'imagine (peut-être à tort ?) qu'il doit
être traité de la même manière dans toutes les fonctions (pour un
compilateur, options, etc. spécifique, toujours). Ça me semble un peu
plus constant que des arguments qui effectivement ne sont pas
forcément toujours stockés de la même manière.

Oui, this est un argument comme un autre, et comme c'est un pointeur, il
peut finir dans un registre. Mais ce qu'il devient après... Typiquement ,
si m.f() appelle n.g(), l'adresse de m est passée dans un registre à
f(), mais elle est ensuite sauvegardée sur la pile (si nécessaire)
puisque le registre doit servir à passer l'adresse de n à g(). Etc.



Ah, donc si je suis dans g() je dois pouvoir retrouver où est le
'this' courant (n), mais même en jouant avec la backtrace, il n'est
pas évident que le 'this' dans f() soit à un endroit prédictible...
ok, je n'avais pas pensé à ça...

J'aime bien "Smashing The Stack For Fun And Profit", àhttp://insecure.o rg/stf/smashstack.html



Ça a l'air bien, ça ! Merci pour l'adresse, je stocke dans un coin. Il
va me falloir du temps pour le digérer (surtout que tout ça n'est que
de la curiosité générique, donc ça n'est pas du tout ma priorité. ..).
--
Rémi
1 2