web-dev-qa-db-fra.com

Objectif de l'instruction NOP et instruction align dans l'assembly x86

Cela fait environ un an que j'ai suivi un cours d'assemblée pour la dernière fois. Dans cette classe, nous utilisions MASM avec les bibliothèques Irvine pour faciliter la programmation.

Après avoir parcouru la plupart des instructions, il a dit que l'instruction NOP ne faisait essentiellement rien et ne se souciait pas de l'utiliser. Quoi qu'il en soit, c'était à mi-parcours et il a un exemple de code qui ne fonctionnerait pas correctement, alors il nous a dit d'ajouter une instruction NOP et cela a bien fonctionné. J'ai demandé après le cours pourquoi et ce que cela avait réellement fait, et il a répondu qu'il ne savait pas.

Quelqu'un le sait?

15
alvonellos

Souvent, NOP est utilisé pour aligner les adresses d'instructions. Cela se produit généralement par exemple lors de l'écriture Shellcode pour exploiter débordement de tampon ou vulnérabilité de chaîne de format .

Supposons que vous ayez un saut relatif de 100 octets vers l'avant et apportez quelques modifications au code. Il est probable que vos modifications gâchent l'adresse de la cible de saut et, en tant que tel, vous devrez également modifier le saut relatif susmentionné. Ici, vous pouvez ajouter NOPs pour pousser l'adresse cible vers l'avant. Si vous avez plusieurs NOP entre l'adresse cible et l'instruction de saut, vous pouvez supprimer les NOP pour tirer l'adresse cible vers l'arrière.

