Author Archives: alex

Convenções de passagem de parâmetros a funções

Este artigo mostra como invocar de funções e passar parâmetros a essas funções, em assembly x86. São apresentadas as convenções do C e do Pascal.

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.

Programa “Olá Mundo” em Assembly 386

O exemplo de código que é apresentado abaixo está escrito na linguagem Assembly 386 e corre em qualquer processador compatível com o Intel 80386 ou superior (Pentium, xCore, etc.). Tem que ser compilado com o NASM em ambiente Linux.

É 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 .data                           ;section declarationmsg     db      “Olá Mundo!”,0xa        ;our dear string
len     equ     $ – msg                 ;length of our dear string

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.

Números fracionários – parte 3

Converter base (2) para base (8) ou (16) e vice-versa

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)

Números fracionários – parte 2

Números fracionários – Converter base (10) para base (b)

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
fig-5

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)

Números fracionários – parte 1

Números fracionários – Converter base (b) para base (10)

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)

 

Conversões entre base 2 e bases 8 e 16

A conversão de números entre os formatos de base 2, 8 e 16 pode ser feita por inspeção visual, sem necessidade de cálculos, uma vez que as três bases de numeração são potências de 2: 8=23 e 16=24. Por isso, agrupando 3 bits é possível convertê-los para um número octal, e agrupando 4 bits, estes podem ser convertidos num símbolo hexadecimal.

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)

Conversão de base (10) para base (b)

Vimos anteriormente como converter a representação de um número inteiro positivo de uma base (b) para a base (10). Vamos ver agora como se faz a conversão inversa.

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)

fig-1
Resultado:              10100101(2)

Exercícios:

Converter o número 1335(10) para base 2, 8 e 16.

fig-2

Em base 2: 10100110111(2)

fig-3

Em base 2: 2467(8)

fig-4

Em base 16: 537(16)

Sistemas de base 8 e 16

Seguindo o raciocínio anterior, o sistema de numeração de base 8 (sistema octal) utiliza os algarismos de 0 a 7, e a contagem natural crescente segue as mesmas regras.

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)

Sistema de numeração de base 2

Conversão de base (b) para base (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)

 

 

Objetivos deste espaço

Entender o funcionamento dos computadores, de uma perspetiva microscópica até uma perspetiva macroscópica. Reconhecimento das potencialidades e limitações de um computador. Entendimento da forma como interagem os diversos elementos da estrutura/arquitetura de um computador.