Créer une fonction C sans les instructions prolog / epilogue et RET générées par le compilateur?

Considérons cette fonction:

void foo(){ //do something } 

En assembly, cela ressemblerait à quelque chose comme ça (pas précis):

 push something ;do stuff pop something ret 

Mais je ne veux pas de ce code généré ( RET , PUSH , POP …). Je veux juste une étiquette pour un bloc de code, je dois donc me retourner moi-même:

 void bar(){ //do something asm("iret") //i want to use this function as a ISR } 

et en assembly cela ressemblerait à quelque chose comme ça:

 ; do something iret 

sans PUSH , POP ou RET . Existe-t-il des directives ou des mots-clés pré-processeurs qui me permettraient d’accomplir cela?

J’utilise GCC et NASM sous Windows et j’essaie de générer mes propres routines de service d’interruption (ISR).

Ce que vous voulez accomplir n’est pas tout à fait clair. il semble que vous souhaitiez un gestionnaire d’interruptions qui effectue la commande iret sans autres envois et pops par défaut.


GCC

En utilisant GCC (sans NASM ) quelque chose comme ceci est possible:

 /* Make C extern declarations of the ISR entry points */ extern void isr_test1(void); extern void isr_test2(void); /* Define a do nothing ISR stub */ __asm__(".global isr_test1\n" "isr_test1:\n\t" /* Other stuff here */ "iret"); /* Define an ISR stub that makes a call to a C function */ __asm__(".global isr_test2\n" "isr_test2:\n\t" "cld\n\t" /* Set direction flag forward for C functions */ "pusha\n\t" /* Save all the registers */ /* Other stuff here */ "call isr_test2_handler\n\t" "popa\n\t" /* Restore all the registers */ "iret"); void isr_test2_handler(void) { return; } 

Les instructions __asm__ base dans GCC peuvent être placées en dehors d’une fonction. Nous définissons des étiquettes pour nos routines de service d’interruption (ISR) et les .globl visibles de l’extérieur avec .globl (vous n’avez peut-être pas besoin d’une visibilité globale mais je la montre quand même).

Je crée quelques exemples de routines de service d’interruption. Un qui ne fait rien d’autre qu’un iret et l’autre qui appelle une fonction à un gestionnaire C. Nous sauvegardons tous les registres et les restaurons après. Les fonctions C exigent que l’indicateur de direction soit placé en avant; nous avons donc besoin d’un CLD avant d’appeler la fonction C. Cet exemple de code fonctionne pour les cibles 32 bits. 64 bits peuvent être réalisés en sauvegardant les registres individuellement plutôt qu’en utilisant PUSHA et POPA .

Remarque : Si vous utilisez GCC sous Windows, les noms de fonction à l’ intérieur des blocs d’assemblage devront probablement être précédés d’un _ (trait de soulignement). Cela ressemblerait à:

 /* Make C extern declarations of the ISR entry points */ extern void isr_test1(void); extern void isr_test2(void); /* Define a do nothing ISR stub */ __asm__(".global _isr_test1\n" "_isr_test1:\n\t" /* Other stuff here */ "iret"); /* Define an ISR stub that makes a call to a C function */ __asm__(".global _isr_test2\n" "_isr_test2:\n\t" "cld\n\t" /* Set direction flag forward for C functions */ "pusha\n\t" /* Save all the registers */ /* Other stuff here */ "call _isr_test2_handler\n\t" "popa\n\t" /* Restore all the registers */ "iret"); void isr_test2_handler(void) { return; } 

MSVC / MSVC ++

Les compilateurs C / C ++ de Microsoft prennent en charge l’atsortingbut naked sur les fonctions. Ils décrivent cet atsortingbut comme:

L’atsortingbut naked storage-class est une extension du langage C spécifique à Microsoft. Pour les fonctions déclarées avec l’atsortingbut naked storage-class, le compilateur génère du code sans prolog ni code d’épilog. Vous pouvez utiliser cette fonctionnalité pour écrire vos propres séquences de code prolog / épilogue à l’aide du code assembleur inline. Les fonctions nues sont particulièrement utiles pour écrire des pilotes de périphériques virtuels.

Un exemple de routine de service d’interruption pourrait être fait comme ceci:

 __declspec(naked) int isr_test(void) { /* Function body */ __asm { iret }; } 

Vous devrez gérer les problèmes de sauvegarde et de restauration des registres, en définissant vous-même l’indicateur de direction de la même manière que l’exemple de GCC ci-dessus.


GCC 7.x + a introduit l’atsortingbut d’interruption sur les cibles x86 / x86-64

Sur GCC 7.0+, vous pouvez maintenant utiliser __atsortingbute__((interrupt)) pour les fonctions. Cet atsortingbut n’a été pris en charge que récemment par les cibles x86 et x86-64:

interrompre

Utilisez cet atsortingbut pour indiquer que la fonction spécifiée est un gestionnaire d’interruption ou un gestionnaire d’exceptions (en fonction des parameters transmis à la fonction, expliqués plus loin). Le compilateur génère des séquences d’entrée et de sortie de fonction pouvant être utilisées dans un gestionnaire d’interruptions lorsque cet atsortingbut est présent. L’instruction IRET, au lieu de l’instruction RET, est utilisée pour renvoyer des gestionnaires d’interruptions. Tous les registres, à l’exception du registre EFLAGS qui est restauré par l’instruction IRET, sont conservés par le compilateur. Etant donné que GCC ne conserve pas les états MPX, SSE, MMX ni x87, l’option -mgeneral-regs-only de GCC doit être utilisée pour comstackr les gestionnaires d’interruptions et d’exceptions.

Cette méthode a encore des lacunes. Si vous souhaitez que votre code C accède au contenu d’un registre tel qu’il était au moment de l’interruption, il n’existe actuellement aucun moyen fiable de le faire avec ce mécanisme. Cela serait pratique si vous écriviez une interruption logicielle et si vous aviez besoin d’accéder aux registres pour déterminer les actions à prendre (par exemple: int 0x80 sous Linux). Un autre exemple consisterait à autoriser une interruption à afficher tout le contenu du registre à l’affichage à des fins de débogage.

J’ai trouvé une solution de contournement soignée:

Définissez la fonction dans l’assemblage mais appelez une fonction externe c:

 bits 32 global _bar extern _foo section .data section .text _bar: call _foo iret 

en C:

 void foo(){ //do your stuff here } extern void bar(); //bar is now your "naked" function 

compilé avec nasm et gcc sous windows

Je suppose que vous voulez la syntaxe d’avoir une fonction sans que le compilateur C fasse quoi que ce soit. Vous pouvez le faire en vous connectant à votre fonction d’assemblage (et il faudra ensuite bien sûr copier les éléments corrects dans la stack).

Vos routines d’assemblage ont simplement besoin d’un point d’entrée que le compilateur C appelle, par exemple:

La partie assemblage contient l’en-tête suivant:

 global my_function my_function: push r1 push r2 ; Code ret 

correspond à:

 void my_function ( int arg1, char arg2 );