Ce ne serait pas un problème si vous travaillez avec un assembleur qui prend en charge les étiquettes. Vous pouvez simplement faire JXX someLabel (Où JXX est un saut conditionnel) et l'assembleur remplacera le someLabel par l'adresse de cette étiquette. Cependant, si vous modifiez simplement le code machine assemblé (les opcodes réels) à la main (comme cela arrive parfois lors de l'écriture du shellcode), vous devez également modifier manuellement l'instruction de saut. Soit vous le modifiez, soit vous déplacez l'adresse de code cible à l'aide de NOPs.

Un autre cas d'utilisation de l'instruction NOP serait quelque chose appelé traîneau NOP . Essentiellement, l'idée est de créer un tableau d'instructions suffisamment grand qui ne provoque aucun effet secondaire (tel que NOP ou l'incrémentation puis la décrémentation d'un registre) mais augmente le pointeur d'instruction. Ceci est utile par exemple lorsque l'on veut passer à un certain morceau de code dont l'adresse n'est pas connue. L'astuce consiste à placer ledit traîneau NOP devant le code cible, puis à sauter quelque part vers ledit traîneau. Ce qui se passe, c'est que l'exécution se poursuit, espérons-le, à partir du tableau qui n'a pas d'effets secondaires et qu'il avance instruction par instruction jusqu'à ce qu'il atteigne le morceau de code souhaité. Cette technique est couramment utilisée dans les exploits de débordement de tampon susmentionnés et en particulier pour contrer les mesures de sécurité telles que ASLR .

Encore une autre utilisation particulière de l'instruction NOP est quand on modifie le code d'un programme. Par exemple, vous pouvez remplacer des parties de sauts conditionnels par NOPs et ainsi contourner la condition. C'est une méthode souvent utilisée lorsque " cracking" protection contre la copie des logiciels. Au plus simple, il s'agit simplement de supprimer la construction de code Assembly pour la ligne de code if(genuineCopy) ... et de remplacer les instructions par NOPs et .. Voilà! Aucun contrôle n'est effectué et la copie non authentique fonctionne!

Notez qu'en substance, les deux exemples de shellcode et de cracking font la même chose; modifier le code existant sans mettre à jour les adresses relatives des opérations qui reposent sur un adressage relatif.

37
zxcdw

Un nop peut être utilisé dans un a intervalle de retard lorsqu'aucune autre instruction ne peut être réorganisée pour y être placée.

lw   v0,4(v1)
jr   v0

Dans MIPS, ce serait un bogue car au moment où le jr lisait le registre v0, le registre v0 n'avait pas encore été chargé avec la valeur de l'instruction précédente.

La solution serait de:

lw   v0,4(v1)
nop
jr   v0
nop

Ceci remplit les emplacements de sourd après les instructions de chargement de Word et de registre de saut avec un nop afin que l'instruction de chargement de Word soit terminée avant l'exécution de la commande de registre de saut.

Pour en savoir plus - un peu sur SPARC remplissage des intervalles de retard . À partir de ce document:

Que peut-on mettre dans le slot de retard?

  • Quelques instructions utiles qui devraient être exécutées, que vous branchiez ou non.
  • Certaines instructions utiles ne fonctionnent que lorsque vous branchez (ou lorsque vous ne branchez pas), mais ne font aucun mal si elles sont exécutées dans l'autre cas.
  • Lorsque tout le reste échoue, une instruction NOP

Qu'est-ce qui NE DOIT PAS être mis dans la tranche de retard?

  • Tout ce qui définit le CC dont dépend la décision de branche. L'instruction de branchement décide immédiatement de la branchement ou non, mais elle ne fait le branchement qu'après l'instruction de retard. (Seule la branche est retardée, pas la décision.)
  • Une autre instruction de branche. (Ce qui se passe si vous faites cela n'est même pas défini! Le résultat est imprévisible!)
  • Une instruction "set". Il s'agit en réalité de deux instructions, pas d'une seule, et seulement la moitié d'entre elles seront dans le slot de retard. (L'assembleur vous en avertira.)

Notez la troisième option dans le quoi mettre dans le slot de retard. Le bug que vous avez vu était probablement quelqu'un remplissant l'une des choses qui ne doivent pas être placées dans le slot de retard. Mettre un nop à cet emplacement résoudrait alors le bogue.

Remarque: après avoir relu la question, c'était pour x86, qui n'a pas de créneaux de retard (la ramification bloque plutôt le pipeline). Ce ne serait donc pas la cause/solution du bogue. Sur les systèmes RISC, cela aurait pu être la réponse.

10
user40980

au moins une raison d'utiliser NOP est l'alignement. Les processeurs x86 lisent les données de la mémoire principale dans des blocs assez gros, et le début du bloc à lire est toujours aligné, donc si l'on a un bloc de code, ce sera beaucoup lu, ce bloc doit être aligné. Cela se traduira par peu d'accélération.

6
permeakra

Il existe un cas spécifique x86 qui n'est toujours pas décrit dans d'autres réponses: la gestion des interruptions. Pour certains styles, il peut y avoir des sections de code lorsque les interruptions sont désactivées car le code principal fonctionne avec certaines données partagées avec les gestionnaires d'interruptions, mais il est raisonnable d'autoriser les interruptions entre ces sections. Si on écrit naïvement


    STI
    CLI

cela ne traitera pas les interruptions en attente car, citant Intel:

Une fois l'indicateur IF défini, le processeur commence à répondre aux interruptions externes masquables après l'exécution de l'instruction suivante.

donc cela doit être réécrit au moins comme:


    STI
    NOP
    CLI

Dans la deuxième variante, toutes les interruptions en attente seront traitées uniquement entre NOP et CLI. (Bien sûr, il peut y avoir de nombreuses variantes alternatives, comme le doublement de l'instruction STI. Mais le NOP explicite est plus évident, du moins pour moi.)

3
Netch

Un but pour NOP (en Assemblée générale, pas seulement x86) c'est d'introduire des délais. Par exemple, vous voulez programmer un microcontrôleur qui doit sortir sur certaines LED avec un retard de 1 s. Ce délai peut être implémenté avec NOP (et branches). Bien sûr, vous pourriez utiliser un ADD ou autre chose, mais cela rendrait le code plus illisible; ou peut-être avez-vous besoin de tous les registres.

3
m3th0dman

En général sur le 80x86, les instructions NOP ne sont pas nécessaires pour la correction du programme, bien que parfois sur certaines machines un NOP stratégiquement placé puisse faire exécuter le code plus rapidement. Sur le 8086, par exemple, le code serait récupéré en morceaux de deux octets, et le processeur avait un tampon interne de "prefetch" qui pouvait contenir trois de ces morceaux. Certaines instructions s'exécuteraient plus rapidement qu'elles ne pourraient être récupérées, tandis que d'autres instructions prendraient un certain temps à s'exécuter. Pendant les instructions lentes, le processeur tenterait de remplir le tampon de prélecture, de sorte que si les quelques instructions suivantes étaient rapides, elles pouvaient être exécutées rapidement. Si l'instruction qui suit l'instruction lente démarre sur une frontière de mot paire, les six octets suivants d'instructions seront prélus; s'il démarre sur une limite d'octets impairs, seuls cinq octets seront prélus. Si je me souviens bien, un NOP a pris trois cycles et une extraction de mémoire en a pris quatre, donc si la prélecture de l'octet supplémentaire sauverait un cycle de mémoire, l'ajout d'un "NOP" pour provoquer l'instruction après un lent à démarrer sur une frontière de Word paire pourrait parfois enregistrer un cycle.

Ces problèmes d'alignement de la mémoire peuvent affecter la vitesse du programme, mais ils n'affectent généralement pas l'exactitude. D'un autre côté, il existe des problèmes liés à la prélecture sur les anciens processeurs où un NOP peut affecter l'exactitude. Si une instruction modifie un octet de code qui a déjà été préluqué, le 8086 (et je pense que les 80286 et 80386) exécutera l'instruction préchargée même si elle ne correspond plus à ce qui est en mémoire. L'ajout d'un NOP ou deux entre l'instruction qui modifie la mémoire et l'octet de code qui est modifié peut empêcher la récupération de l'octet de code jusqu'à ce qu'il ait été écrit. Notez, en passant, que de nombreux schémas de protection contre la copie exploitent ce type de comportement; notez également, cependant, que ce comportement n'est pas garanti. Différentes variantes de processeur peuvent gérer la prélecture différemment, certaines peuvent invalider les octets prélus si la mémoire à partir de laquelle ils ont été lus est modifiée, et les interruptions invalideront généralement le tampon de prélecture; le code sera récupéré à nouveau lorsque les interruptions reviendront.

3
supercat