Mar 14, 2020

Attention à l’écart: Faire face aux compensations dans Mono.Cecil

Read this post in English
Leia este post em Português

Attention à l’écart

S'il y a une chose que j'ai appris dans ma carrière informatique, c'est que tôt ou tard (le plus souvent la première), une connaissance plus approfondie des différentes technologies abstraites par les bibliothèques / frameworks sera nécessaire pour les utiliser efficacement et / ou résoudre des problèmes (dans le contexte de cet article, Mono.Cecil abstrait divers aspects CIL). 

La semaine dernière, alors que je travaillais sur une fonctionnalité en Cecilifier, j'ai commencé à obtenir des assemblys invalides sans aucune modification du code Cecilfier lui-même. Après quelques enquêtes j'ai compris le motif: en changeant le code de les classes de testes il y a des instructions de branchement que était plus de 128 bytes de son cibles.

Dans IL il y a deux formats des instructions de branchement : les branches courtes i et les branches longues ii. En bref, le format court utilise un octet (de -128 à 127) pour le décalage tandis que les autres utilisent 4 bytes (une plage beaucoup plus large) et Cecilifier était produit des instructions sur le format courtes indistinctement de le décalage.

Plus concrètement, le programme suivant ajoute naïvement une série d'instructions nop dans le bloc if menant au décalage de la branche (jusqu'à la fin du if) à déborder:

Lorsqu'il est exécuté, il enregistre une version modifiée d'elle-même, (dans votre dossier temp, c'est-à-dire %temp% sous Windows / /tmp sous Linux), laquelle lève une exception dès que la méthode affectée est jited (dans l'exemple, lorsque nous essayons d'exécuter la méthode Foo()).

Heureusement Mono.Cecil offre un moyen relativement simple de s'assurer que tous les branchements ont le décalage correct grâce à la méthode SimplifyMacros() (définie dans Mono.Cecil.Rocks.MethodBodyRocks) qui visite toutes les instructions de la méthode remplaçant celles qui utilisent le format court des opcodes avec le format long respectif; cela signifie que les instructions de branchement telles que br.s, beq.s, etc. sont remplacées par leurs équivalents longs br, beq, etc de le même manière que les opcodes ldarg.x sont remplacées par darg x. Puisque ces instructions utilisent des décalages de 4 octets, il est très improbable que les cibles se trouvent en dehors de la plage valide.

Après avoir fait des modifications dans la méthode, vous pouvez appeler OptimizeMacros() pour s'assurer que toutes les instructions utilisent le format le plus efficiente possible en remplaçant des instructions qui utilisent le format long par les équivalents en format court quand c’est possible.

En connaissant ces méthodes nous pouvons changer nos programmes comme suit :

Maintenant l'assembly produit n'est plus invalide !




Finalement, rappelle toi que dans  le but de produire les petites assemblys ,en général, les compilateurs émettent des instructions les plus petites possible (ou, les instructions en format court). C’est bien mais ça signifie aussi qu’en changeant une assembly (avec Mono.Cecil) c'est possible qu’une instruction de branchement devienne invalide quand on ajoute quelques instructions.


Amuse-toi.

#monocecil #cecilifier

No comments: