Category Archives: Assembly 386

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.