Introducción X86 Asembler
En este curso usaremos el asembler nasm porque usa la misma sintaxis que intel.
Asembler a diferencia del compilador traslada un lenguaje a código de maquina.
El asembler de Intel se llama MASM pero es propietario.
En primer lugar debemos instalar al nasm.
sudo apt-get nasm install
unc@HP:~$ sudo apt-get install nasm<
[sudo] password for dbritos:
Reading package lists... Done
Building dependency tree
Reading state information... Done
nasm is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded.
Directivas
El nasm tiene directivas parecidas al compilador de Intel, directivas son sentencias que las interpreta el asembler pero no son convertidas en codigo.
symbol
equ value
A "symbol" le asocia el valor "value". Ojo symbol no es un lugar de memoria
%
Es muy similar a ¨C¨ #define y es usada para constantes en macros
Directivas de datos
Realizan una reserva de memoria, el lugar de memoria queda etiquetado.
M1 db 23 ;etiqueta el lugar de memoria y lo
inicializa con 23 en decimal.
Ml times 11 db 23 ;repite la anterior 11 veces
M2 dw 5698 ;reserva un word y lo inicializa
a 5698 decimal
L1 dw 5A9Dh ;reserva un word y lo inicializa a
5A9D hexa
CASA dd 23456o ;reserva un doble word y lo
inicializa a 23456 en octal
pi dq 314159265359 ;reserva un cuadruple word
PPbuf resb 512 ;reserva 512 bytes de memoria.
PPbuf resw 256 ;igual que la anterior
LTable db "A" ;reserva un byte con valor ASCII
; 65
Varios db 0,1,5,7 ;reserva 4 baytes
Hello db 'Hello world,0 ;reserva una cadena inicializada
Hello world.
Algunas diferencias con MASM
Los parentices "[]" indican la direccion apuntada por.
mov al, [M2] ; copy el valor apuntado por la dirección M2 a al registro
al
Primer programa
Vamos a escribir el programa básico para escribir en pantalla " Hello World", usando las interrupciones 80 hex[1] del kernel de linux.
Para escribir el programa se puede usar cualquier editor vi, gedit o sasm.
; hello.asm a first program for nasm for Linux, Intel, gcc
;
; assemble: nasm -f elf -l hello.lst hello.asm
; link: gcc -o hello hello.o
; run: hello
; output is: Hello World
SECTION .data ; seccion de datos
msg: db "Hello World",10 ;la cadena a imprimir, 10=retorno de carro cr
len: equ $-msg ; "$" significa esta direccion
;len es un valor numerico
SECTION .text ;seccion de codigo
global main ;main es un rotulo disponible para el
;linkeador
main: ;entrada estandar para gcc
mov edx,len ;arg3, longitud de la cadena a escribir
mov ecx,msg ;arg2, puntero a la cadena
mov ebx,1 ;arg1, dispositivo a escribir panatalla
mov eax,4 ;write sysout comando a la int 80 hex
int 0x80 ;interrupcion 80 hex, llamodo al kernel
;de linux
mov ebx,0 ;codigo de salida, 0=normal
mov eax,1 ;comando de salida al sistema
int 0x80 ;interrupcion 80 hex,llamodo al kernel
;de linux
Luego se lo asembla con nasm y se lo linkea con ld o gcc en este caso usamos la llave m32 porque el codigo es de 32 bit y lo debemos linkear para una arquitectura de 64 bits.
dbritos@HP:~/Dropbox/nasm$ nasm -f elf -l hello.lst hello.asm
dbritos@HP:~/Dropbox/nasm$ gcc -m32 -o hello hello.o
dbritos@HP:~/Dropbox/nasm$ ./hello
Hello World
dbritos@HP:~/Dropbox/nasm$
En el programa anterior si estamos es un sistema x64 podemos llamar a "syscall" en vez de "int 0x80"
Debuggin
Instalamos el debugger para linux "
gdb"
dbritos@HP:~/Dropbox/nasm$ sudo apt-get install gdb
[sudo] password for dbritos:
Reading package lists... Done
Building dependency tree
Reading state information... Done
gdb is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded.
dbritos@HP:~/Dropbox/nasm$
Para debugear el programa al asemblarlo y linkearlo debemos agregar la llave -g y lanzamos el debugger con
gdb y la llave
-quiet
dbritos@HP:~/Dropbox/nasm$ nasm -g -f elf -l hello.lst hello.asm
dbritos@HP:~/Dropbox/nasm$ gcc -m32 -g -o hello hello.o
dbritos@HP:~/Dropbox/nasm$ gdb -quiet hello
Reading symbols from hello...done.
Instalamos un breakpoint en la etiqueta "main" sino al correr el programa iria hasta el final.
(gdb) break main
Breakpoint 1 at 0x80483f0
(gdb)
Corremos el programa mediante el comando "run" y este se detiene en el breackpoint de la etiqueta "main"
(gdb) run
Starting program: /home/dbritos/Dropbox/nasm/hello
Breakpoint 1, 0x080483f0 in main ()
(gdb)
Para ver el programa primero le debemos decir en que formato lo queremos ver con set dissaembly-flavor intel le decimos que el formato es "INTEL" luego ejecutamos el comando "disassembly main" con esto le decimos que va a comenzar en la etiqueta "main" con el símbolo "=>" nos señala la próxima instrucción a ejecutar.
(gdb) set disassembly-flavor intel
(gdb)
(gdb) disassemble main
Dump of assembler code for function main:
=> 0x080483f0 <+0> : mov edx,0xc
0x080483f5 <+5> : mov ecx,0x804a01c
0x080483fa <+10>: mov ebx,0x1
0x080483ff <+15>: mov eax,0x4
0x08048404 <+20>: int 0x80
0x08048406 <+22>: mov ebx,0x0
0x0804840b <+27>: mov eax,0x1
0x08048410 <+32>: int 0x80
0x08048412 <+34>: xchg ax,ax
0x08048414 <+36>: xchg ax,ax
0x08048416 <+38>: xchg ax,ax
0x08048418 <+40>: xchg ax,ax
0x0804841a <+42>: xchg ax,ax
0x0804841c <+44>: xchg ax,ax
0x0804841e <+46>: xchg ax,ax
End of assembler dump.
(gdb)
Si queremos ver los bytes el comando "x/40xb main" nos muestra 40 bytes de la memoria en formato hexadecimal a partir de la etiqueta "main"
(gdb) x/40xb main
0x80483f0 <main> : 0xba 0x0c 0x00 0x00 0x00 0xb9 0x1c 0xa0
0x80483f8 <main+8> : 0x04 0x08 0xbb 0x01 0x00 0x00 0x00 0xb8
0x8048400 <main+16>: 0x04 0x00 0x00 0x00 0xcd 0x80 0xbb 0x00
0x8048408 <main+24>: 0x00 0x00 0x00 0xb8 0x01 0x00 0x00 0x00
0x8048410 <main+32>: 0xcd 0x80 0x66 0x90 0x66 0x90 0x66 0x90
(gdb)
Para observar el contenido de los registros ejecutamos "info registers"
(gdb) info registers
eax 0x1 1
ecx 0x9458ca20 -1806120416
edx 0xffffd584 -10876
ebx 0xf7faa000 -134569984
esp 0xffffd55c 0xffffd55c
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x80483f0 0x80483f0
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb)
Si queremos ver solo un registro en hexadecimal ejecutamos "print/x $nombre del registro"
(gdb) print/x $esp
$1 = 0xffffd55c
(gdb)
Para ejecutar una sola instrucción "nexti"
(gdb) nexti
0x080483f5 in main ()
(gdb)
Alli ejecutamos las instrucciones para que carge los registros ecx y edx.
(gdb) nexti
0x080483f5 in main ()
(gdb) nexti
0x080483fa in main ()
(gdb) print/x $ecx
$2 = 0x804a01c
(gdb) print/x $edx
$3 = 0xc
(gdb)
gdb GUI
Si iniciamos el debugaer con la opcion gdb --tui arranca en modo GUI como puede verse en la siguiente figura.
Otra version de "GUI" para "gdb" es el "ddd"
1 - http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html
Un ejemplo para averiguar el nombre del procesador usando el comando "CPUID"
section .text
GLOBAL main
main:
xor eax,eax ; place 0x0 in EAX for getting the name of the processor
cpuid
shl rdx,0x20 ; shifting lower 32-bits into upper 32-bit of RDX
xor rdx,rbx ; moving EBX into EDX
push rcx ; push the string on the stack
push rdx
mov rdx, 0x10 ; since we are pushing 2 registers, the length is not more than 16 bytes.
mov rsi, rsp ; The address of the string is RSP because the string is on the stack
push 0x1 ; The system call write() has the value 0x1 in the sytem call table
pop rax
mov rdi, rax ; Since we are printing to stdout, the value of the file descriptor is also 0x1
syscall ; make the system call
mov rax, 0x3c ; We now make the exit() system call here.
xor rdi, rdi ; the argument is 0x0
syscall ; this exits the application and gives control back to the shell or the Operating system