Desenhar um caractere em memory VGA com a assembly inline GNU C

Eu estou aprendendo a fazer alguma programação VGA de baixo nível em DOS com C e assembly inline. No momento estou tentando criar uma function que imprima um personagem na canvas.

Este é o meu 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" ); } 

Estou me guiando desta série de tutoriais escritos em PASCAL: http://www.joco.homeserver.hu/vgalessons/lesson8.html .

Eu mudei a syntax do assembly de acordo com o compilador gcc, mas ainda estou recebendo esses erros:

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

EDITAR:

Eu tenho trabalhado em melhorar meu código e pelo menos agora vejo algo na canvas. Aqui está o meu código atualizado:

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

Se você ver a imagem acima, eu estava tentando escrever a letra “S”. Os resultados são os pixels verdes que você vê no lado superior esquerdo da canvas. Não importa que xey forneçam o functon, ele sempre plota os pixels no mesmo ponto.

insira a descrição da imagem aqui

Alguém pode me ajudar a corrigir meu código?

Veja abaixo uma análise de algumas coisas que estão especificamente erradas com sua function put_char e uma versão que pode funcionar. (Eu não tenho certeza sobre a substituição do segmento %cs , mas além disso, ele deve fazer o que você pretende).


Aprender DOS e 16-bit asm não é a melhor maneira de aprender asm

Primeiro de tudo, o DOS e o x86 de 16 bits são completamente obsoletos e não são mais fáceis de aprender do que o normal x86 de 64 bits. Mesmo x86 de 32 bits é obsoleto, mas ainda em uso no mundo do Windows.

O código de 32 bits e 64 bits não precisa se preocupar com muitas limitações / complicações de 16 bits, como segmentos ou opções de registro limitadas nos modos de endereçamento. Alguns sistemas modernos usam substituições de segmento para armazenamento local de thread, mas aprender como usar segmentos em código de 16 bits mal é conectado a isso.

Um dos maiores benefícios em saber asm é depurar / criar perfis / otimizar programas reais. Se você quiser entender como escrever C ou outro código de alto nível que pode (e realmente faz ) compilar para um asm eficiente , você provavelmente estará olhando para a saída do compilador . Isso será de 64 bits (ou 32 bits). (Veja, por exemplo, a palestra do Matt Godbolt sobre CppCon2017: “O que meu compilador fez por mim ultimamente? Unbolting the Compiler’s Lid”, que tem uma excelente introdução à leitura do x86 asm para iniciantes e à saída do compilador).

Asm conhecimento é útil quando se olha para resultados de contador de desempenho anotando uma desassembly do seu binário ( perf stat ./a.out && perf report -Mintel : veja a palestra de CppCon2015 de Chandler Carruth: “Ajustando C ++: Benchmarks e CPUs e Compiladores! Oh Meu! ” ). As otimizações agressivas do compilador significam que observar as contagens de ciclo / falta de cache / stall por linha de origem é muito menos informativo do que por instrução.

Além disso, para que o seu programa realmente faça alguma coisa, ele precisa falar diretamente com o hardware ou fazer chamadas do sistema. O aprendizado de chamadas do sistema DOS para access a arquivos e input do usuário é um completo desperdício de tempo (exceto para responder ao stream constante de perguntas de SO sobre como ler e imprimir números de vários dígitos no código de 16 bits). Eles são bem diferentes das APIs dos principais sistemas operacionais atuais. Desenvolver novos aplicativos DOS não é útil, então você teria que aprender outra API (assim como ABI) quando chegar ao estágio de fazer algo com seu conhecimento.

Aprender asm em um simulador 8086 é ainda mais limitante: 186, 286 e 386 adicionaram muitas instruções convenientes como imul ecx, 15 , tornando o ax menos “especial”. Limitar-se a apenas instruções que funcionam no 8086 significa que você descobrirá maneiras “ruins” de fazer as coisas. Outros grandes são movzx / movsx , mudam por uma contagem imediata (diferente de 1), e push immediate . Além do desempenho, também é mais fácil escrever código quando estão disponíveis, porque você não precisa escrever um loop para mudar em mais de 1 bit.


Sugestões para melhores maneiras de ensinar a si mesmo

