Assembly

Agora que voc√™ j√° sabe como um bin√°rio PE √© constru√≠do, est√° na hora de entender como o c√≥digo contido em suas se√ß√Ķes de c√≥digo de fato executa. Acontece que um processador √© programado em sua f√°brica para entender determinadas sequ√™ncias de bytes como c√≥digo e executar alguma opera√ß√£o. Para entender isso, vamos fazer uma analogia com um componente muito mais simples que um processador, um circuito integrado (popularmente chamado de chip).

Um circuito integrado (CI) bastante conhecido no mundo da eletr√īnica √© o 7400 que tem o seguinte diagrama esquem√°tico:

Se você já estudou portas lógicas, vai perceber que este CI tem 4 portas NAND (AND com saída negada). Cada porta possui duas entradas e uma saída, cada uma delas conectada a seu respectivo pino/perna.

Admitindo duas entradas (A e B) e uma saída S, a tabela verdade de cada uma das portas deste CI é a seguinte:

A

B

A & B

S

0

0

0

1

1

0

0

1

0

1

0

1

1

1

1

0

Podemos dizer ent√£o que este CI faz uma √ļnica opera√ß√£o sempre, com as entradas de dados que recebe.

Se seu projeto precisasse tamb√©m de portas OR, XOR, AND, etc voc√™ precisaria comprar outros circuitos integrados, certo? Bem, uma solu√ß√£o inteligente seria utilizar um chip que fosse program√°vel. Dessa forma, voc√™ o configuraria, via software, para atuar em certos pinos como porta NAND, outros como porta OR e por a√≠ vai, de acordo com sua necessidade. Aumentando ainda mais a complexidade, temos os microcontroladores, que podem ser programados em linguagens de alto n√≠vel, contando com recursos como repeti√ß√Ķes, condicionais, e tudo que uma linguagem de programa√ß√£o completa oferece. No entanto, estes chips requerem uma reprograma√ß√£o a cada mudan√ßa no projeto, assim como se faz com o Arduino hoje em dia.

Neste sentido um microprocessador, ou simplesmente processador √© muito mais poderoso. Ao inv√©s de o usu√°rio gravar um programa nele, o pr√≥prio fabricante j√° o faz, de modo que este microprograma entenda diferentes instru√ß√Ķes para realizar diferentes opera√ß√Ķes de muito alto n√≠vel (se comparadas √†s simples opera√ß√Ķes booleanas). Sua entrada de dados tamb√©m √© muito mais flex√≠vel: ao inv√©s de entradas bin√°rias, um processador pode receber n√ļmeros bem maiores. O antigo Intel 8088 j√° possu√≠a um barramento de 8 bits, por exemplo.

Isso significa que se um processador receber em seu barramento um conjunto de bytes específico, sabe que vai precisar executar uma operação específica. À estes bytes possíveis damos o nome de opcodes. Ao conjunto dos opcodes + operandos damos o nome de instrução.

Supondo que queiramos ent√£o fazer uma opera√ß√£o OR entre os valores 0x20 e 0x18 utilizando um processador x86. Na documenta√ß√£o deste processador, constam as seguintes informa√ß√Ķes:

  • Ao receber o opcode 0xb8, os pr√≥ximos quatro bytes ser√£o um n√ļmero salvo em mem√≥ria (similar √†quela mem√≥ria M+ das calculadoras), acess√≠vel atrav√©s do byte identificador 0xc8.

  • Ao receber o opcode 0x83, seguido de um byte 0xc8 (que identifica a mem√≥ria), seguido de mais um byte, o n√ļmero armazenado na mem√≥ria identificada pelo segundo byte vai sofrer uma opera√ß√£o OR com este terceiro byte.

Precisar√≠amos ent√£o utilizar as duas instru√ß√Ķes, da seguinte forma:

B8 20 00 00 00
83 C8 18

Na primeira, que tem um total de 5 bytes, o opcode 0xb8 √© utilizado para colocar o n√ļmero de 32-bits (4 bytes) na sequ√™ncia em mem√≥ria. Como nosso n√ļmero desejado possui somente 1 byte, preenchemos os outros tr√™s com zero, respeitando o endianess.

A segunda instrução tem 3 bytes sendo que o primeiro é o opcode dela (OR), o segundo é o identificador da área de memória e o terceiro é o nosso operando 0x18, que deve sofrer o OR com o valor na área de memória indicada por C8.

Temos que concordar que criar um programa assim n√£o √© nada f√°cil. Para resolver este problema foi criada uma linguagem de programa√ß√£o, completamente presa √† arquitetura do processador (seus opcodes, suas instru√ß√Ķes), chamada Assembly. Com ela, os programadores poderiam escrever o programa acima praticamente em ingl√™s:

MOV EAX, 20
OR EAX, 18

De posse de um compilador de Assembly, chamado na época de assembler, o resultado da compilação do código-fonte acima é justamente um arquivo (objeto) que contém os opcodes e argumentos corretos para o processador alvo, onde o programa vai rodar.

Agora voc√™ sabe o motivo pelo qual um programa compilado n√£o √© compat√≠vel entre diferentes processadores, de diferentes arquiteturas. Como estes possuem instru√ß√Ķes diferentes e opcodes diferentes, n√£o h√° mesmo compatibilidade.

Perceba que Assembly √© uma linguagem leg√≠vel para humanos, diferente da linguagem de m√°quina que n√£o passa de uma "tripa de bytes". Os comandos da linguagem Assembly s√£o chamados de mnem√īnicos. No exemplo de c√≥digo acima, utilizamos dois: o MOV e o OR. Estudaremos mais mnem√īnicos em breve.