Segue um exemplo de um programa em C e de uma solução equivalente em assembly.
| C | Assembly |
| int main() { int x=5, y=7, z; z = soma(x,y); } |
section .data x dd 5 y dd 7 z dd 0 |
| section .text push dword[y] push dword[x] call soma add esp, 8 mov dword[z], eax |
|
| int soma(int a, int b) { return a+b; } |
soma: push ebp mov ebp, esp mov eax, [ebp+8] add eax, [ebp+12] pop ebp ret |
Como se pode ver no exemplo acima, o C carrega os parâmetros da direita para a esquerda. Assim, o primeiro parâmetro a ser carregado em stack é o y e depois o x. Por fim, invoca a função.
Quando o controlo retorna da função, o programa de chamada tem que acertar a stack. Isso é feito, adicionando 8 unidades ao stack pointer, 4 unidades por cada parâmetro carregado, pois num máquina a 32 bits, todos os movimentos em stack são a 32 bits.
De seguida, apresenta-se um exemplo de um programa em Pascal e de uma solução equivalente em assembly.
| Pascal | Assembly |
| program somar; var x, y, z: integer;function soma(a,b: integer): integer; begin soma := a+b; end;begin x := 5; y := 7; z := soma(x,y); end. |
section .data x dd 5 y dd 7 z dd 0 |
| section .text push dword[x] push dword[y] call soma mov dword[z], eax |
|
| soma: push ebp mov ebp, esp mov eax, [ebp+12] add eax, [ebp+8] pop ebp ret 8 |
Como se pode ver no exemplo acima, o Pascal carrega os parâmetros da esquerda para a direita. Assim, o primeiro parâmetro a ser carregado em stack é o x e depois o y. Por fim, invoca a função.
Na convenção do Pascal, o acerto da stack é feito dentro da função, na instrução ret. O ret 8 adiciona 8 unidades ao stack pointer, 4 unidades por cada parâmetro carregado.
Como o acerto da stack é feito dentro da função, é obrigatório passar exatamente 2 parâmetros quando se invoca esta função.
Em C, por outro lado, como o acerto da stack é feito no programa de chamada, podemos passar o número de parâmetros que entendermos, mesmo que sejam diferentes, em número, do que a função espera.
]]>É uma programa simples que define uma variável (msg) com conteúdo de texto (string) e uma constante (len). Depois invoca o sistema operativo (int 0x80) para mandar imprimir a string e termina invocando a função EXIT.
A label _start tem que ser declarada pública (global _start) e aponta para a primeira instrução de código do programa.
section .text ;section declaration
;we must export the entry point to the ELF linker or
global _start ;loader. They conventionally recognize _start as their
;entry point. Use ld -e foo to override the default.
_start:
;write our string to stdout
mov edx,len ;third argument: message length
mov ecx,msg ;second argument: pointer to message to write
mov ebx,1 ;first argument: file handle (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
;and exit
mov ebx,0 ;first syscall argument: exit code
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
Na secção .data são declaradas as variáveis inicializadas. Neste caso, é declarada a variável msg do tipo vetor de bytes. A diretiva db significa define byte e indica que se pretende definir uma variável do tipo byte (ou seja com um tamanho de 8 bits) ou uma sequência de bytes, que é o caso da variável msg, que contém a string “Olá Mundo” seguida do caráter 0xA, ou seja, o caráter 10 (da codificação ASCII, que representa o caráter newline, de mudança de linha). [Nota: o prefixo “0x” indica que o número seguinte está no formato hexadecimal, logo 0xA = 10]
Logo abaixo da variável msg define-se a constante len. As constantes são definidas à custa da diretiva equ (equal, ou seja, igual). Neste caso, o valor da constante len é definido de forma dinâmica. O símbolo $ significa o endereço da posição onde o $ aparece. Por outro lado, uma vez que os nomes das variáveis em Assembly são todos apontadores, o nome msg contém o endereço da string “Olá Mundo”, ou seja, o endereço da letra ‘O’. Assim, a diferença $-msg contém o tamanho da string “Olá Mundo”,0xA, ou seja 10 carateres.
A secção .text é a secção de código. Como foi dito atrás, este programa executa apenas duas ações: imprime uma string no ecrã e termina a execução do programa.
Qualquer dessas ações deve ser executada pelo sistema operativo, uma vez que o processador apenas sabe efetuar cálculos e comunicar com a memória central. O processador também pode comunicar com os dispositivos periféricos, mas apenas em baixo nível: não conhece os protocolos que permitam fazer um uso útil desses periféricos (esse é o objetivo dos drivers).
Estas duas ações estão codificadas considerando o sistema operativo Linux. Para efetuá-las é necessário invocar duas funções de sistema: a função WRITE e a função EXIT.
A sintaxe de chamada destas funções pode ser representada pelo pseudocódigo seguinte:
// write - writes data via a file handle. procedure linux.write( fd:dword; var buf:var; count:linux.size_t ); begin write; linux.pushregs; mov( linux.sys_write, eax ); mov( fd, ebx ); mov( buf, ecx ); mov( count, edx ); int( $80 ); linux.popregs; end write; procedure linux._exit( status:int32 ); begin _exit; mov( [esp+4], ebx ); // Get the status value mov( linux.sys_exit, eax ); // exit opcode. int( $80 ); // Does not return! end _exit;
A instrução int 0x80 invoca o sistema operativo. O sistema operativo consulta o registo eax para saber que função de sistema foi invocada. No primeiro caso é invocada a função número 4, ou seja, a função WRITE; no segundo caso é invicada a função número 1, ou seja, a função EXIT. Nos outros registos (ebx, ecx, e edx) são passados (caso seja necessário) os restantes parâmetros necessários à execução da função.
Na função WRITE, é passado o valor 1 no registo ebx, indicando que se pretende escrever no ficheiro número 1, ou seja, no stdout (o ecrã). No registo ecx coloca-se o endereço da string a escrever, e no registo edx, o tamanho dessa string.
Na função EXIT, é passado o valor 0 (código de erro ou código de retorno) no registo ebx, indicando que o programa terminou corretamente.
]]>A conversão entre números fracionários nos formatos de base 2, 8 e 16 pode ser feita por inspeção visual, sem necessidade de cálculos, tal como a conversão de números inteiros, uma vez que as três bases de numeração são potências de 2: 8=3 e 16=24. Por isso, agrupando 3 bits fracionários é possível convertê-los para um número octal fracionário, e agrupando 4 bits fracionários, estes podem ser convertidos num símbolo hexadecimal fracionário.
No entanto os agrupamentos de bits devem ser feitos sempre a partir da vírgula: a parte inteira agrupa-se a partir da vírgula para a esquerda; a parte fracionária agrupa-se a partir da vírgula para a direita. Só desta forma é possível criar agrupamentos de bits cujo bit menos significativo esteja numa posição com valor de uma potência de 2 de expoente múltiplo de 3 ou de 4, consoante se pretenda fazer a conversão para base 8 ou 16, respetivamente.
Converter para base 8: 11101,10101(2)
| 011 | 101 | 101 | 010 |
| 3 | 5 | 5 | 2 |
11101,10101(2) = 35,52(8)
Converter para base 16: 11101,10101(2)
| 0001 | 1101 | 1010 | 1000 |
| 1 | D | A | 8 |
11101,10101(2) = 1D,A8(16)
]]>Considere-se o número em base (b): 0,a-1a-2…a-n(b)
O mesmo número pode ser representado em base 10 da seguinte forma:
a-1 x b-1 + a-2 x b-2 + … + a-n x b-n(10)
A conversão de um número fracionário de base (b) para (10) pode ser feita por multiplicação da parte fracionária dos produtos sucessivamente por b. Em cada multiplicação obtém-se, à esquerda da vírgula, um ai do número pretendido na base (b).
(a-1 x b-1 + a-2 x b-2 + … + a-n x b-n) x b
Resultado da 1ª multiplicação:
a-1 x b0 + a-2 x b-1 + … + a-n x b-n+1
O que corresponde ao número:
a-1,a-2…a-n
Depois multiplica-se apenas a parte fracionária:
0,a-2…a-n
Ou seja:
(a-2 x b-1 + … + a-n x b-n+1) x b
Resultado da 2ª multiplicação:
a-2 x b0 + a-3 x b-1 + … + a-n x b-n+2
O que corresponde ao número:
a-2,a-3…a-n
Por fim constrói-se o número, com base nos ai.
Resultado final: 0,a-1a-2…a-n
Exemplo: Passar 0,5625(10) para base 2
0,5625 x 2 = 1,125 extrai-se um 1 (inteiro)
0,125 x 2 = 0,25 extrai-se um 0 (inteiro)
0,25 x 2 = 0,5 extrai-se um 0 (inteiro)
0,5 x 2 = 1,0 extrai-se um 1 (inteiro)
0,5625(10) = 0,1001(2)
Exercício: Passar 12,3125(10) para base 2