Eu aprendi principalmente a ler a saída do compilador, depois fazendo pequenas alterações. Eu não tentei escrever coisas quando eu realmente não entendia as coisas, mas se você vai aprender rapidamente (ao invés de apenas desenvolver um entendimento durante a debugging / criação de perfil C), você provavelmente precisará testar sua compreensão escrevendo seu próprio código. Você precisa entender o básico, que existem 8 ou 16 registradores inteiros + os sinalizadores e o ponteiro de instrução, e que cada instrução faz uma modificação bem definida no estado atual da arquitetura da máquina. (Veja o manual Intel insn ref para descrições completas de todas as instruções (links no wiki x86 , juntamente com muito mais coisas boas ).

Você pode querer começar com coisas simples, como escrever uma única function em conjunto, como parte de um programa maior. Compreender o tipo de asm necessário para fazer chamadas de sistema é útil, mas em programas reais normalmente é útil apenas escrever à mão asm for loops internos que não envolvem chamadas do sistema. É demorado escrever asim para ler os resultados de input e impressão, então sugiro fazer essa parte em C. Certifique-se de ler a saída do compilador e entender o que está acontecendo, e a diferença entre um inteiro e uma string, e o que strtol e printf , mesmo que você não os escreva.

Quando você entender o suficiente sobre o básico, encontre uma function em algum programa com o qual esteja familiarizado e / ou interessado, e veja se consegue vencer o compilador e salvar instruções (ou usar instruções mais rápidas). Ou implemente-o você mesmo sem usar a saída do compilador como ponto de partida, o que achar mais interessante. Essa resposta pode ser interessante, embora o foco fosse encontrar fonte C que fizesse o compilador produzir o ASM ótimo.

Como tentar resolver seus próprios problemas (antes de fazer uma pergunta sobre SO)

Há muitas perguntas SO de pessoas perguntando “como faço X em asm”, ea resposta é geralmente “o mesmo que você faria em C”. Não fique tão envolvido em ser desconhecido que você esqueça como programar. Descubra o que precisa acontecer com os dados em que a function opera e, em seguida, descubra como fazer isso em um asm. Se você ficar preso e tiver que fazer uma pergunta, você deve ter a maior parte de uma implementação em funcionamento, com apenas uma parte que você não sabe quais instruções usar para uma etapa.

Você deve fazer isso com 32 ou 64 bits x86. Eu sugeriria 64 bits, já que a ABI é melhor, mas as funções de 32 bits irão forçá-lo a fazer mais uso da pilha. Isso pode ajudá-lo a entender como uma instrução de call coloca o endereço de retorno na pilha, e onde os argumentos que o chamador pressionou realmente são depois disso. (Isso parece ser o que você tentou evitar ao usar asm inline).


Programar hardware diretamente é puro, mas não é uma habilidade geralmente útil

Aprender a fazer charts modificando diretamente a RAM de vídeo não é útil, a não ser para satisfazer a curiosidade sobre como os computadores costumavam trabalhar. Você não pode usar esse conhecimento para nada. APIs gráficas modernas existem para permitir que vários programas desenhem em suas próprias regiões da canvas, e para permitir a indireção indireta (por exemplo, desenhar uma textura em vez da canvas diretamente, para que a aba alt que inverte janelas possa parecer extravagante). Há muitas razões para listar aqui por não desenhar diretamente na RAM de vídeo.

Desenhar em um buffer de pixmap e, em seguida, usando uma API gráfica para copiá-lo para a canvas é possível. Ainda assim, fazer charts de bitmap é mais ou menos obsoleto, a menos que você esteja gerando imagens para PNG ou JPEG ou algo assim (por exemplo, otimize a conversão de histogramas para um gráfico de dispersão no código de back-end de um serviço da web). As APIs gráficas modernas abstraem a resolução, para que seu aplicativo possa desenhar coisas em um tamanho razoável, independentemente do tamanho de cada pixel. (pequena, mas extremamente alta canvas rez vs. grande TV em baixa rez).

É legal escrever na memory e ver algo mudar na canvas. Ou melhor ainda, conecte LEDs (com resistores pequenos) aos bits de dados em uma porta paralela e execute uma instrução outb para ligá-los / desligá-los. Eu fiz isso no meu sistema Linux há muito tempo. Fiz um pequeno programa wrapper que usava iopl(2) e inline asm e o executava como root. Você provavelmente pode fazer semelhante no Windows. Você não precisa do DOS ou do código de 16 bits para se molhar conversando com o hardware.

instruções de out / out e cargas / armazenamentos normais para E / S de mapeamento de memory, e DMA, são como os drivers reais falam com o hardware, incluindo coisas muito mais complicadas do que as portas paralelas. É divertido saber como o seu hardware “realmente” funciona, mas apenas gastar tempo com isso se você estiver realmente interessado ou quiser gravar drivers. A tree de código-fonte do Linux inclui drivers para cargas de hardware e geralmente é bem comentada, portanto, se você gosta de ler código tanto quanto escrever código, essa é outra maneira de ter uma ideia do que os drivers de leitura fazem quando conversam com o hardware.

Geralmente é bom ter uma ideia de como as coisas funcionam sob o capô. Se você quiser aprender sobre como os charts costumavam trabalhar há muito tempo (com o modo de texto VGA e bytes de cor / atributo), então, com certeza, enlouqueça. Esteja ciente de que sistemas operacionais modernos não usam o modo de texto VGA, então você não está nem aprendendo o que acontece sob o capô em computadores modernos.

Muitas pessoas gostam de https://retrocomputing.stackexchange.com/ , revivendo um momento mais simples em que os computadores eram menos complexos e não podiam suportar tantas camadas de abstração. Apenas esteja ciente de que é o que você está fazendo. Eu posso ser um bom passo para aprender a escrever drivers para hardware moderno, se você tem certeza que é por isso que você quer entender asm / hardware.


Em linha reta

Você está adotando uma abordagem totalmente incorreta para usar o ASM embutido. Você parece querer escrever funções inteiras em asm, então você deve fazer isso . Por exemplo, coloque seu código em asmfuncs.S ou algo assim. Use .S se você quiser continuar usando a syntax GNU / AT & T; ou use .asm se você quiser usar a syntax Intel / NASM / YASM (o que eu recomendaria, já que todos os manuais oficiais usam a syntax da Intel. Consulte o wiki x86 para obter manuais e manuais).

O GNU inline asm é a maneira mais difícil de aprender o ASM . Você precisa entender tudo o que seu asm faz e o que o compilador precisa saber sobre isso. É muito difícil acertar tudo. Por exemplo, em sua edição, esse bloco de asm inline modifica muitos registradores que você não lista como clobberados, incluindo %ebx que é um registro preservado de chamadas (portanto, isso é interrompido mesmo que essa function não esteja embutida). Pelo menos você tirou o ret , então as coisas não serão tão espetaculares quando o compilador include essa function no loop que a chama. Se isso soa muito complicado, é porque é, e parte do porque você não deve usar o modo inline para aprender asm .

Essa resposta a uma pergunta semelhante de usar indevidamente o inline asm ao tentar aprender asm em primeiro lugar tem mais links sobre o inline asm e como usá-lo bem.


Conseguir essa bagunça funcionando, talvez

Esta parte poderia ser uma resposta separada, mas eu vou deixar isso juntos.

Além de toda a sua abordagem ser fundamentalmente uma má idéia, há pelo menos um problema específico com sua function put_char : você usa offset como um operando somente de saída. O gcc compila muito bem toda a sua function com uma única instrução ret , porque a instrução asm não é volatile e sua saída não é usada. (Declarações asm inline sem saídas são consideradas volatile .)

Eu coloquei sua function em godbolt , então eu poderia ver qual assembly o compilador gera em torno dela. Esse link é para a versão fixa que funciona talvez, com clobbers, comentários, limpezas e otimizações declarados corretamente. Veja abaixo o mesmo código, se esse link externo quebrar.

Eu usei o gcc 5.3 com a opção -m16 , que é diferente de usar um compilador real de 16 bits. Ele ainda faz tudo do modo de 32 bits (usando 32bit endereços, 32bit int 32bit funções args na pilha), mas informa ao montador que a CPU estará no modo de 16 bits, assim ele saberá quando emitir o tamanho e o endereço do operando -preços de tamanho.

Mesmo se você compilar sua versão original com -O0 , o compilador calcula o offset = (y<<8) + (y<<6) + x; , mas não coloca em %edi , porque você não pediu. Especificá-lo como outro operando de input teria funcionado. Depois do inline asm, ele armazena %edi em -12(%ebp) , onde o offset mora.


Outras coisas erradas com put_char :

Você passa 2 coisas ( ascii_char e current_color ) para sua function através de globals, ao invés de argumentos de function. Isso é nojento. VGA e characters são constantes, então carregá-los de globals não parece tão ruim. Escrever em asm significa que você deve ignorar boas práticas de codificação apenas quando isso ajudar o desempenho em uma quantidade razoável. Como o chamador provavelmente tinha que armazenar esses valores nos globais, você não está salvando nada comparado ao chamador que os armazena na pilha como argumentos de function. E para x86-64, você estaria perdendo o desempenho porque o chamador poderia simplesmente passá-los em registradores.

Além disso:

 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 as variables ​​locais não são utilizadas, além do offset . Você ia escrever em C ou algo assim?

Você usa endereços de 16 bits para characters , mas modos de endereçamento de 32 bits para memory VGA. Eu suponho que isso é intencional, mas não tenho idéia se está correto. Além disso, você tem certeza que deve usar um CS: override para as cargas dos characters ? A seção .rodata vai para o segmento de código? Embora você não tenha declarado uint8_t characters[464] como const , provavelmente está apenas na seção .data . Eu me considero afortunado por não ter escrito código para um modelo de memory segmentada, mas isso ainda parece suspeito.

Se você estiver realmente usando o djgpp, então, de acordo com o comentário de Michael Petch, seu código será executado no modo de 32 bits . Usar endereços de 16 bits é, portanto, uma má ideia.


Otimizações

Você pode evitar usar %ebx totalmente ao fazer isso, em vez de carregar no ebx e, em seguida, adicionar %ebx a %edi .

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

Você não precisa de lea para obter um endereço em um registro. Você pode simplesmente usar

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

$_characters significa o endereço como uma constante imediata. Podemos salvar muitas instruções combinando isso com o cálculo anterior do deslocamento na matriz de characters de bitmaps. A forma de funcionamento imul do imul nos permite produzir o resultado em %si em primeiro 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. 

Uma vez que esta forma de imul apenas mantém a baixa 16b da multiplicação 16 * 16 -> 32b, as formas 2 e 3 operando podem ser usadas para multiplicações assinadas ou não assinadas , razão pela qual apenas imul (não mul ) tem essas formas extras. Para multiplicações de tamanho de operando maiores, o 2 e o 3 operando é mais rápido , porque não precisa armazenar a metade alta em %[er]dx .

Você poderia simplificar o loop interno um pouco, mas complicaria ligeiramente o loop externo: você poderia ramificar no sinalizador zero, conforme definido por shl $1, %al , em vez de usar um contador. Isso também tornaria imprevisível, como o salto sobre a loja para pixels não-em primeiro plano, de modo que o aumento das distribuições de ramificação pode ser pior do que os loops extras de do-nothing. Isso também significaria que você precisaria recalcular %edi no loop externo a cada vez, porque o loop interno não seria executado um número constante de vezes. Mas poderia parecer:

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

Note que os bits nos seus bitmaps de caractere vão mapear para bytes em memory VGA como {7 6 5 4 3 2 1 0} , porque você está testando o bit deslocado para fora por um turno esquerdo . Então começa com o MSB. Bits em um registro são sempre "big endian". Um turno esquerdo se multiplica por dois, mesmo em uma máquina little-endian como x86. Little-endian afeta apenas o ordenamento de bytes na memory, não os bits em um byte e nem mesmo os bytes dentro dos registradores.


Uma versão da sua function que pode fazer o que você pretende.

Isso é o mesmo que o link do 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. ); } 

Eu não usei operandos de saída fictícios para deixar a alocação de registradores de acordo com o critério do compilador, mas é uma boa idéia reduzir a sobrecarga de obter dados nos locais certos para as linhas sequenciais. (instruções extra mov ). Por exemplo, aqui não houve necessidade de forçar o compilador a colocar o offset em %edi . Poderia ter sido qualquer registro que já não estamos usando.