2010-08-12 3 views
8

Si vous voulez appeler une fonction C/C++ de l'assemblage en ligne, vous pouvez faire quelque chose comme ceci:appel direct à l'aide de l'assembleur en ligne de GCC

void callee() {} 
void caller() 
{ 
    asm("call *%0" : : "r"(callee)); 
} 

GCC émettra alors un code qui ressemble à ceci:

movl $callee, %eax 
call *%eax 

Cela peut être problématique car l'appel indirect détruira le pipeline sur les anciennes UC. Comme l'adresse de callee est éventuellement une constante, on peut imaginer qu'il serait possible d'utiliser la contrainte i. Je cite le CCG en ligne docs:

`i'

An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time or later.

Si j'essaie de l'utiliser comme ceci:

asm("call %0" : : "i"(callee)); 

que je reçois l'erreur suivante de l'assembleur:

Error: suffix or operands invalid for `call'

C'est parce que GCC émet le code

call $callee 

Au lieu de

call callee 

Donc, ma question est de savoir s'il est possible de faire la production de GCC l'call correcte.

+1

Etes-vous sûr que l'appel indirect détruit le pipeline? Avez-vous évalué? Ma compréhension était que dans les vieux jours sur x86 (pré-i686), les appels indirects étaient très mauvais (je me souviens qu'ils sont un bon 10-100 fois plus lent sur mon K6), mais de nos jours, les processeurs sont plus intelligents et peuvent les gérer . Alors faites quelques tests avant de sauter aux conclusions! –

+0

@R ..: Vous avez raison: si je compare cela à un vrai CPU, cela ne fait aucune différence. Je cours mon code en qemu, cependant, et il semble faire une différence là-bas (environ 20% plus de cycles/appel). – Job

+0

Ensuite, je voudrais juste coller à la façon dont vous le faites, avec l'appel indirect. Cela permettra à gcc de générer le code correct pour les bibliothèques/exécutables PIC/PIE sans avoir à insérer des hacks spéciaux pour gérer ces choses. –

Répondre

10

J'ai obtenu le answer de la liste de diffusion de GCC:

asm("call %P0" : : "i"(callee)); 

Maintenant, je dois juste savoir ce %P0 signifie en fait parce qu'il semble être une fonctionnalité non documentée ...

Modifier: Après avoir regardé le code source de GCC, le code P devant une contrainte n'est pas très clair. Mais, entre autres, il empêche GCC de mettre un $ devant des valeurs constantes. Ce qui est exactement ce dont j'ai besoin dans ce cas.

+0

Voir aussi https://stackoverflow.com/questions/37639993/is-this-assembly-function-call-safe-complete pour ** les dangers d'utiliser 'call' de inline asm **. Il est fondamentalement impossible de faire de manière fiable pour x86-64 System V (Linux), parce que vous ne pouvez pas dire au compilateur que vous voulez écraser la zone rouge. C'est généralement une mauvaise idée. Voir https://stackoverflow.com/questions/7984209/calling-a-function-in-gcc-inline-assembly. –

0

Que diriez-vous de.

asm volatile ("call $callee"); 

puisque vous connaissez de toute façon le nom du symbole. Editer: Le point que je voulais faire est que vous n'avez pas à passer par les conventions de gcc pour les arguments asm ici, mais que vous devez simplement faire du traitement de texte pour faire le travail.

+0

Eh bien, ce serait mon dernier recours (il devrait être 'asm (" call callee "),', cependant). Le problème est que vous auriez besoin du nom du symbole mutilé. Ce n'est pas un gros problème en C mais un PITA en C++. – Job

+0

Ceci devrait être 'asm (" call callee ");', mais en C++ cela ressemblerait à 'asm (" call _ZL4callv ");' –

+4

Cela ne fonctionne pas, pour plusieurs raisons. Un, différents systèmes d'exploitation (sur le même matériel) ont des conventions différentes pour la façon dont les noms de symboles sont mappés (la curiosité la plus courante est le préfixage d'un trait de soulignement). Et deux, à moins que gcc ne soit en quelque sorte dit que l'asm utilise la fonction, il peut très bien ne jamais omettre une version appelable autonome de la fonction. Par exemple, il peut l'aligner partout ailleurs ou l'omettre complètement s'il n'est jamais appelé à partir du code C. Trois, il y a probablement des problèmes avec le code PIC et PIE ... –

0

L'astuce est la concaténation littérale de chaîne. Avant que GCC commence à essayer d'obtenir une signification réelle de votre code, il concaténera les littéraux de chaîne adjacents, même si les chaînes d'assemblage ne sont pas les mêmes que celles utilisées dans votre programme:

Puisque vous utilisez GCC, vous pouvez également faire

#define ASM_CALL(X) asm("\t call " #X "\n") 

int main(void) { 
    ASM_CALL(my_function); 
    return 0; 
} 

Si vous ne connaissez pas déjà, vous devriez savoir que appeler les choses de l'assemblage en ligne est très délicat. Lorsque le compilateur génère ses propres appels vers d'autres fonctions, il inclut du code pour configurer et restaurer les éléments avant et après l'appel. Cependant, il ne sait pas qu'il devrait faire cela pour votre appel. Vous devrez soit l'inclure vous-même (très difficile à obtenir et peut rompre avec une mise à niveau du compilateur ou des drapeaux de compilation) ou vous assurer que votre fonction est écrite de telle manière qu'elle ne semble pas avoir changé les registres ou les conditions du pile (ou variable dessus).

Cela ne fonctionnera que pour les noms de fonctions C - pas C++ car ils sont mutilés.

+1

Non, cela ne fonctionne pas du tout.Premièrement, le '$' est totalement faux pour l'appel, deuxièmement il ne veut pas "my_function" dans la chaîne asm, mais le ** nom mutilé généré par C++ **. –

+0

Je n'ai pas vu que c'était aussi pour C++. Dans ce cas, il est probable que cela ne fonctionnera pas si le nom de la fonction est surchargé sans beaucoup de cerceaux. Je viens de copier le "$" d'un autre post puisque je ne me souviens pas de la syntaxe x86 asm. Corriger cela ... – nategoose

0

Si vous générez du code 32 bits (par exemple -m32 gcc option), la ligne asm suivante émet un appel direct:

asm ("call %0" :: "m" (callee)); 
1

Peut-être que je manque quelque chose ici, mais

extern "C" void callee(void) 
{ 

} 

void caller(void) 
{ 
    asm("call callee\n"); 
} 

devrait fonctionner correctement. Vous avez besoin d'extern "C" pour que le nom ne soit pas décoré en fonction des règles de gestion des noms C++.

Questions connexes