rhide descargar c gcc x86 dos djgpp

descargar - Dibujar un personaje en la memoria VGA con el ensamblaje en línea GNU C



djgpp descargar (1)

Estoy aprendiendo a hacer una programació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 sintaxis del ensamblado de acuerdo con el compilador 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 ve la imagen de arriba, estaba tratando de escribir la letra "S". Los resultados son los píxeles verdes que ves en el lado superior izquierdo de la pantalla. No importa qué x e y le dé a la función, siempre traza los píxeles en ese mismo lugar.

¿Alguien puede ayudarme a corregir mi código?


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

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

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

El código de 32 bits y 64 bits no tiene que preocuparse por muchas limitaciones / complicaciones de 16 bits, como segmentos o opciones de registro limitadas en los modos de direccionamiento. Algunos sistemas modernos usan anulaciones de segmentos para el 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 conocer asm es para depurar / perfilar / optimizar programas reales. Si desea comprender cómo escribir C u otro código de alto nivel que pueda (y de hecho lo haga ) compilar en un sistema eficiente de asm , probablemente estará viendo la salida del compilador . Será de 64 bits (o 32 bits). (por ejemplo, vea la charla CppCon2017 de Matt Godbolt: "¿Qué ha hecho mi compilador por mí últimamente? Desatornillar la tapa del compilador", que tiene una excelente introducción a la lectura de x86 asm para principiantes totales , y para ver la salida del compilador).

El conocimiento de Asm es útil cuando se observan los resultados del contador de rendimiento que perf stat ./a.out un desensamblaje de su binario ( perf stat ./a.out && perf report -Mintel : consulte la charla CppCon2015 de Chandler Carruth: "Ajuste de C ++: puntos de referencia, CPU y compiladores. ¡Oh! Mi! " ). Las optimizaciones agresivas del compilador significan que mirar los recuentos de ciclo / falta de caché / bloqueo por línea de origen es mucho menos informativo que por instrucción.

Además, para que su programa realmente haga algo, tiene que hablar directamente con el hardware o hacer llamadas al sistema. Aprender el sistema DOS requiere acceso a los archivos y la entrada del usuario es una pérdida de tiempo completa (excepto para responder al flujo constante de preguntas SO sobre cómo leer e imprimir números de varios dígitos en código de 16 bits). Son bastante diferentes de las API en los principales sistemas operativos actuales. El desarrollo de nuevas aplicaciones de DOS no es útil, por lo que tendría que aprender otra API (así como 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 , lo que hace 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 , cambian por un conteo 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 tiene que escribir un bucle para cambiar más de 1 bit.

Sugerencias para mejores formas de enseñarte a ti mismo asm

Sobre todo aprendí asm leyendo la salida del compilador y luego haciendo 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 / perfilas C), probablemente necesites probar tu comprensión por escribiendo tu propio código. Debe comprender los conceptos básicos, que hay 8 o 16 registros enteros + las banderas 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 el wiki x86 , junto con muchas más cosas buenas ).

Es posible que desee comenzar con cosas simples como escribir una sola función en asm, como parte de un programa más grande. Comprender el tipo de asm necesario para realizar llamadas al sistema es útil, pero en programas reales normalmente solo es útil escribir asm a mano para bucles internos que no involucran ninguna llamada al sistema. Es lento escribir asm para leer los datos de entrada e impresión, por lo que te sugiero que hagas esa parte en C. Asegúrate de leer el resultado del compilador y entender lo que está sucediendo, y la diferencia entre un entero y una cadena, y qué strtol e printf hacen, incluso si no las escribe usted mismo.

Una vez que crea que comprende lo básico, encuentre una función en algún programa con el que esté familiarizado y / o le interese, y vea si puede vencer al compilador y guardar instrucciones (o usar instrucciones más rápidas). O impleméntelo usted mismo sin usar la salida del compilador como punto de partida, lo que le parezca más interesante. Esta respuesta puede ser interesante, aunque el foco allí fue encontrar la fuente C que consiguió que el compilador produjera el ASM óptimo.

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

Hay muchas preguntas SO de personas que preguntan "cómo hago X en asm", y la respuesta suele ser "la misma que lo haría en C". No te quedes tan atrapado en el desconocimiento que olvidas cómo programar. Averigüe qué debe suceder con los datos con los que opera la función, luego descubra cómo hacerlo en asm. Si se queda atascado y tiene que hacer una pregunta, debe tener la mayor parte de una implementación funcional, con solo una parte que no sabe qué instrucciones usar para un paso.

Debe hacer esto con 32 o 64 bits 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 pila. Entonces, eso podría ayudarlo a comprender cómo una instrucción de call coloca la dirección de retorno en la pila y dónde están los argumentos que la persona que llama realmente están después de eso. (Esto parece ser con lo que trataste de evitar tratar usando asm en línea).

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

Aprender a hacer gráficos modificando directamente la RAM de video no es útil, excepto para satisfacer la curiosidad acerca de cómo funcionaban las computadoras. No puedes usar ese conocimiento para nada. Existen modernas API de gráficos para permitir que varios programas dibujen en sus propias regiones de la pantalla y para permitir la indirección (por ejemplo, dibujar una textura en lugar de la pantalla directamente, de modo que la pestaña alternativa 3D que se abre por ventana puede parecer elegante). Hay muchas 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 en la pantalla. Aún así, hacer gráficos de mapa 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 contenedores de histograma en un diagrama 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 qué tan grande sea cada píxel. (pantalla de resolución pequeña pero extremadamente alta frente a TV grande a baja resolución).

