Dibujar un personaje en la memoria VGA con GNU C inline assembly

Estoy aprendiendo a hacer una progtwigción VGA de bajo nivel en DOS con C y ensamblaje en línea. En este momento estoy tratando de crear una función que imprima un personaje en la pantalla.

Este es mi código:

//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" ); } 

Me estoy guiando de esta serie de tutoriales escritos en PASCAL: http://www.joco.homeserver.hu/vgalessons/lesson8.html .

Cambié la syntax del ensamblado de acuerdo con el comstackdor de gcc, pero sigo recibiendo estos errores:

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

EDITAR:

He estado trabajando para mejorar mi código y al menos ahora veo algo en la pantalla. Aquí está mi código actualizado:

 /************************************************************************** * 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 ves la imagen de arriba, intentaba escribir la letra “S”. Los resultados son los píxeles verdes que se ven en la parte superior izquierda de la pantalla. No importa qué xey proporcione la función, siempre traza los píxeles en ese mismo lugar.

enter image description here

¿Alguien puede ayudarme a corregir mi código?

Vea a continuación para un análisis de algunas cosas que son específicamente incorrectas con su función put_char , y una versión que podría funcionar. (No estoy seguro de la anulación del segmento %cs , pero aparte de eso, debe hacer lo que usted desea).


Aprender DOS y 16 bits asm no es la mejor manera de aprender asm

En primer lugar, DOS y 16-bit x86 son completamente obsoletos, y no son más fáciles de aprender que los normales de 64 bits x86. Incluso 32-bit x86 es obsoleto, pero aún se usa ampliamente en el mundo de Windows.

Los códigos de 32 bits y de 64 bits no tienen que preocuparse por muchas limitaciones / complicaciones de 16 bits, como los segmentos o la selección limitada de registros en los modos de direccionamiento. Algunos sistemas modernos usan anulaciones de segmento para almacenamiento local de subprocesos, pero aprender a usar segmentos en código de 16 bits apenas está conectado a eso.

Uno de los principales beneficios de saber asm es para depurar / perfilar / optimizar progtwigs reales. Si quiere comprender cómo escribir C u otro código de alto nivel que pueda comstackr (y realmente lo haga ) con un asm eficiente , probablemente esté mirando la salida del comstackdor . Esto será de 64 bits (o 32 bits). (por ejemplo, consulte la charla de CppCon2017 de Matt Godbolt: “¿Qué ha hecho mi comstackdor últimamente? Desenganche la tapa del comstackdor”, que tiene una excelente introducción a la lectura de x86 asm para principiantes totales , y para ver la salida del comstackdor).

El conocimiento de Asm es útil cuando se buscan resultados de contador de rendimiento al anotar un desensamblaje de su binario ( perf stat ./a.out && perf report -Mintel : vea la charla CppCon2015 de Chandler Carruth: “Ajuste C ++: Benchmarks, CPUs y comstackdores! Oh ¡Mi! ” ). Las optimizaciones agresivas del comstackdor significan que mirar los recuentos de ciclo / falta de caché / pérdida por línea de fuente son mucho menos informativos que por instrucción.

Además, para que su progtwig realmente haga algo, tiene que hablar directamente con el hardware o realizar llamadas al sistema. El aprendizaje de las llamadas al sistema DOS para el acceso a los archivos y la entrada del usuario es una completa pérdida de tiempo (excepto para responder al flujo constante de preguntas sobre cómo leer e imprimir números de varios dígitos en un código de 16 bits). Son bastante diferentes de las API en los sistemas operativos principales actuales. Desarrollar nuevas aplicaciones de DOS no es útil, por lo que tendría que aprender otra API (además de ABI) cuando llegue a la etapa de hacer algo con su conocimiento de ASM.

Aprender asm en un simulador 8086 es aún más limitante: 186, 286 y 386 agregaron muchas instrucciones convenientes como imul ecx, 15 , haciendo que ax menos “especial”. Limitarse solo a las instrucciones que funcionan en 8086 significa que descubrirá formas “malas” de hacer las cosas. Otros grandes son movzx / movsx , cambio por un recuento inmediato (que no sea 1) y push immediate . Además del rendimiento, también es más fácil escribir código cuando están disponibles, porque no es necesario escribir un ciclo para desplazarlo en más de 1 bit.


Sugerencias para mejores formas de enseñarte a ti mismo

Aprendí sobre todo el asm leyendo el resultado del comstackdor y luego realizando pequeños cambios. No intenté escribir cosas en ASM cuando realmente no entendía las cosas, pero si vas a aprender rápidamente (en lugar de solo desarrollar una comprensión mientras depuras / perfila C), probablemente necesites poner a prueba tu comprensión escribiendo tu propio código Necesita comprender los conceptos básicos, que hay 8 o 16 registros enteros + los indicadores y el puntero de instrucción, y que cada instrucción realiza una modificación bien definida del estado arquitectónico actual de la máquina. (Consulte el manual de Intel insn ref para obtener descripciones completas de cada instrucción (enlaces en la wiki x86 , junto con mucho más cosas buenas ).

Es posible que desee comenzar con cosas simples como escribir una sola función en asm, como parte de un progtwig más grande. Es útil comprender el tipo de asm necesario para realizar llamadas al sistema, pero en los progtwigs reales, normalmente solo es útil escribir asm para los bucles internos que no implican ninguna llamada al sistema. Lleva mucho tiempo escribir asm para leer los resultados de entrada e impresión, así que sugeriría hacer esa parte en C. Asegúrese de leer la salida del comstackdor y comprender qué está sucediendo, y la diferencia entre un entero y una cadena, y qué strtol y printf do, incluso si no los escribe usted mismo.

Una vez que piense que comprende lo básico, busque una función en algún progtwig que le resulte familiar y / o que le interese, y vea si puede vencer al comstackdor y guardar instrucciones (o usar instrucciones más rápidas). O impleméntelo usted mismo sin utilizar la salida del comstackdor como punto de partida, lo que le parezca más interesante. Esta respuesta puede ser interesante, aunque el foco fue encontrar una fuente C que hiciera que el comstackdor produjera el ASM óptimo.

Cómo tratar de resolver sus propios problemas (antes de hacer una pregunta de SO)

Hay muchas preguntas SO de personas que preguntan “¿cómo hago X en asm?” Y la respuesta suele ser “la misma que harías en C”. No se deje atrapar tanto por el hecho de que no esté familiarizado que olvide progtwigr. Averigüe qué necesita pasar con los datos en los que funciona la función, luego descubra cómo hacerlo en asm. Si te quedas atascado y tienes que hacer una pregunta, deberías tener la mayor parte de una implementación en funcionamiento, con solo una parte de la cual no sabes qué instrucciones usar para un paso.

Deberías hacer esto con 32 o 64bit x86. Sugeriría 64 bits, ya que el ABI es más agradable, pero las funciones de 32 bits te obligarán a hacer un mayor uso de la stack. De modo que eso podría ayudarlo a comprender cómo una instrucción de call coloca la dirección de retorno en la stack, y dónde están los argumentos que la persona que llamó en realidad está después de eso. (Esto parece ser lo que trataste de evitar usando asm en línea).


La progtwigción de hardware directamente es ordenada, pero no es una habilidad generalmente útil

Aprender cómo hacer gráficos modificando directamente la RAM de video no es útil, salvo para satisfacer la curiosidad sobre cómo funcionaban las computadoras. No puedes usar ese conocimiento para nada. Las API gráficas modernas existen para permitir que múltiples progtwigs dibujen en sus propias regiones de la pantalla, y para permitir la indirección (por ejemplo, dibujar en una textura en lugar de la pantalla directamente, por lo que la ventana alterna de inversión de ventanas en 3D puede parecer sofisticada). Hay demasiadas razones para enumerar aquí para no dibujar directamente en la RAM de video.

Es posible dibujar en un buffer de pixmap y luego usar una API de gráficos para copiarlo a la pantalla. Aún así, hacer gráficos de mapas de bits es más o menos obsoleto, a menos que esté generando imágenes para PNG o JPEG o algo así (por ejemplo, optimizar la conversión de los contenedores de histogtwigs a un diagtwig de dispersión en el código de fondo para un servicio web). Las API gráficas modernas abstraen la resolución, por lo que su aplicación puede dibujar cosas a un tamaño razonable, independientemente de cuán grande sea cada píxel. (pantalla de rez pequeña pero extremadamente alta vs. TV grande a bajo rez).

Es genial escribir en la memoria y ver algo cambiar en la pantalla. O mejor aún, conecte los LED (con pequeñas resistencias) a los bits de datos en un puerto paralelo, y ejecute una instrucción de salida para encenderlos / apagarlos. Hice esto en mi sistema Linux hace siglos. Hice un pequeño progtwig envoltorio que usaba iopl(2) y asm en línea, y lo ejecuté como root. Probablemente puedas hacer algo similar en Windows. No necesita DOS o código de 16 bits para mojarse los pies al hablar con el hardware.

out instrucciones de out / out , y las cargas / tiendas normales para IO y DMA mapeados en memoria, son la forma en que los controladores reales hablan con el hardware, incluso cosas mucho más complicadas que los puertos paralelos. Es divertido saber cómo funciona realmente su hardware, pero solo dedique tiempo si realmente está interesado o si desea escribir controladores. El árbol de fonts de Linux incluye controladores para embarcaciones de hardware y, a menudo, está bien comentado, por lo que si le gusta leer código tanto como escribir código, esa es otra forma de hacerse una idea de lo que hacen los lectores de lectura cuando hablan con el hardware.

En general, es bueno tener alguna idea de cómo funcionan las cosas bajo el capó. Si quiere aprender sobre cómo funcionaban los gráficos hace siglos (con el modo de texto VGA y los bytes de color / atributo), entonces seguro, se vuelve loco. Solo tenga en cuenta que los sistemas operativos modernos no usan el modo de texto VGA, por lo que ni siquiera está aprendiendo lo que sucede debajo del capó en las computadoras modernas.

Muchas personas disfrutan de https://retrocomputing.stackexchange.com/ , reviviendo un momento más simple cuando las computadoras eran menos complejas y no podían soportar tantas capas de abstracción. Solo ten en cuenta que eso es lo que estás haciendo. Podría ser un buen trampolín para aprender a escribir controladores para hardware moderno, si está seguro de que es por eso que quiere comprender asm / hardware.


Asm en línea

Está tomando un enfoque totalmente incorrecto para usar ASM en línea. Parece que quieres escribir funciones completas en asm, por lo que deberías hacer eso . por ejemplo, pon tu código en asmfuncs.S o algo así. Use .S si desea seguir usando la syntax GNU / AT & T; o use .asm si quiere usar la syntax Intel / NASM / YASM (que recomendaría, ya que todos los manuales oficiales usan la syntax Intel. Consulte la wiki x86 para ver las guías y los manuales).

GNU inline asm es la forma más difícil de aprender ASM . Debes entender todo lo que hace tu asm y lo que el comstackdor necesita saber al respecto. Es realmente difícil hacer las cosas bien. Por ejemplo, en su edición, ese bloque de asma en línea modifica muchos registros que no enumera como cargados, incluido %ebx que es un registro preservado de llamada (por lo que se rompe incluso si esa función no está en línea). Al menos sacaste el ret , así que las cosas no se romperán tan espectacularmente cuando el comstackdor inserte esta función en el bucle que lo llama. Si eso suena realmente complicado, es porque lo es, y es parte de por qué no deberías usar el asm en línea para aprender asm .

Esta respuesta a una pregunta similar de utilizar indebidamente asm en línea al intentar aprender asm en primer lugar tiene más enlaces sobre asm en línea y cómo usarlo bien.


Hacer funcionar este desastre, tal vez

Esta parte podría ser una respuesta separada, pero lo dejaré junto.

Además de que todo su enfoque es fundamentalmente una mala idea, hay al menos un problema específico con su función put_char : utiliza el offset como un operando de solo salida. gcc comstack muy felizmente toda su función en una única instrucción ret , porque la instrucción asm no es volatile y su resultado no se utiliza. (Se supone que las declaraciones de ASM en línea sin salidas son volatile ).

Puse tu función en godbolt , para poder ver qué ensamblaje genera el comstackdor que lo rodea. Ese enlace es para la versión fija que funciona, con palabras, comentarios, limpiezas y optimizaciones correctamente declaradas. Vea a continuación el mismo código, si ese enlace externo alguna vez se rompe.

-m16 gcc 5.3 con la opción -m16 , que es diferente de usar un comstackdor real de 16 bits. Todavía hace todo el camino de 32 bits (utilizando direcciones de 32 bits, 32 bits int y args de función de 32 bits en la stack), pero le dice al ensamblador que la CPU estará en modo de 16 bits, por lo que sabrá cuándo emitir el tamaño del operando y la dirección prefijos de tamaño.

Incluso si comstack su versión original con -O0 , el comstackdor calcula offset = (y< <8) + (y<<6) + x; , pero no lo pone en %edi , porque no se lo pidió. Especificarlo como otro operando de entrada hubiera funcionado. Después del asm en línea, almacena %edi en -12(%ebp) , donde vive el offset .


Otras cosas mal con put_char :

Usted pasa 2 cosas ( ascii_char y current_color ) en su función a través de globales, en lugar de argumentos de función. Yuck, eso es desagradable. VGA y los characters son constantes, por lo que cargarlos desde los globales no se ve tan mal. Escribir en asm significa que debe ignorar las buenas prácticas de encoding solo cuando ayuda al rendimiento en una cantidad razonable. Dado que la persona que llama probablemente tuvo que almacenar esos valores en los globales, no está guardando nada en comparación con la persona que llama que los almacena en la stack como argumentos de función. Y para x86-64, estarías perdiendo perf, porque la persona que llama podría simplemente pasarlos en registros.

También:

 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 

Todas las variables locales no se utilizan de todos modos, salvo el offset . ¿Ibas a escribirlo en C o algo así?

Utiliza direcciones de 16 bits para characters , pero modos de direccionamiento de 32 bits para memoria VGA. Supongo que esto es intencional, pero no tengo idea si es correcto. Además, ¿está seguro de que debería usar un CS: anular las cargas de los characters ? ¿ .rodata sección .rodata entra en el segmento de código? Aunque no declaró uint8_t characters[464] como const , por lo que probablemente solo esté en la sección .data todos modos. Me considero afortunado de no haber escrito el código para un modelo de memoria segmentada, pero aún parece sospechoso.

Si realmente está usando djgpp, según el comentario de Michael Petch, su código se ejecutará en modo de 32 bits . Usar direcciones de 16 bits es una mala idea.


Optimizaciones

Puede evitar usar %ebx por completo haciendo esto, en lugar de cargar en ebx y luego agregar %ebx a %edi .

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

No necesita lea para obtener una dirección en un registro. Puedes simplemente usar

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

$_characters significa la dirección como una constante inmediata. Podemos guardar muchas instrucciones combinando esto con el cálculo anterior de la compensación en la matriz de characters de los mapas de bits. La forma de operando inmediato de imul nos permite producir el resultado en %si en primer lugar:

  "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. 

Dado que esta forma de imul solo mantiene el bajo 16b de 16 * 16 -> 32b multiplicar, las formas de 2 y 3 operandos imul se pueden usar para multiplicaciones con signo o sin signo , por lo que solo imul (no mul ) tiene esas formas extra. Para multiplicaciones de tamaño de operando más grandes, 2 y 3 imul operando es más rápido , porque no tiene que almacenar la mitad alta en %[er]dx .

Podría simplificar un poco el bucle interno, pero complicaría un poco el bucle externo: podría ramificarse en el indicador de cero, establecido por shl $1, %al , en lugar de usar un contador. Eso lo haría también impredecible, como la tienda de salto para los píxeles que no son de primer plano, por lo que las lecturas erróneas de las twigs aumentadas podrían ser peores que los bucles extra de no hacer nada. También significa que necesitaría recalcular %edi en el bucle externo cada vez, porque el bucle interno no se ejecutaría una cantidad constante de veces. Pero podría verse así:

  ... 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" 

Tenga en cuenta que los bits de los mapas de bits de su personaje se asignarán a bytes en la memoria VGA, como {7 6 5 4 3 2 1 0} , porque está probando el bit desplazado por un cambio a la izquierda . Entonces comienza con el MSB. Los bits en un registro son siempre "big endian". Un desplazamiento a la izquierda se multiplica por dos, incluso en una máquina little-endian como x86. Little-endian solo afecta el orden de los bytes en la memoria, no los bits en un byte, y ni siquiera los bytes dentro de los registros.


Una versión de tu función que podría hacer lo que pretendías.

Esto es lo mismo que el enlace 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. ); } 

No utilicé los operandos de salida ficticios para dejar la asignación de registros a discreción del comstackdor, pero es una buena idea reducir la sobrecarga de obtener datos en los lugares correctos para el asm en línea. (instrucciones extra mov ) Por ejemplo, aquí no había necesidad de forzar al comstackdor a poner el offset en %edi . Podría haber sido cualquier registro que no estamos usando.