Dessiner un personnage dans la mémoire VGA avec l’assemblage en ligne GNU C

J’apprends à faire de la programmation VGA de bas niveau sous DOS avec C et assemblage en ligne. En ce moment, je suis en train de créer une fonction qui affiche un caractère à l’écran.

Ceci est mon code:

//This is the characters BITMAPS uint8_t characters[464] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x20,0x00,0x20,0x00,0x50, 0x50,0x00,0x00,0x00,0x00,0x00,0x50,0xf8,0x50,0x50,0xf8,0x50,0x00,0x20,0xf8,0xa0, 0xf8,0x28,0xf8,0x00,0xc8,0xd0,0x20,0x20,0x58,0x98,0x00,0x40,0xa0,0x40,0xa8,0x90, 0x68,0x00,0x20,0x40,0x00,0x00,0x00,0x00,0x00,0x20,0x40,0x40,0x40,0x40,0x20,0x00, 0x20,0x10,0x10,0x10,0x10,0x20,0x00,0x50,0x20,0xf8,0x20,0x50,0x00,0x00,0x20,0x20, 0xf8,0x20,0x20,0x00,0x00,0x00,0x00,0x00,0x60,0x20,0x40,0x00,0x00,0x00,0xf8,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x08,0x10,0x20,0x40,0x80, 0x00,0x70,0x88,0x98,0xa8,0xc8,0x70,0x00,0x20,0x60,0x20,0x20,0x20,0x70,0x00,0x70, 0x88,0x08,0x70,0x80,0xf8,0x00,0xf8,0x10,0x30,0x08,0x88,0x70,0x00,0x20,0x40,0x90, 0x90,0xf8,0x10,0x00,0xf8,0x80,0xf0,0x08,0x88,0x70,0x00,0x70,0x80,0xf0,0x88,0x88, 0x70,0x00,0xf8,0x08,0x10,0x20,0x20,0x20,0x00,0x70,0x88,0x70,0x88,0x88,0x70,0x00, 0x70,0x88,0x88,0x78,0x08,0x70,0x00,0x30,0x30,0x00,0x00,0x30,0x30,0x00,0x30,0x30, 0x00,0x30,0x10,0x20,0x00,0x00,0x10,0x20,0x40,0x20,0x10,0x00,0x00,0xf8,0x00,0xf8, 0x00,0x00,0x00,0x00,0x20,0x10,0x08,0x10,0x20,0x00,0x70,0x88,0x10,0x20,0x00,0x20, 0x00,0x70,0x90,0xa8,0xb8,0x80,0x70,0x00,0x70,0x88,0x88,0xf8,0x88,0x88,0x00,0xf0, 0x88,0xf0,0x88,0x88,0xf0,0x00,0x70,0x88,0x80,0x80,0x88,0x70,0x00,0xe0,0x90,0x88, 0x88,0x90,0xe0,0x00,0xf8,0x80,0xf0,0x80,0x80,0xf8,0x00,0xf8,0x80,0xf0,0x80,0x80, 0x80,0x00,0x70,0x88,0x80,0x98,0x88,0x70,0x00,0x88,0x88,0xf8,0x88,0x88,0x88,0x00, 0x70,0x20,0x20,0x20,0x20,0x70,0x00,0x10,0x10,0x10,0x10,0x90,0x60,0x00,0x90,0xa0, 0xc0,0xa0,0x90,0x88,0x00,0x80,0x80,0x80,0x80,0x80,0xf8,0x00,0x88,0xd8,0xa8,0x88, 0x88,0x88,0x00,0x88,0xc8,0xa8,0x98,0x88,0x88,0x00,0x70,0x88,0x88,0x88,0x88,0x70, 0x00,0xf0,0x88,0x88,0xf0,0x80,0x80,0x00,0x70,0x88,0x88,0xa8,0x98,0x70,0x00,0xf0, 0x88,0x88,0xf0,0x90,0x88,0x00,0x70,0x80,0x70,0x08,0x88,0x70,0x00,0xf8,0x20,0x20, 0x20,0x20,0x20,0x00,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x88,0x88,0x88,0x88,0x50, 0x20,0x00,0x88,0x88,0x88,0xa8,0xa8,0x50,0x00,0x88,0x50,0x20,0x20,0x50,0x88,0x00, 0x88,0x50,0x20,0x20,0x20,0x20,0x00,0xf8,0x10,0x20,0x40,0x80,0xf8,0x00,0x60,0x40, 0x40,0x40,0x40,0x60,0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x30,0x10,0x10,0x10, 0x10,0x30,0x00,0x20,0x50,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8, 0x00,0xf8,0xf8,0xf8,0xf8,0xf8,0xf8}; /************************************************************************** * put_char * * Print char * **************************************************************************/ void put_char(int x ,int y,int ascii_char ,byte color){ __asm__( "push %si\n\t" "push %di\n\t" "push %cx\n\t" "mov color,%dl\n\t" //test color "mov ascii_char,%al\n\t" //test char "sub $32,%al\n\t" "mov $7,%ah\n\t" "mul %ah\n\t" "lea $characters,%si\n\t" "add %ax,%si\n\t" "mov $7,%cl\n\t" "0:\n\t" "segCS %lodsb\n\t" "mov $6,%ch\n\t" "1:\n\t" "shl $1,%al\n\t" "jnc 2f\n\t" "mov %dl,%ES:(%di)\n\t" "2:\n\t" "inc %di\n\t" "dec %ch\n\t" "jnz 1b\n\t" "add $320-6,%di\n\t" "dec %cl\n\t" "jnz 0b\n\t" "pop %cx\n\t" "pop %di\n\t" "pop %si\n\t" "retn" ); } 