12(10) = 1100(2)
0,3125 x 2 = 0,625
0,625 x 2 = 1,25
0,25 x 2 = 0,5
0,5 x 2 = 1,0
12,3125(10) = 1100,0101(2)
]]>165,23(10) = 1 x 102 + 6 x 101 + 5 x 100 + 2 x 10-1 + 3 x 10-2
| 102 | 101 | 100 | 10-1 | 10-2 |
| 1 | 6 | 5 | 2 | 3 |
Converter para base 10: 10110,101(2)
Lembrar que: b-n = 1/bn
10110,101(2) = 24 + 22 + 21 + 2-1 + 2-3
= 16 + 4 + 2 + 0,5 + 0,125
= 22,625(10)
Converter para base 10: 375,34(8)
375,34(8) = 3 x 82 + 7 x 81 + 5 x 80 + 3 x 8-1 + 4 x 8-2
= 192 + 56 + 5 + 0,375 + 0,0625
= 253,4375(10)
Converter para base 10: 1A7,C8(16)
1A7,C8(16) = 1 x 162 + 10 x 161 + 7 x 160 + 12 x 16-1 + 8 x 16-2
= 256 + 160 + 7 + 0,75 + 0,03125
= 423,78125(10)
]]>
De seguida apresenta-se a justificação analítica para esta afirmação.
Conversão entre base 2 e 8
Considere-se o número binário seguinte:
a3n+2a3n+1a3n…a5a4a3a2a1a0(2)
Aplicando o processo de conversão de base 2 para base 10 visto anteriormente, obtém-se a representação decimal deste número:
a3n+223n+2 + a3n+123n+1 + a3n 23n… + a525 + a424 + a323 + a222 + a121 + a020
Juntar em grupos de 3 parcelas, a começar pela direita:
(a3n+223n+2 + a3n+123n+1 + a3n 23n)… + (a525 + a424 + a323) + (a222 + a121 + a020)
Em cada grupo, fatorizar potências de 2:
(a3n+222 + a3n+121 + a3n 20).23n… + (a522 + a421 + a320).23 + (a222 + a121 + a020).20
Passar potências de 2 fatorizadas a potências de 8:
(a3n+222 + a3n+121 + a3n 20).8n… + (a522 + a421 + a320).81 + (a222 + a121 + a020).80
Somar as parcelas dentro de parêntesis:
xn8n… + x181 + x080
em que xi é um número de 0 a 7, segundo a tabela:
| base 8 | base 2 |
|---|---|
| 0 | 000 |
| 1 | 001 |
| 2 | 010 |
| 3 | 011 |
| 4 | 100 |
| 5 | 101 |
| 6 | 110 |
| 7 | 111 |
Converter de base 2 para base 8: 11011010110(2)
| 011 | 011 | 010 | 110 |
| 3 | 3 | 2 | 6 |
Para converter de base 2 para base 16, usar a tabela:
| base 16 | base 2 |
|---|---|
| 0 | 0000 |
| 1 | 0001 |
| 2 | 0010 |
| 3 | 0011 |
| 4 | 0100 |
| 5 | 0101 |
| 6 | 0110 |
| 7 | 0111 |
| 8 | 1000 |
| 9 | 1001 |
| A | 1010 |
| B | 1011 |
| C | 1100 |
| D | 1101 |
| E | 1110 |
| F | 1111 |
Converter de base 2 para base 16: 11011010110(2)
| 0110 | 1101 | 0110 |
| 6 | D | 6 |
11011010110(2) = 6D6(16)
Converter de base 16 para base 8: AB9C3(16)
Passar primeiro para base 2:
| A | B | 9 | C | 3 |
| 1010 | 1011 | 1001 | 1100 | 0011 |
AB9C3(16) = 10101011100111000011(2)
Converter de base 2 para base 8:
| 101 | 101 | 011 | 100 | 111 | 000 | 011 |
| 2 | 5 | 3 | 4 | 7 | 0 | 3 |
AB9C3(16) = 2534703(8)
]]>Considere-se o número seguinte em base (b):
Número em base (b): anan-1an-2…a2a1a0
Este número pode ser representado da seguinte forma em base (10):
an x bn + an-1 x bn-1 + an-2x bn-2… + a2 x b2 + a1 x b1 + a0 x b0
Se dividirmos este número e, depois, os cociente sucessivamente por b, obtêm-se os ai do número original.
(anbn + an-1bn-1 + an-2 bn-2… + a2b2 + a1b1 + a0b0) / b
Resultado 1ª divisão:
Cociente: anbn-1 + an-1bn-2 + an-2 bn-3… + a2b1 + a1b0
Resto: a0
Resultado 2ª divisão:
Cociente: anbn-2 + an-1bn-3 + an-2 bn-4… + a2b0
Resto: a1
Exemplo: Converter para base 2 o número 165(10)
Exercícios:
Converter o número 1335(10) para base 2, 8 e 16.
Em base 2: 10100110111(2)
Em base 2: 2467(8)
Em base 16: 537(16)
]]>Alfabeto em base 8 = {0,1,2,3,4,5,6,7}
Contagem natural crescente em base 8
0
1
2
3
4
5
6
7
10
11
12
13
Um sistema octal de contagem natural utiliza a sequência acima para efetuar uma contagem crescente. Ao chegar ao último algarismo, o 7, a contagem volta a zero e adiciona uma unidade à coluna imediatamente à esquerda dando, dessa forma, a indicação que já foi efetuada uma contagem até 8 na própria coluna. A relação de pesos entre duas colunas consecutivas é de 1 para 8.
Qual o valor decimal do número 524(8)?
|
82 |
81 |
80 |
|
5 |
2 |
4 |
524(8) = 5 x 82 + 2 x 81 + 4 x 80 = 320 + 16 + 4 = 340(10)
O sistema de numeração de base 16 (sistema hexadecimal) utiliza os algarismos de 0 a 9 e as letras de A a F, com significado crescente de 10 a 15.
Alfabeto em base 16 = {0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F}
Um sistema hexadecimal de contagem natural utiliza a sequência acima para efetuar uma contagem crescente. Ao chegar ao último símbolo, o F, a contagem volta a zero e adiciona uma unidade à coluna imediatamente à esquerda dando, dessa forma, a indicação que já foi efetuada uma contagem até 16 na própria coluna. A relação de pesos entre duas colunas consecutivas é, pois, de 1 para 16.
Contagem em base 16
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
10
11
12
13
…
Quanto vale, então, o número 2E3(16) em base 10?
|
162 |
161 |
160 |
|
2 |
E |
3 |
2E3(16) = 2 x 162 + 14 x 161 + 3 x 160 = 512 + 224 + 3 = 739(10)
]]>No sistema de numeração de base 10, o peso de cada algarismo num número depende da posição que esse algarismo ocupa. Da direita para a esquerda, temos o algarismo das unidades, o algarismo das dezenas, o algarismo das centenas, etc.
325(10) = 3 x 100 + 2 x 10 + 5 x 1 = 325(10)
No sistema de numeração de base 10, o alfabeto utilizado é composto pelos 10 símbolos seguintes:
Alfabeto em base 10 = {0,1,2,3,4,5,6,7,8,9}
Um sistema de contagem natural em base 10 utiliza a sequência acima para efetuar uma contagem crescente. Ao chegar ao último algarismo, o 9, a contagem volta a zero e adiciona uma unidade à coluna imediatamente à esquerda dando, dessa forma, a indicação que já foi efetuada uma contagem até 10 na própria coluna.
Sendo assim, a relação de pesos entre duas colunas consecutivas é de 1 para 10, logo, a expressão acima pode ser rescrita como uma soma de produtos por potências de 10:
325(10) = 3 x 102 + 2 x 101 + 5 x 100 = 325(10)
Exemplo de contagem natural crescente em base 10:
0
1
2
3
4
5
6
7
8
9
10
11
…
Da forma semelhante à base 10, no sistema de numeração de base 2, utilizam-se apenas dois símbolos – 0 e 1 – e a contagem natural crescente segue as mesmas regras.
Alfabeto em base 2 = {0,1}
Os dígitos de um sistema binário denominam-se bits. Bit é a abreviatura de binary digit, ou dígito binário.
Exemplo de contagem natural crescente em base 2:
0
1
10
11
100
101
110
…
Sempre que a contagem numa coluna atinge o valor 1 (o maior algarismo do alfabeto), a contagem nessa coluna volta a 0, e adiciona-se uma unidade à coluna imediatamente à esquerda dando a indicação que já foi efetuada uma contagem até 2 na própria coluna.
Por esta ordem de ideias, quanto vale o número 101(2)?
|
22 |
21 |
20 |
|
1 |
0 |
1 |
Num sistema binário de contagem natural, a relação de pesos entre colunas, é de 1 para 2, portanto, pode obter-se o valor decimal do número 101(2) multiplicando cada bit pelo peso que a coluna de onde provém esse bit tem.
101(2) = 1 x 22 + 0 x 21 + 1 x 20 = 4 + 0 + 1 = 5(10)
Exercício:
Converter 10011101(2) para base 10
Solução:
|
27 |
26 |
25 |
24 |
23 |
22 |
21 |
20 |
|
1 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
10011101(2) = 1 x 27 + 0 x 26 + 0 x 25 + 1 x 24 + 1 x 23
+ 1 x 22 + 0 x 21 + 1 x 20
= 27 + 24 + 23 + 22 + 20
= 128 + 16 + 8 + 4 + 1
= 157(10)
]]>