Es genial escribir en la memoria y ver que algo cambia en la pantalla. O incluso mejor, conecte los LED (con resistencias pequeñas) a los bits de datos en un puerto paralelo y ejecute una instrucción outb para encenderlos / apagarlos. Lo hice en mi sistema Linux hace mucho tiempo. Hice un pequeño programa de envoltura que usaba iopl(2) e inline asm, y lo ejecuté como root. Probablemente puedas hacer algo similar en Windows. No necesita código DOS o 16 bits para mojarse los pies hablando con el hardware.

in instrucciones de out / out , y las cargas / almacenes normales para IO mapeado en memoria y DMA, son cómo los controladores reales hablan con el hardware, incluidas 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 fuentes de Linux incluye controladores para grandes cantidades 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 tener una idea de lo que hacen los controladores de lectura cuando hablan con el hardware.

En general, es bueno tener una idea de cómo funcionan las cosas bajo el capó. Si quiere aprender cómo funcionaban los gráficos hace mucho tiempo (con el modo de texto VGA y los bytes de color / atributo), entonces, enloquezca. 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 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 entender asm / hardware.

Asm en línea

Está adoptando un enfoque totalmente incorrecto para usar ASM en línea. Parece que desea escribir funciones completas en asm, por lo que debe hacer eso . Por ejemplo, ponga su código en asmfuncs.S o algo así. Use .S si desea seguir usando la sintaxis de GNU / AT&T; o use .asm si desea usar la sintaxis Intel / NASM / YASM (lo cual recomendaría, ya que todos los manuales oficiales usan la sintaxis Intel. Consulte la wiki x86 para obtener guías y manuales).

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

Esta respuesta a una pregunta similar por el mal uso de 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 que este desastre funcione, tal vez

Esta parte podría ser una respuesta separada, pero la dejaré junta.

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 offset como un operando de solo salida. gcc compila muy felizmente toda su función en una sola instrucción ret , porque la declaración asm no es volatile y su salida no se utiliza. (Se supone que las declaraciones asm en línea sin salidas son volatile ).

Puse tu función en godbolt , para poder ver qué ensamblaje genera el compilador que lo rodea. Ese enlace es a la versión fija que tal vez funcione, con clobbers, comentarios, limpiezas y optimizaciones correctamente declarados. 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 compilador real de 16 bits. Todavía hace todo de la manera de 32 bits (usando direcciones de 32 bits, int 32 bits y argumentos de función de 32 bits en la pila), 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 compila su versión original con -O0 , el compilador calcula el offset = (y<<8) + (y<<6) + x; , pero no lo pone en %edi , porque no se lo pidió. Especificarlo como otro operando de entrada habría funcionado. Después del asm en línea, almacena %edi en -12(%ebp) , donde se offset vidas.

Otras cosas mal con put_char :

ascii_char 2 cosas ( ascii_char y current_color ) en tu función a través de globales, en lugar de argumentos de función. Qué asco, eso es asqueroso. VGA y los characters son constantes, por lo que cargarlos desde globales no se ve tan mal. Escribir en asm significa que debe ignorar las buenas prácticas de codificación 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 almacenándolos en la pila como argumentos de función. Y para x86-64, estaría perdiendo rendimiento porque la persona que llama podría 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, excepto el offset . ¿Lo ibas a escribir en C o algo así?

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

Si realmente está usando djgpp, de acuerdo con el comentario de Michael Petch, su código se ejecutará en modo de 32 bits . Usar direcciones de 16 bits es, por lo tanto, una mala idea.

Optimizaciones

Puede evitar usar %ebx por completo haciendo esto, en lugar de cargarlo 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 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 previo del desplazamiento en la matriz de characters de 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] // i.e. the start of the bitmap for the current ascii character.

Dado que esta forma de imul solo mantiene el 16b bajo de la multiplicación 16 * 16 -> 32b, las formas de operandos 2 y 3 imul se pueden usar para multiplicaciones con signo o sin signo , por lo que solo imul (no mul ) tiene esas formas adicionales. Para multiplicaciones más grandes de tamaño de operando, 2 y 3 operando imul 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 cero, como se establece mediante shl $1, %al , en lugar de usar un contador. Eso también lo haría impredecible, como el salto sobre la tienda para píxeles que no son de primer plano, por lo que el aumento de las predicciones erróneas de las ramas podría ser peor que los bucles adicionales de no hacer nada. También significaría que necesitaría recalcular %edi en el bucle externo cada vez, porque el bucle interno no se ejecutaría un número 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 en sus mapas de bits de caracteres 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 desplazamiento 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 su función que podría hacer lo que pretendía.

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] // i.e. 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é operandos de salida ficticios para dejar la asignación de registros a discreción del compilador, pero esa es una buena idea para reducir la sobrecarga de obtener datos en los lugares correctos para asm en línea. (instrucciones mov adicionales). Por ejemplo, aquí no había necesidad de forzar al compilador a colocar el offset en %edi . Podría haber sido cualquier registro que no estemos usando.