Je me guide moi-même à partir de cette série de didacticiels écrits en PASCAL: http://www.joco.homeserver.hu/vgalessons/lesson8.html .

J’ai changé la syntaxe de l’assembly en fonction du compilateur gcc, mais je reçois toujours cette erreur:

 Operand mismatch type for 'lea' No such instruction 'segcs lodsb' No such instruction 'retn' 

MODIFIER:

Je travaille sur l’amélioration de mon code et au moins maintenant, je vois quelque chose à l’écran. Voici mon code mis à jour:

 /************************************************************************** * put_char * * Print char * **************************************************************************/ void put_char(int x,int y){ int char_offset; int l,i,j,h,offset; j,h,l,i=0; offset = (y<<8) + (y<<6) + x; __asm__( "movl _VGA, %%ebx;" // VGA memory pointer "addl %%ebx,%%edi;" //%di points to screen "mov _ascii_char,%%al;" "sub $32,%%al;" "mov $7,%%ah;" "mul %%ah;" "lea _characters,%%si;" "add %%ax,%%si;" //SI point to bitmap "mov $7,%%cl;" "0:;" "lodsb %%cs:(%%si);" //load next byte of bitmap "mov $6,%%ch;" "1:;" "shl $1,%%al;" "jnc 2f;" "movb %%dl,(%%edi);" //plot the pixel "2:\n\t" "incl %%edi;" "dec %%ch;" "jnz 1b;" "addl $320-6,%%edi;" "dec %%cl;" "jnz 0b;" : "=D" (offset) : "d" (current_color) ); } 

Si vous voyez l’image ci-dessus, j’essayais d’écrire la lettre “S”. Les résultats sont les pixels verts que vous voyez dans le coin supérieur gauche de l’écran. Peu importe ce que x et y atsortingbue à la fonction, il trace toujours les pixels au même endroit.

entrez la description de l'image ici

Quelqu’un peut-il m’aider à corriger mon code?

    Voir ci-dessous pour une parsing de certains problèmes spécifiquement put_char votre fonction put_char , ainsi qu’une version qui pourrait fonctionner. (Je ne suis pas sûr du remplacement du segment %cs , mais à part cela, il devrait faire ce que vous souhaitez).


    Apprendre le DOS et l’Asm 16 bits n’est pas le meilleur moyen d’apprendre asm

    Tout d’abord, DOS et 16 bits x86 sont totalement obsolètes et ne sont pas plus faciles à apprendre que la normale x86 64 bits. Même le x86 32 bits est obsolète, mais il est encore largement utilisé dans le monde Windows.

    Les codes 32 bits et 64 bits n’ont pas à se préoccuper de nombreuses limitations / complications 16 bits telles que les segments ou le choix limité des registres dans les modes d’adressage. Certains systèmes modernes utilisent des substitutions de segments pour le stockage local de threads, mais apprendre à utiliser les segments dans du code 16 bits est à peine connecté à cela.

    L’un des principaux avantages de connaître asm est le débogage / profilage / optimisation de programmes réels. Si vous voulez comprendre comment écrire du C ou un autre code de haut niveau qui peut (et le fait réellement) comstackr en asm efficace , vous allez probablement regarder la sortie du compilateur . Ce sera 64 bits (ou 32 bits). (Voir, par exemple, la conférence CppCon2017 de Matt Godbolt: “Qu’est-ce que mon compilateur a fait récemment? Déboulonner le couvercle du compilateur” qui offre une excellente introduction à la lecture de x86 pour les débutants et à la sortie du compilateur).

    La connaissance Asm est utile lorsque vous regardez les résultats des compteurs de performance qui annotent un désassemblage de votre fichier binary ( perf stat ./a.out && perf report -Mintel : voir la conférence CppCon2015 de Chandler Carruth: “Optimisation de C ++: tests de performances, processeurs et compilateurs! Oh Mon! ” ). Les optimisations agressives du compilateur signifient que regarder le nombre de cycles / cache-miss / stall par ligne source est beaucoup moins informatif que par instruction.

    De plus, pour que votre programme puisse faire quoi que ce soit, il doit soit parler directement au matériel, soit faire des appels système. L’apprentissage d’appels système DOS pour l’access aux fichiers et la saisie de l’utilisateur est une perte de temps totale (sauf pour répondre au flot continu de questions SO sur la lecture et l’impression de nombres à plusieurs chiffres dans un code 16 bits). Elles sont assez différentes des API des principaux systèmes d’exploitation actuels. Développer de nouvelles applications DOS n’est pas utile, vous devez donc apprendre une autre API (ainsi que ABI) lorsque vous arrivez à faire quelque chose avec vos connaissances asm.

    Apprendre à utiliser un simulateur 8086 est encore plus contraignant: 186, 286 et 386 ont ajouté de nombreuses instructions pratiques comme imul ecx, 15 , ce qui rend ax moins “spécial”. En vous limitant aux seules instructions qui fonctionnent avec 8086, cela signifie que vous découvrirez de “mauvaises” façons de faire les choses. Les autres plus gros sont movzx / movsx , movsx d’un nombre immédiat (autre que 1) et push immediate . Outre les performances, il est également plus facile d’écrire du code lorsque ceux-ci sont disponibles, car il n’est pas nécessaire d’écrire une boucle pour effectuer un décalage de plus d’un bit.


    Suggestions pour mieux apprendre à vous apprendre

    J’ai surtout appris à lire la sortie du compilateur, puis à faire de petits changements. Je n’ai pas essayé d’écrire des choses en asm quand je ne comprenais pas vraiment les choses, mais si vous voulez apprendre rapidement (plutôt que de simplement développer une compréhension lors du débogage / profilage C), vous devez probablement tester votre compréhension en écrire votre propre code. Vous devez comprendre les bases, qu’il existe 8 ou 16 registres entiers + les indicateurs et le pointeur d’instruction, et que chaque instruction apporte une modification bien définie à l’état architectural actuel de la machine. (Voir le manuel Intel insn ref pour une description complète de chaque instruction (liens dans le wiki x86 , avec beaucoup plus de choses intéressantes ).

    Vous voudrez peut-être commencer par des choses simples, telles que l’écriture d’une seule fonction dans asm, dans le cadre d’un programme plus volumineux. Comprendre le type d’asm nécessaire pour passer des appels système est utile, mais dans les programmes réels, il est généralement utile de n’écrire qu’à la main asm pour les boucles internes qui n’impliquent aucun appel système. Il est fastidieux d’écrire en asm pour lire les résultats en entrée et les résultats d’impression. Je suggère donc de faire cette partie en C. Assurez-vous de lire la sortie du compilateur et de comprendre ce qui se passe, ainsi que la différence entre un entier et une chaîne, et ce strtol et printf font, même si vous ne les écrivez pas vous-même.

    Une fois que vous pensez en savoir assez sur les bases, trouvez une fonction dans un programme que vous connaissez bien et / ou qui vous intéresse, et voyez si vous pouvez battre le compilateur et sauvegarder les instructions (ou utiliser des instructions plus rapides). Ou implémentez-le vous-même sans utiliser la sortie du compilateur comme sharepoint départ, selon ce que vous trouvez le plus intéressant. Cette réponse pourrait être intéressante, bien que l’objective principal ait été de trouver une source C qui permette au compilateur de produire l’ASM optimal.

    Comment essayer de résoudre ses propres problèmes (avant de poser une question SO)

    Il y a beaucoup de questions SO de gens qui demandent “comment puis-je faire X en asm”, et la réponse est généralement “la même chose que vous le feriez en C”. Ne vous laissez pas prendre au piège par une inconnue, vous oubliez comment programmer. Déterminez ce qui doit arriver aux données sur lesquelles la fonction fonctionne, puis déterminez comment le faire dans asm. Si vous êtes bloqué et que vous devez poser une question, vous devriez avoir une implémentation fonctionnelle, avec juste une partie que vous ne savez pas quelles instructions utiliser pour une étape.

    Vous devriez le faire avec 32 ou 64 bits x86. Je suggérerais 64 bits, car l’ABI est plus agréable, mais les fonctions 32 bits vous obligeront à utiliser davantage la stack. Cela peut donc vous aider à comprendre comment une instruction call place l’adresse de retour sur la stack et où se trouvent les arguments poussés par l’appelant. (Cela semble être ce que vous avez essayé d’éviter en utilisant inline asm).


    La programmation matérielle directe est soignée, mais pas une compétence généralement utile

    Apprendre à faire des graphiques en modifiant directement la RAM vidéo n’est pas utile, sinon pour satisfaire la curiosité sur le fonctionnement des ordinateurs. Vous ne pouvez utiliser cette connaissance pour rien. Les API graphiques modernes existent pour permettre à plusieurs programmes de dessiner dans leurs propres régions de l’écran, et pour permettre l’indirection (par exemple, dessiner sur une texture plutôt que sur l’écran, de façon à ce que les alt-tab 3D retournant des fenêtres puissent sembler fantaisistes). Il y a trop de raisons de lister ici pour ne pas dessiner directement sur la RAM vidéo.

    Dessiner sur un tampon pixmap puis utiliser une API graphique pour le copier à l’écran est possible. Malgré tout, il est plus ou moins obsolète de faire des graphiques bitmap, sauf si vous générez des images au format PNG, JPEG ou quelque chose du genre (par exemple, optimisation de la conversion des emplacements d’histogramme en nuage de points dans le code d’arrière-plan d’un service Web). Les API graphiques modernes font abstraction de la résolution afin que votre application puisse dessiner des éléments à une taille raisonnable, quelle que soit la taille de chaque pixel. (petit écran mais très haut rez vs grande télé à faible rez).

    C’est plutôt cool d’écrire dans la mémoire et de voir quelque chose changer à l’écran. Ou encore mieux, twigz des DEL (avec de petites résistances) aux bits de données d’un port parallèle et exécutez une instruction outb pour les activer / les désactiver. Je l’ai fait sur mon système Linux il y a bien longtemps. J’ai créé un petit programme wrapper utilisant iopl(2) et inline asm, et je l’ai exécuté en tant que root. Vous pouvez probablement faire la même chose sous Windows. Vous n’avez pas besoin de code DOS ou 16 bits pour vous familiariser avec le matériel.

    in instructions in / out , ainsi que les chargements / stockages normaux sur les E / S mappées en mémoire, et le DMA, permettent aux vrais pilotes de communiquer avec le matériel, y compris des choses bien plus compliquées que les ports parallèles. Il est amusant de savoir comment votre matériel “fonctionne réellement”, mais ne passez du temps là-dessus que si vous êtes réellement intéressé ou si vous souhaitez écrire des pilotes. L’arborescence des sources Linux comprend des pilotes pour les chargements de matériel et est souvent bien commentée. Si vous aimez lire le code autant que l’écrire, c’est un autre moyen de vous faire une idée de ce que les pilotes de lecture font lorsqu’ils parlent au matériel.

    Il est généralement bon d’avoir une idée du fonctionnement du système. Si vous souhaitez en savoir plus sur la manière dont les graphiques fonctionnaient il y a bien longtemps (avec le mode texte VGA et les octets de couleur / atsortingbut), ne vous fâchez pas. Sachez simplement que les systèmes d’exploitation modernes n’utilisent pas le mode texte VGA, vous n’apprenez même pas ce qui se passe sous le capot des ordinateurs modernes.

    Beaucoup de gens apprécient https://retrocomputing.stackexchange.com/ , en revivant une époque plus simple où les ordinateurs étaient moins complexes et ne pouvaient pas supporter autant de couches d’abstraction. Sachez simplement que c’est ce que vous faites. Je pourrais être un bon tremplin pour apprendre à écrire des pilotes pour le matériel moderne, si vous êtes certain de comprendre pourquoi vous voulez comprendre asm / matériel.


    Asm en ligne

    Vous utilisez une approche totalement incorrecte pour utiliser ASM en ligne. Vous semblez vouloir écrire des fonctions entières en asm, vous devriez donc le faire. par exemple, mettez votre code dans asmfuncs.S ou quelque chose. Utilisez .S si vous souhaitez continuer à utiliser la syntaxe GNU / AT & T; ou utilisez .asm si vous souhaitez utiliser la syntaxe Intel / NASM / YASM (ce que je recommanderais, car les manuels officiels utilisent tous la syntaxe Intel. Consultez le wiki x86 pour obtenir des guides et des manuels.)

    GNU inline asm est le moyen le plus difficile d’apprendre l’ASM . Vous devez comprendre tout ce que fait votre asm et ce que le compilateur doit savoir à ce sujet. C’est vraiment difficile de tout bien faire. Par exemple, dans votre édition, ce bloc d’inline asm modifie de nombreux registres que vous ne %ebx pas comme obstrués, y compris %ebx qui est un registre préservé des appels (donc, il est cassé même si cette fonction n’est pas insérée). Au moins, vous avez retiré le ret , afin que les choses ne se cassent pas de manière aussi spectaculaire lorsque le compilateur insère cette fonction dans la boucle qui l’appelle. Si cela semble vraiment compliqué, c’est parce que c’est le cas, et c’est l’une des raisons pour lesquelles vous ne devriez pas utiliser inline asm pour apprendre asm .

    Cette réponse à une question similaire de mal utiliser inline asm tout en essayant d’apprendre asm en premier lieu contient plus de liens sur inline asm et sur la façon de bien l’utiliser.


    Faire fonctionner ce gâchis, peut-être

    Cette partie pourrait constituer une réponse distincte, mais je la laisserai ensemble.

    Outre que votre approche est fondamentalement une mauvaise idée, il existe au moins un problème spécifique à votre fonction put_char : vous utilisez offset comme un opérande de sortie uniquement. gcc comstack très volontiers toute votre fonction en une seule instruction ret , car l’instruction asm n’est pas volatile et sa sortie n’est pas utilisée. (Les instructions asm en ligne sans sorties sont supposées volatile .)

    Je mets votre fonction sur godbolt pour pouvoir regarder l’assemblage généré par le compilateur qui l’entoure. Ce lien renvoie à la version corrigée peut-être qui fonctionne, avec des clobbers correctement déclarés, des commentaires, des nettoyages et des optimisations. Voir ci-dessous pour le même code, si ce lien externe se casse.

    J’ai utilisé gcc 5.3 avec l’option -m16 , ce qui est différent de l’utilisation d’un vrai compilateur 16 bits. Il fait toujours tout ce qui est en 32 bits (en utilisant des adresses 32 bits, des int 32 bits et des fonctions 32 bits sur la stack), mais il indique à l’assembleur que le processeur sera en mode 16 bits, ainsi il saura quand émettre la taille d’opérande et l’adresse. préfixes de taille.

    Même si vous comstackz votre version d’origine avec -O0 , le compilateur calcule offset = (y<<8) + (y<<6) + x; , mais ne le met pas dans %edi , parce que vous ne l’avez pas demandé. Le spécifier comme un autre opérande d’entrée aurait fonctionné. Après l’asim en ligne, il stocke %edi dans -12(%ebp) , où offset réside.


    Autres choses qui ne va pas avec put_char :

    Vous transmettez deux éléments ( ascii_char et current_color ) dans votre fonction par le biais de globals, au lieu d'arguments de fonction. Beurk, c'est dégueulasse. VGA et les characters sont des constantes, donc leur chargement depuis les globales n'a pas l'air si mal. Ecrire en asm signifie que vous devez ignorer les bonnes pratiques de codage uniquement lorsque cela améliore les performances. Comme l'appelant a probablement dû stocker ces valeurs dans les globales, vous ne sauvegardez rien comparé à l'appelant qui les stocke sur la stack en tant qu'arguments de fonction. Et pour x86-64, vous perdriez perf parce que l’appelant pourrait simplement les transmettre dans des registres.

    Également:

     j,h,l,i=0; // sets i=0, does nothing to j, h, or l. // gcc warns: left-hand operand of comma expression has no effect j;h;l;i=0; // equivalent to this j=h=l=i=0; // This is probably what you meant 

    De toute façon, toutes les variables locales sont inutilisées, sauf offset . Allais-tu l'écrire en C ou quelque chose?

    Vous utilisez des adresses 16 bits pour les characters , mais des modes d'adressage 32 bits pour la mémoire VGA. Je suppose que c'est intentionnel, mais je n'ai aucune idée si c'est correct. Aussi, êtes-vous sûr de devoir utiliser un remplacement CS: pour les chargements de characters ? .rodata section .rodata va-t-elle dans le segment de code? Bien que vous n'ayez pas déclaré les uint8_t characters[464] tant que const , il s'agit probablement de la section .data toute façon. Je me considère chanceux de ne pas avoir écrit de code pour un modèle de mémoire segmenté, mais cela a toujours l'air suspect.

    Si vous utilisez réellement djgpp, alors, selon le commentaire de Michael Petch, votre code fonctionnera en mode 32 bits . L'utilisation d'adresses 16 bits est donc une mauvaise idée.


    Optimisations

    Vous pouvez éviter totalement d'utiliser %ebx en procédant ainsi, au lieu de charger dans ebx puis d'append %ebx à %edi .

      "add _VGA, %%edi\n\t" // load from _VGA, add to edi. 

    Vous n'avez pas besoin de vous pour obtenir une adresse dans un registre. Vous pouvez simplement utiliser

      "mov %%ax, %%si\n\t" "add $_characters, %%si\n\t" 

    $_characters signifie que l'adresse est une constante immédiate. Nous pouvons économiser beaucoup d’instructions en combinant ceci avec le calcul précédent du décalage dans le tableau de characters des images bitmap. La forme immédiate d'opérande de imul nous permet de produire le résultat en %si en premier lieu:

      "movzbw _ascii_char,%%si\n\t" //"sub $32,%%ax\n\t" // AX = ascii_char - 32 "imul $7, %%si, %%si\n\t" "add $(_characters - 32*7), %%si\n\t" // Do the -32 at the same time as adding the table address, after multiplying // SI points to characters[(ascii_char-32)*7] // ie the start of the bitmap for the current ascii character. 

    Comme cette forme d’ imul ne conserve que la imul 16b des 16 * 16 -> 32b, les formes 2 et 3 de l’opérande imul peuvent être utilisées pour les multiplications signées ou non signées . C’est pourquoi seul imul (et non mul ) possède ces formes supplémentaires. Pour des multiplications d'opérandes plus grandes, 2 et 3 opérandes imul sont plus rapides , car il n'est pas nécessaire de stocker la moitié haute dans %[er]dx .

    Vous pourriez simplifier un peu la boucle interne, mais cela compliquerait légèrement la boucle externe: vous pouvez créer une twig sur l'indicateur zéro, comme défini par shl $1, %al , au lieu d'utiliser un compteur. Cela le rendrait également imprévisible, comme le changement de magasin par défaut pour les pixels qui ne sont pas au premier plan, de sorte que les erreurs de prédiction de twig accrues pourraient être pires que les boucles supplémentaires à ne rien faire. Cela signifierait également que vous devrez recalculer %edi dans la boucle externe à chaque fois, car la boucle interne ne s'exécutera pas un nombre de fois constant. Mais cela pourrait ressembler à:

      ... same first part of the loop as before // re-initialize %edi to first_pixel-1, based on outer-loop counter "lea -1(%%edi), %%ebx\n" ".Lbit_loop:\n\t" // map the 1bpp bitmap to 8bpp VGA memory "incl %%ebx\n\t" // inc before shift, to preserve flags "shl $1,%%al\n\t" "jnc .Lskip_store\n\t" // transparency: only store on foreground pixels "movb %%dl,(%%ebx)\n" //plot the pixel ".Lskip_store:\n\t" "jnz .Lbit_loop\n\t" // flags still set from shl "addl $320,%%edi\n\t" // WITHOUT the -6 "dec %%cl\n\t" "jnz .Lbyte_loop\n\t" 

    Notez que les bits dans les bitmaps de votre personnage vont mapper des octets dans la mémoire VGA comme {7 6 5 4 3 2 1 0} , car vous testez le bit décalé par un décalage à gauche . Donc, ça commence avec le MSB. Les bits dans un registre sont toujours "big endian". Un décalage gauche se multiplie par deux, même sur une machine little-endian telle que x86. Little-endian n'affecte que le classement des octets en mémoire, et non des bits dans un octet, ni même des octets dans les registres.


    Une version de votre fonction qui pourrait faire ce que vous vouliez.

    C'est le même que le lien godbolt.

     void put_char(int x,int y){ int offset = (y<<8) + (y<<6) + x; __asm__ volatile ( // volatile is implicit for asm statements with no outputs, but better safe than sorry. "add _VGA, %%edi\n\t" // edi points to VGA + offset. "movzbw _ascii_char,%%si\n\t" // Better: use an input operand //"sub $32,%%ax\n\t" // AX = ascii_char - 32 "imul $7, %%si, %%si\n\t" // can't fold the load into this because it's not zero-padded "add $(_characters - 32*7), %%si\n\t" // Do the -32 at the same time as adding the table address, after multiplying // SI points to characters[(ascii_char-32)*7] // ie the start of the bitmap for the current ascii character. "mov $7,%%cl\n" ".Lbyte_loop:\n\t" "lodsb %%cs:(%%si)\n\t" //load next byte of bitmap "mov $6,%%ch\n" ".Lbit_loop:\n\t" // map the 1bpp bitmap to 8bpp VGA memory "shl $1,%%al\n\t" "jnc .Lskip_store\n\t" // transparency: only store on foreground pixels "movb %%dl,(%%edi)\n" //plot the pixel ".Lskip_store:\n\t" "incl %%edi\n\t" "dec %%ch\n\t" "jnz .Lbit_loop\n\t" "addl $320-6,%%edi\n\t" "dec %%cl\n\t" "jnz .Lbyte_loop\n\t" : : "D" (offset), "d" (current_color) : "%eax", "%ecx", "%esi", "memory" // omit the memory clobber if your C never touches VGA memory, and your asm never loads/stores anywhere else. // but that's not the case here: the asm loads from memory written by C // without listing it as a memory operand (even a pointer in a register isn't sufficient) // so gcc might optimize away "dead" stores to it, or reorder the asm with loads/stores to it. ); } 

    Je n'ai pas utilisé d'opérandes de sortie factices pour laisser l'allocation des registres à la discrétion du compilateur, mais c'est une bonne idée de réduire les frais généraux liés à la mise à disposition des données aux bons endroits pour inline asm. (instructions de mov supplémentaires). Par exemple, ici il n’était pas nécessaire de forcer le compilateur à mettre offset dans %edi . Cela aurait pu être n'importe quel registre que nous n'utilisons pas déjà.