Fala
ae pessoal, para os que acompanham ou não isso aqui ... ta vendo aquilo
la encima ? la encima do post ? sim o "part.1" , isso significa que
esse post não vai tratar de tudo o'que tenho em mente sobre assembly , e
vou ressaltar a parte "i386" ... essa bagaça é mais para quem opera com
computadores com processador 32 bits ..
PS : O conteudo apresentado nesse post foi testado no linux na distribuição manjaro...
OBS
: Um conhecimento básico sobre hardware se faz necessário para o
entendimento desse post, não preciso nem falar que você vai ter que ter o
linux instalado,o Assembler NASM tambem e um linkador que pode ser o ld
e tambem um pouco de café para não dormir lendo isso...
NASM : www.linuxfromscratch.org/blfs/view/svn/general/nasm.html
LD : O ld vem por padrão na maioria das distros linux...
Agora vamos ao que interessa !
Bom ... vejo muita gente
reclamando que assembly é dificil , que é isso ou aquilo ,não consigo
entender isso assembly é menos complexa que linguagens de alto nivel e
na verdade ela é facil em sí...
Algo importante a se levar em conta ... de assembler para assembler algumas coisas mudam como de nasm para gas , tasm , masm e etc ... a arquitetura tambem muda algumas coisas como registradores dentre outros "pequenos" detalhes.
Bom , putsss , eu nem sei por onde começar , não sou nenhum "L33t0"
nesse tipo de assunto...
Vamos
começar pelos registradores ... eu não vou mencionar todos , mas os
principais que iremos estudar vão ser os processadores 32 bits...
EAX - ( Acumulador )
EBX - ( Base )
ECX - ( Contador )
EDX - ( Dados )
Por ser em ordem alfabetica são faceis de decorar ( ou não ) certo ?
Outra coisa que terão que entender são os segmentos de memoria como :
section .data ( segmento aonde ficarão os dados como váriaveis "estáticas" ou não, mas a declaração de váriaveis fixas se faz sim nesse segmento )
section .text ( segmento ou espaço aonde ficara o codigo do programa )
Tendo isso em mente vamos continuar...
Agora teremos que compreender um pouco como asm funciona...
Nas
linguagens de nivel mais alto , como C , precisam ter um ponto central
ao qual estarão as instruções iniciais ... no caso do C é o main() ...
No assembly esse ponto central são os chamados labels ... você devera definir o label inicial ...
Usamos a instrução global pra isso ...
Por exemplo : global _start
logo
depois colocamos no codigo "_start:" e dali pra baixo as instruções que
tiverem escritas fazem parte da label "_start" , entenderam ?
Vamos a um exemplo ...
<--code
section .data
section .text
global _start
_start:
mov eax,0x1
mov ebx,0x0
int 0x80
-->code
Então as instruções "mov eax,0x1" e "mov ebx,0x0" fazem parte da label _start...
E o int 0x80 ? int 0x80 é uma interrupção , veremos isso mais pra frente ...
O'que
é uma syscall ou system call ( chamada de sistema ) é quando o programa
solicita ao kernel um serviço ou funçao para determinada tarefa ... por
exemplo uma função que trata de exibir algo na tela ...
Vou
explicar a minhas palavras de uma forma que o mais iniciante possa
entender ... se você veio de outra linguagem de programação ... sabe
o'que um printf , getch , exit, rand e outras funções fazem não ??
Digamos que a syscall é o numero de cada função ...
Por exemplo 1 = exit ... 4 = print e por ae vai ...
Lembre-se
disso ! para nós chamarmos funções no assembly iremos precisar do
numero da tal função ! vou passar uma tabela aqui de syscalls...
http://asm.sourceforge.net/syscall.html
Bom , tendo tudo isso em mente vamos partir para as interrupções ...
Primeiramente vamos ao conceito de interrupção, concedida pela wikipedia e depois sim vou falar sobre o assunto .
" Em
Ciência da Computação, uma
interrupção é um sinal de um dispositivo que tipicamente resulta em uma troca de contextos, isto é, o
processador para de fazer o que está fazendo para atender o dispositivo que pediu a interrupção.
Computadores digitais geralmente oferecem uma maneira de iniciar rotinas de
software em resposta a eventos eletrônicos
assíncronos. Esses eventos são sinalizados para o processador através de
pedidos de interrupção (
IRQs). O processamento da interrupção compõe uma troca de contexto para uma rotina de software especificamente escrita para
tratar a interrupção. Essa rotina é chamada
rotina de serviço de interrupção, ou
tratador de interrupção (
interrupt handler)."
Caralhoooo que texto longo da wikipedia ... isso que coloquei só uma parte ;-;
Bom
, vou me por no modo ignorante e tentar lhe passar a idéia de como
funciona uma interrupção ... a interrupção irá parar o processador para
executar algo expecifico, como o seu código em sí ...
Bom
digamos que ao final de todas as instruções do assembly , colocamos uma
interrupção "int" como int 0x80 para que ele execute as instruções
acima...
Agora vamos ver algumas instruções em assembly
... a principal que iremos usar nessa primeira parte da séries de post
que irei fazer sobre assembly é a função "mov" que serve para mover
valores para dentro de registradores ...
Sua sintaxe é : mov registrador,valor
Exemplo : mov eax,0x3
Ou ... mov eax,3
// Você não é obrigado a usar hexadecimal nos valores , isso é opcional !
a instrução xor , tambem é util pois zera o valor do registrador expecificado...
Sua sintaxe é : xor registrador,registrador
Exemplo : xor eax,eax
Outra coisa ... comentarios em assembly são definidos pelo prefixo ";" por exemplo...
; comentarios
E esse prefixo taxa como comentario apenas na linha que ele está então:
; o'que tiver escrito aqui é ignora
Aqui não!
bom , agora vamos a uma base de como iremos usar os registradores...
primeiramente
, o valor de eax é a syscall que sera executada ... então se queremos
executar um exit , devemos mover o valor 1 para o eax...
os demais registradores vai de acordo com os argumentos que a função ou syscall que você expecificou recebe ...
por exemplo a função write que iremos utilizar daqui a pouco...
sys_write(unsigned int fd, const char * buf, size_t count)
Perceberam
? os proximos registradores devem receber valores relacionados aos
argumentos que devem ser passados pra função, no exemplo da write
seria...
mov eax,0x4
mov ebx,0x1 ; Isso na verdade não é necessário ser 1
mov ecx,nome-da-variavel
mov edx,numero-de-caracters-da-string-da-variavel
Por
exemplo vamos a um programa basico em assembly... um que ele
simplesmente chame a função exit e finalize o programa ! ( isso um
programa bem inutil mesmo :/ )
Nósso programa se chamara teste1.s
<--code
section .data
section .text
global _start
_start:
mov eax,0x1
mov ebx,0x0
-->code
tendo o nasm instalado use o comando abaixo para compilar...
PS : O nasm ira criar um codigo objeto com o mesmo nome e extenção .o que você deve compilar com o ld...
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste1
E logo depois executamos como vemos abaixo...
$ ./teste1
E você logo percebe que ele não retornou absolutamente porcaria nenhuma ...
Bem , foi pra isso que ele foi feito ... mas vamos continuar ...
0x1
ou 1 em decimal , é a syscall da função exit ... aliás todo o programa
escrito assembly deve conter esse código pois um programa não se fecha
sozinho, diferente de programas escritos em outras linguagens que ja
fazem isso...
Se você fizer um codigo em asm sem usar
uma instrução para finalizar ele , ele não ira retornar erro, talvez ele
simplesmente feche sozinho e retorne um warning no terminal ... depende
muito do caso ...
<--code
section .data
variavel db "ola mundo"
len equ $-msg
section .text
global _start
_start:
mov eax,0x4
mov ebx,0x0
mov ecx,variavel
mov edx,len
int 0x80
mov eax,0x1
mov ebx,0x0
int 0x80
-->code
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste
$ ./teste1
Ola mundo
$
O prefixo "db" expecifica que a váriavel ira conter uma string!
O prefixo "$-" é pra expecificar o endereço de uma variavel ao qual ele ira guardar o numero de caracters dela nessa variavel...
O codigo acima foi bem compreendido ?
Vamos partir para aos sistemas de condições como if e else das linguagens de alto nivel ???
para comparar dados no assembly utilizamos a função cmp...
ela compara o dado de um registrador com o do outro...
logo precisaremos mover um dado para um registrador com a instrução mov, nesse caso o dado que iremos comparar...
Sua sintaxe é ...
cmp registrador,registrador-a-ser-comparado
Exemplo : cmp eax,ebx
logo ele compara os dados dos 2 registradores expecificados
Esse
"dado" pode ser o endereço de um registrador ... o dado-a-ser-comparado
tambem ... você pode utilizar os registradores como variaveis =)
Para saber se um dado é igual usamos a instrução je ...
Sua sintaxe é :
je label
Como
assim label ? bom , você pode criar outras labels alem de _start , e
deve ... label são espaços aonde contem instruções a serem executadas
...
Para criar uma basta colocar um nome qualquer seguido do prefixo ":" por exemplo no codigo abaixo...
<--code
section .data
section .text
global _start
_start:
mov eax,0x1
mov ebx,0x0
int 0x80
lol:
mov eax,0x1
mov ebx,0x0
-->code
A label "lol" contem instruções que apesar de estarem escritas não seram executadas ...
OBS : Não é porquê existe uma label antes de "_start" que ela sera executada primeiro !
bom ... vamos voltar as condições ... vamos usar a label lol por exemplo...
Se queremos comparar um dado por exemplo se 1 é igual a 1... fica o seguinte codigo ...
<--code
section .data
section .text
global _start
_start:
mov edx,1
mov ecx,1
cmp edx,ecx
je lol
lol:
mov eax,0x1
mov ebx,0x0
int 0x80
-->code
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste1
$ ./teste1
$
Ele não retornou nada , então ta tudo correto ... do contrario poderia dar segmentation fault ...
Bom sobre comparação ... o je compara se são iguals ... mas e outros ?
Aqui vai uma lista dos prefixos de comparação...
je = se for igual
jne = se não for igual
jle = se for menor ou igual
jge = se for maior ou igual
jg = se for maior
jl = se for menor
Vamos
cair de cara nas labels ... bom , mudando o prefixo , vamos "saltar"
para as labels , pois é exatamente disso que iremos falar ...
Como "saltar" de um label para outra ?
Digamos que você queira executar as instruções de uma label chamada "fuck"
Como saltar para ela ? bom ... uma função que faz isso é a jmp
e sua sintaxe é : jmp label
simples não ? o codigo ficaria o seguinte com ela...
<--code
section .data
section .text
global _start
_start:
jmp fuck
fuck:
mov eax,0x1
mov ebx,0x0
int 0x80
-->
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste1
$ ./teste1
$
Ele não ira retornar nada pois só executa um exit !
Bom
caso você pule para uma label ele continuara a execução la e ficara
APENAS la , a menos que você tenha escrito la uma função que pule para
outro lugar ou que finalize o programa ...
Mas e caso você queira tornar a label inicial no caso "_start"
Pra isso existe a função ret ...
Ela não tem sintaxe , basta apenas digitar ret kkk por exemplo
<--code
section .data
section .text
global _start
_start:
jmp fuck
mov eax,0x1
mov ebx,0x0
int 0x80
fuck:
ret
-->
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste1
$ ./teste1
$
Retornou
nada novamente kkk , bom agora veremos um exemplo de um código que le o
teclado ... usaremos a syscall read ( 3 ) para isso ...
<--code
section .data
section .bss
var resb 0x4
section .text
global _start
_start:
mov eax,0x3
mov ebx,0x0
mov ecx,var
mov edx,0x4
int 0x80
mov eax,0x1
mov ebx,0x0
int 0x80
-->code
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste1
$ ./teste1
lol
$
Perceberam
que ele leu o teclado certo ? eu digitei a palavra lol ( pois ela
combina comigo ) e logo ela vou enviada para a variavel "var" que está
no segmento bss...
mas section .bss ? que segmento é
esse ... bom digamos que é o segmento aonde você podera declarar
váriaveis para alocar dados e vamos deixar assim nesse primeiro momento (
eu explico da forma que melhor pode ser compreendida , não significa
que o'que eu falo seja como deveria ser explicado... )
a section .bss deve ficar antes da section .text ... é bom lembrar disso okay ?
Vamos fazer agora um programa que peça para digitarmos algo e leia oque digitarmos e imprima novamente...
<--code
section .data
imprimir db "insira algo: "
imprimirlen equ $-imprimir
section .bss
var resb 0x4
section .text
global _start
_start:
mov eax,0x4
mov ebx,0x0
mov ecx,imprimir
mov edx,imprimirlen
int 0x80
mov eax,0x3
mov ebx,0x0
mov ecx,var
mov edx,0x4
int 0x80
mov eax,0x4
mov ebx,0x0
mov ecx,var
mov edx,4
int 0x80
mov eax,0x1
mov ebx,0x0
int 0x80
-->code
$ nasm -f elf32 teste1.s
$ ld teste1.o -o teste1
insira algo: ola
ola
$
Bom
, vocês viram que executou normalmente ... tem outras funções como
add,sub, inc e etc ... que não vou falar nesse post , porque ja está
granda pra caramba e porque ja são 2 da manhã e ainda to aqui ...
escrevendo ...
Se você leu até aqui ... poxa , valeu
cara ! de verdade ... fico pensando se existe alguem que leu até aqui
kkkk, vamos parando de drama.. é isso vou ficando por aqui ...
Fontes de estudo:
Link1 :
www.nasm.us/
Cara
... sobre nasm , nem tenho o'que falar , não preciso por fontes de
estudo ... basta procurar a documentação da linguagem ... livros eu não
conheço muitos , eu aprendi lendo docs e alguns artigos expecificos ...
desculpem mesmo :/
Achou algo incorreto ? Nós informe Aqui!
- Achou que tinha terminado aqui ? hahaha
Você não entendeu nada até aqui ? vamos entender a base de um programa em asm só pra deixar tudo claro , basicamente isso aqui é um resumão de tudo que falamos até agora .
section .data
; daqui pra baixo é o espaço para declarar variaveis , vamos a um exemplo ?
; vamos declarar uma variavel contando a string "ola mundo-unix"
section .data
mundounix db "ola mundo-unix"
Pronto , declaramos , simples não ? :P
section .text
; daqui pra baixo fica o código do seu programa a ser executado =p
; simples tambem não ?
global _start
; Label ( espaço ) aonde ficarão as instruções do seu programa
; No caso devemos definir a label depois ... quer um exemplo em C ?
<--code
int contar(){
}
int main(){
}
-->code
o código acima contem 2 funções né ? pense nelas como labels ... é exatamente isso no assembly , oq muda eq chamamos de label mesmo kkkk
E para definirmos a label inicial global _start, assim como somos obrigados a definir o main() no C !
logo ficaria assim
global _start
_start:
; daqui pra baixo deve conter o código da label _start ( label principal )
Agora a interrupção ou int 0x80 ... que alguns podem não ter entendido ... incialmente pense no int 0x80 ou int 80h como uma instrução para executar o código que você escreveu acima !
Exemplo...
mov eax,0x1
mov ebx,0x0
int 0x80
; Isso executa as 2 instruções mov e as executa !!!
Facil não ??? pense no int 0x80 apenas como algo para executar seu código...
Vamos a um exemplo final ...
<--code
section .data
variavel db "ola mundo"
section .text
global_start
_start:
mov eax,0x4 ; syscall write q é usada para escrever algo na tela
mov ebx,0x1 ; Define o modo de video se não me engano rsrs (stdout = 1)
mov ecx,variavel ; nome da variavel a ser exibida
mov edx,0x9 ; numero de caracters da variavel
int 0x80 ; executa tudo acima
mov eax,0x1 ; mov o valor 1 para eax ( que serve para chamar a syscall exit )
mov ebx,0x0 ; move o valor 0 para ebx... isso não é necessário !
int 0x80 ; executa as instruções acima
-->code
É isso, espero que tenham entendido como é a estrutura de um programa em asm ...
Tenham uma boa noite ... e pra mim tambem , pois ja são 2 da madrugada :/ hehe nos vemos em uma segunda parte desse post .