Est-il légal de commencer une fonction x64 avec une instruction à un octet?

Selon le macamd64.inc de masm, rex_push_reg ,

… rex_push_reg doit être utilisé à la place de push_reg lorsqu’il apparaît comme la première instruction dans une fonction, car la norme appelante stipule que les fonctions ne doivent pas commencer par une instruction à un seul octet.

Je n’ai pas pu trouver de documentation exprimant cela, cependant. Est-ce vrai? Où est-il documenté? pourquoi est-ce le cas?

La partie opérationnelle de cette revendication semble être “la norme appelante” – quelle norme d’appel? La blague est peut-être une vieille, mais elle rest valable: le grand avantage des normes, c’est qu’il ya beaucoup de choix.

Dans ce cas, étant donné que vous parlez de MASM, nous pouvons supposer que la plate-forme cible est Windows, de sorte que la convention d’appel Windows 64 bits serait supposée, plutôt que quelque chose dans la spécification officielle AMD64. Cependant, comme vous, je ne peux rien y trouver qui parle de cette exigence.

Cependant, je pense que ce commentaire fait référence au standard interne de Microsoft conçu pour permettre l’application à chaud de binarys du système. Par “hot patching”, on entend la possibilité de corriger dynamicment les binarys en mémoire, par exemple pour appliquer une mise à jour du système, sans avoir à redémarrer.

L’exigence minimale pour que cela fonctionne est qu’il y a de la place pour une instruction JMP courte de 2 octets à patcher au début de chaque fonction. (Notez qu’un saut court permet uniquement de passer l’exécution entre -128 et +127 octets à partir du pointeur d’instruction en cours, mais cela suffit pour passer à un saut en longueur, qui se dirige ensuite vers la fonction corrigée fournie par la mise à jour. , l’instruction de saut en longueur est appliquée dans le remplissage entre les fonctions.)

Par conséquent, une fonction ne peut pas commencer par une instruction à 1 octet, car un correctif à chaud peut alors entraîner le pointeur d’instruction au milieu d’une instruction. (Pensez aux conditions de concurrence multi-threading.) Donc, la règle est que si vous voulez commencer une fonction avec une instruction de prolog comme PUSH RBP qui ne devrait normalement être que de 1 octet, vous devez append un préfixe REX de 1 octet. Ce préfixe REX inutile est ignoré par le CPU et fonctionne essentiellement comme un NOP 1 octet.

Dans les versions 32 bits, les correctifs à chaud étaient fournis par l’instruction MOV EDI, EDI 2 octets. Cela copie le registre EDI sur lui-même sans affecter les drapeaux. Il s’agit donc d’un NOP.

Pour les versions 32 bits, vous devez spécifiquement passer le commutateur /hotpatch au compilateur pour qu’il insère cette instruction. Cependant, sur les versions 64 bits, le compilateur agit toujours comme si /hotpatch avait été spécifié. Par conséquent, cette exigence que la première instruction ait 2 octets de longueur devient effectivement partie intégrante de la norme de la plate-forme.

Alors, pourquoi faire cette règle compliquée au lieu de demander au compilateur d’insérer une NOP de 2 octets au début de chaque fonction, comme dans les versions 32 bits? Eh bien, je ne peux pas dire avec certitude, mais je peux spéculer. Un problème est que MOV EDI, EDI n’est pas un NOP sur x64, car il zéros implicitement les 32 bits supérieurs du registre RDI . Vous devez choisir une instruction différente de celle du NOP, et une fois que vous avez fait cela, vous pouvez aussi repenser l’ensemble des activités. Deuxièmement, il y a un coût de performance (léger) pour avoir ce NOP là-bas, et comme la plupart des instructions en mode long ont au moins 2 octets de long, cela ne vaut guère la peine de demander une instruction NOP inutile. il suffit de quelques exceptions seulement.