# Assembly no Linux/Exemplos de uso de syscalls

Outra semelhança com os procedimentos usados para o DOS. Temos o compilador (nasm) e o linker (Id), portanto primeiro será gerado um arquivo objeto, com extensão. O e, depois, com o uso do linker Id, será transformado em um executável. Com uma pesquisa no manual do Id (man Id) você pode aprender um pouco mais sobre suas opções e recursos. Agora as opções utilizadas são somente as necessárias para gerarmos um arquivo executável.

## Hello world

Qualquer introdução a uma linguagem, compilador ou qualquer ferramenta ligada à programação não é completa sem o exemplo do hello world. Neste exemplo seguimos o seguinte raciocínio.
Temos para cada processo no Linux, três file descriptors que são criados e definidos automaticamente, que são: stdin, stdout, e stderr. Um file descriptor é um código que indica um arquivo. No nosso caso os arquivos são, respectivamente: entrada padrão do terminal. Em um programa em C podemos fazer o seguinte: write (1, “oi”, 2); que teremos como saída na tela a palavra “oi”.
Isto se explica por que escrevemos (write) no arquivo indicado pelo fd (file descriptor) número 1, uma string de 2 caracteres.
Seguindo este raciocínio, vemos que não tem uma syscall chamada print, como a maioria pode esperar, mas sim, a SYS_write. Portanto, traduzindo para assembler a linha acima, teremos impressa a string que queremos na tela.
Esta syscall pede três parâmetros a seguir: ebx com o numero do file descriptor (no nosso caso 1, stdout), ecx um ponteiro (endereço da memória) para a string e edx o tamanho da mensagem. O registro eax deve estar carregado com o valor 4, que na tabela corresponde a SYS_exit, para sair corretamente do programa, avisando o kernel para limpar os recursos usados. O registro eax é carregado com 1 (código de SYS_exit), e int 0x80 é chamada novamente, finalizando assim o programa.

## Lê parâmetros da linha de comando e os imprime

Este segundo exemplo, um pouco mais elaborado, lê os parâmetros passados pela linha de comando, limpa a tela e imprime-os. Novamente, a criatividade de ser usada para suprir nossas necessidades.
Quando executamos um programa, do tipo ./programa parm1parm2, após as inicializações do kernel, quando o controle é entregue à primeira função do programa, normalmente chamada start ou main, temos a pilha da maquina (stack) e os registros com alguns valores carregados, tais como parâmetros de linha de comando, variáveis de ambiente, em suma, o que receberíamos na função main de um programa em C:
Onde argc, é o numero de argumentos passados pela linha de comando, argv uma matriz de ponteiros para os argumentos, terminada em NULL e envp uma matriz de ponteiros para as variáveis de ambiente.
Todos estes dados estão no stack, na seguinte ordem: argc, todos os ponteiros argv, seguidos por um NULL, e todos os ponteiros envp seguidos por um NULL.
Portanto, para recuperarmos estes dados, simplesmente vamos retirando (instrução pop) os valores do stack, e tratando-os.
Reparem que no início do programa, algumas strings são definidas, tais como LF (line feed), para mudarmos de linha e uma string ANSI, com o código que limpa a tela. Não temos uma syscall que faz isso, nem faria muito sentido, visto que o terminal é apenas uma das interfaces que podemos encontrar em um ambiente Unix. N maioria dos terminais, que é comparável com ANSI, este código surte efeito. No DOS, dependendo da versão, deveríamos usar o driver ANSI.SYS no confing.sys, para que tais códigos fossem interpretados. É com uma solução semelhante a esta que fizemos o HOME, ou seja, a função que faz o cursor se posicionar nas coordenadas 0,0, canto superior esquerdo da tela.
Note que após clrs e home, já calculamos os respectivos tamanhos, pois as imprimiremos com a técnica usada no Exemplo 1, SYS_write e stdout.
Assim, com a tela limpa e o cursor em (0,0), o processamento é iniciado com a checagem do número de parâmetros, se não forem passados nenhum parâmetro, o programa deve sair imediatamente. Caso contrario, deve haver um loop que enquanto eles não terminem(testando se é NULL), é neste loop que será calculado o tamanho de cada um e impresso na tela, usando a técnica do exemplo anterior. Ao final, SYS_exit é chamada.
Verifique o uso intensivo de loops e labels, que apesar de parecerem complexos, guardam uma lógica bem simples, como usar o registro que deve passar o parâmetro de tamanho da string para SYS_write, como acumulador no loop que calcula este dado (strlen). Assim, ao final do loop, o parâmetro já esta correto e economiza mais trabalho.
Este exemplo é bem mais elaborado, mas serve para ilustrar as possibilidades existentes.

## Usando bibliotecas com código Assembly

Além de usar diretamente as syscalls, podemos também nos aproveitar das muitas bibliotecas (libraries) presentes em nosso sistema, para aliar a velocidade do Assembly com a praticidade de rotinas prontas. Basicamente, isso significa passar os argumentos no stack e chamar a rotina desejada usando call. Na hora de linkar, devem ser indicadas as bibliotecas corretas, ou no caso da libc, o Id automaticamente pode linkar da forma correta.
O procedimento para compilação muda um pouco, porque usaremos o gcc para nos ajudar a linkar e resolver as pendências de bibliotecas em vez do Id, que exigiria mais parâmetros em linhas de comando. Na realidade o gcc vai usar o Id, mas nos poupará trabalho. A única mudança neste caso, é trocar de_start para main, para que o gcc entenda que este é o ponto de entrada.

## Usando libc

Neste exemplo, simplesmente utilizamos uma chamada à função printf, da libc, para imprimir nossa string, em vez de imprimi-la caractere a caractere usando SYS_write. Quando chamamos uma função de uma biblioteca, seus parâmetros devem estar em ordem, na pilha.