gcc supprime le code assembleur en ligne
il semble que gcc 4.6.2 supprime le code qu'il considère comme non utilisé des fonctions.
de test.c
int main(void) {
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
démontage de main()
0x08048404 <+0>: push ebp
0x08048405 <+1>: mov ebp,esp
0x08048407 <+3>: nop # <-- This is all whats left of my jmp.
0x08048408 <+4>: mov eax,0x0
0x0804840d <+9>: pop ebp
0x0804840e <+10>: ret
options du Compilateur
aucune optimisation activée, juste gcc -m32 -o test test.c
(-m32
parce que je suis sur une machine 64 bits).
Comment puis-je arrêter ce comportement?
Edit: de préférence en utilisant les options du compilateur, pas en modifiant le code.
7 réponses
mise à Jour 2012/6/18
juste en y réfléchissant, on peut mettre le goto exit
dans un asm bloc, ce qui signifie que seulement 1 ligne de code doit changer:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp x0");
exit:
return 0;
}
c'est nettement plus propre que mon autre solution ci-dessous (et peut-être plus agréable que la solution actuelle de @ugoren aussi).
c'est assez hacky, mais ça semble fonctionner: cacher le handler dans un conditionnel qui ne peut jamais être suivi dans des conditions normales, mais l'arrêter d'être éliminé en empêchant le compilateur de faire son analyse correctement avec un assembleur en ligne.
int main (void) {
int x = 0;
__asm__ __volatile__ ("" : "=r"(x));
// compiler can't tell what the value of x is now, but it's always 0
if (x) {
handler:
__asm__ __volatile__ ("jmp x0");
}
return 0;
}
Même -O3
jmp
est préservé:
testl %eax, %eax
je .L2
.L3:
jmp x0
.L2:
xorl %eax, %eax
ret
(Ce qui semble vraiment douteuses, donc j'espère qu'il y est une meilleure façon de le faire. modifier simplement en mettant un volatile
devant x
donc on n'a pas besoin de faire la supercherie asm en ligne.)
dirait que c'est juste la façon dont il est Quand gcc
voit que le code dans une fonction est inaccessible, il le supprime. D'autres compilateurs pourraient être différents.
gcc
, une des premières phases de la compilation est la construction du "graphique de flux de contrôle "- un graphique de" blocs de base", chacun libre de conditions, connectés par des branches. Lors de l'émission du code réel, certaines parties du graphique, qui ne sont pas accessibles depuis la racine, sont écartées.
Cela ne fait pas partie de la phase d'optimisation, et n'est donc pas affectée par les options de compilation.
donc toute solution impliquerait de faire gcc
pense que le code est accessible.
ma suggestion:
au lieu de mettre votre code d'assemblage dans un endroit inaccessible (où GCC peut le supprimer), vous pouvez le mettre dans un endroit accessible, et sauter l'instruction problématique:
int main(void) {
goto exit;
exit:
__asm__ __volatile__ (
"jmp 1f\n"
"jmp x0\n"
"1:\n"
);
return 0;
}
voir Aussi ce fil de discussion au sujet de la question.
Je ne crois pas qu'il existe un moyen fiable d'utiliser les options de compilation pour résoudre ce problème. Le mécanisme préférable est quelque chose qui fera le travail et travaillera sur les versions futures du compilateur indépendamment des options utilisées pour compiler.
Commentaire à propos de Accepté de Répondre à
Dans la accepté de répondre il y a une modification de l'original que suggère cette solution:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp x0");
exit:
return 0;
}
d'Abord jmp x0
doit être jmp 0x0
. Deuxièmement C les étiquettes sont généralement traduites en étiquettes locales. jmp exit
ne fait pas de saut à l'étiquette exit
dans le C fonction, elle saute à l' exit
fonction dans le C la bibliothèque court-circuite effectivement le return 0
au bas de main
. En utilisant Godbolt avec GCC 4.6.4 nous obtenons cette sortie non optimisée (j'ai réduit les étiquettes dont nous ne nous soucions pas):
main:
pushl %ebp
movl %esp, %ebp
jmp exit
jmp 0x0
.L3:
movl , %eax
popl %ebp
ret
.L3
est en fait l'étiquette locale pour exit
. Vous ne trouverez pas de l' exit
étiquette dans l'assemblage généré. compiler et lier si le C bibliothèque est présente. Ne pas utiliser C des marques locales de goto en montage inline comme celle-ci.
utiliser asm goto comme Solution
à partir de GCC 4.5 (OP utilise 4.6.x) il y a un support pour asm goto
gabarits d'assemblage étendus. asm goto
vous permet de spécifier des cibles de saut que l'inline l'assemblée peut utiliser:
6.45.2.7 Étiquettes Goto
asm goto permet au Code d'assemblage de passer à une ou plusieurs étiquettes C. La section GotoLabels dans une instruction goto asm contient une liste séparée par des virgules de toutes les étiquettes C auxquelles le code assembleur peut sauter. GCC suppose que l'exécution asm tombe jusqu'à la prochaine instruction (si ce n'est pas le cas, envisagez d'utiliser l'intrinsèque __builtin_unreachable après l'instruction asm). L'optimisation de goto asm peut être améliorée en utilisant les attributs d'étiquette chaude et froide (voir attributs D'étiquette).
une instruction goto asm ne peut pas avoir de sortie. Ceci est dû à une restriction interne du compilateur: les instructions de transfert de contrôle ne peuvent pas avoir de sorties. Si le code de l'assembleur modifie quelque chose, utilisez le clobber "mémoire" pour forcer les optimiseurs à vider toutes les valeurs de registre en mémoire et les recharger si nécessaire après l'instruction asm.
Aussi notez qu'une déclaration asm goto est toujours implicitement considérée comme volatile.
pour faire référence à une étiquette dans le modèle de l'assembleur, il faut la préfixer par ‘%l’ (minuscule ‘L’) suivi de sa position (base zéro) dans les GotoLabels plus le nombre d'opérandes d'entrée. Par exemple, si l'asm a trois entrées et fait référence à deux étiquettes, se référer à la première étiquette comme "%l3 "et la seconde comme "%l4").
alternativement, vous pouvez référencer les étiquettes en utilisant le nom d'étiquette C réel inclus dans support. Par exemple, pour référencer une étiquette nommée carry, vous pouvez utiliser ‘%l[carry]’. L'étiquette doit toujours être listée dans la section GotoLabels lors de l'utilisation de cette approche.
Le code peut être écrit de cette manière:
int main(void) {
__asm__ goto ("jmp %l[exit]" :::: exit);
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
nous pouvons utiliser asm goto
. Je préfère __asm__
asm
puisqu'il ne lancera pas d'avertissements en compilant avec -ansi
ou -std=?
options.
Après les sabots, vous pouvez lister les cibles de saut que l'assemblage en ligne peut utiliser. C ne sait pas réellement si nous sautons ou pas puisque GCC n'analyse pas le code réel dans le modèle d'assemblage en ligne. Il ne peut pas supprimer ce saut, ni supposer que ce qui vient après est du code mort. En utilisant Godbolt avec GCC 4.6.4 le unoptimized code (coupé) ressemble à:
main:
pushl %ebp
movl %esp, %ebp
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
movl , %eax
popl %ebp
ret
Godbolt avec GCC 4.6.4 sortie a toujours l'air correct et apparaît sous la forme:
main:
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
xorl %eax, %eax
ret
ce mécanisme devrait également fonctionner si vous avez des optimisations on ou off, et cela ne devrait pas avoir d'importance si vous compilez pour des cibles x86 64-bit ou 32-bit.
Autres Observations
Lorsqu'il n'y a pas de contraintes de sortie dans un modèle d'assemblage en ligne étendu, le
asm
l'énoncé est implicitement volatile. La ligne__asm__ __volatile__("jmp 0x0");
peut s'écrire comme:
__asm__ ("jmp 0x0");
asm goto
les énoncés sont considérés comme implicitement volatils. Ils ne nécessitent pasvolatile
modificateur.
Est-ce que cela fonctionnerait, faire en sorte que gcc ne puisse pas savoir son inaccessible
int main(void)
{
volatile int y = 1;
if (y) goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
si un compilateur pense qu'il peut vous tromper, trichez simplement en retour: (GCC seulement)
int main(void) {
{
/* Place this code anywhere in the same function, where
* control flow is known to still be active (such as at the start) */
extern volatile unsigned int some_undefined_symbol;
__asm__ __volatile__(".pushsection .discard" : : : "memory");
if (some_undefined_symbol) goto handler;
__asm__ __volatile__(".popsection" : : : "memory");
}
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Cette solution n'ajoutera pas de frais généraux supplémentaires pour des instructions sans signification, bien que cela ne fonctionne que pour GCC lorsqu'il est utilisé avec AS (comme c'est le cas par défaut).
Explication: .pushsection
change la sortie texte du compilateur vers une autre section, dans ce cas .discard
(qui est supprimé pendant la liaison par défaut). "memory"
clobber empêche GCC d'essayer de déplacer d'autres textes à l'intérieur du l'article qui sera mis au rebut. Cependant, GCC ne se rend pas compte (et ne pourrait jamais parce que le __asm__
__volatile__
) que tout ce qui se passe entre les deux énoncés sera écarté.
some_undefined_symbol
, qui est littéralement juste un symbole qui n'est jamais défini (ou est en fait défini, il ne devrait pas). Et puisque la section de code qui l'utilise sera éliminée pendant la liaison, elle ne produira pas non plus d'erreurs de référence non résolues.
Enfin, l' le saut conditionnel à l'étiquette que vous voulez faire apparaître comme si elle était accessible fait exactement cela. En plus du fait qu'il n'apparaîtra pas du tout dans le binaire de sortie, GCC se rend compte qu'il ne peut rien savoir à propos de some_undefined_symbol
, ce qui signifie qu'elle n'a pas d'autre choix que de supposer que les deux branches de la fi sont accessibles, ce qui signifie qu'en ce qui la concerne, le flux de contrôle peut continuer à la fois en atteignant goto exit
, ou en sautant handler
(même s'il n'y aura aucun code qui pourrait même faire cet)
Cependant, soyez prudent lorsque vous activez la collecte des ordures dans votre linker ld --gc-sections
(il est désactivé par défaut), parce que sinon, il pourrait avoir l'idée de se débarrasser de la encore inutilisé étiquette indépendamment.
modifier: Oubliez tout ça. Faites simplement ceci:
int main(void) {
__asm__ __volatile__ goto("" : : : : handler);
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Je n'ai jamais entendu parler d'un moyen d'empêcher gcc de supprimer le code inaccessible; il semble que peu importe ce que vous faites, une fois que gcc détecte le code inaccessible, il le supprime toujours (utilisez gcc -Wunreachable-code
option pour voir ce qu'il considère être inaccessible).
cela dit, vous pouvez toujours mettre ce code dans une fonction statique et il ne sera pas optimisé:
static int func()
{
__asm__ __volatile__("jmp x0");
}
int main(void)
{
goto exit;
handler:
func();
exit:
return 0;
}
P. S
Cette solution est particulièrement pratique si vous voulez éviter la redondance lorsque implantation du même bloc de code" handler " à plus d'un endroit du code original.
gcc peut dupliquer les déclarations asm à l'intérieur des fonctions et les supprimer lors de l'optimisation (même à-O0), de sorte que cela ne fonctionnera jamais de manière fiable.
une façon de faire cela de manière fiable est d'utiliser un mondial de l'asm instruction (une instruction asm en dehors de toute fonction). gcc le copiera directement sur la sortie et vous pouvez utiliser des étiquettes globales sans aucun problème.