sábado, 28 de marzo de 2015

x86 Asembler scanf

x86 asembler

Para leer una cadena de caracteres desde el teclado usamos la funcion de ¨C¨ ¨scanf¨.


BITS 64
SECTION .text

extern printf
extern scanf

GLOBAL main
main:
 mov  rdi, yourname
 mov  al, 0         ; numero de argumentos en SSE
 call printf

 mov rax,  [welcome]
 mov [buf], rax

 mov  rdi, scanstring
 lea  rsi, [buf+8]
 mov  al, 0            ; numero de argumentos en SSE
 call scanf

 mov  rdi, buf
 mov  al, 0            ; numero de argumentos en SSE
 call printf

 mov  rdi, end
 mov  al, 0            ; numero de argumentos en SSE
 call printf

 ret

SECTION .bss
buf: resb 4096

SECTION .data
yourname:   db "Your name: ", 0
welcome:    db "Welcome "
end:        db "!", 10, 0
scanstring: db "%s"
Se lo asembla con:

nasm -f elf64 -l sf.lst -o sf.o sf.asm
 gcc -o sf  sf.o

Para leer un entero largo de 64 bits le pasamos a través del registro "rsi' el lugar donde lo va guardar y luego cargamos el valor leído en el registro "rsi' y lo imprimimos en pantalla.

BITS 64
SECTION .text

extern printf
extern scanf

GLOBAL main
main:
 mov rdi, scanstring
 mov rsi, numero
 xor al,al ; number of arguments in SSE
 call scanf
 mov rsi,[numero]
 mov rdi, scanstring
 mov al, 0 ; number of arguments in SSE
 call printf
 ret

SECTION .data
numero:   dq 0
scanstring: db "%ld"

Lo asemblamos con:

nasm -f elf64 -l sfd.lst -o sf.o sfd.asm
 gcc -o sfd  sfd.o

Para ingresar números en notación de punto flotante, se utilizan los registros "MMX" y en el registro "RAX" colocamos el numero de registros "MMX" usados, en el programa no se observan el uso de los registros "MMX" ya que quedan implícitos, la salida de la función "scanf" es el registro "MMX0" y la entrada de la función "printf" es el mismo registro.


BITS 64
SECTION .text

extern printf
extern scanf

GLOBAL main
main:
 push    rbp
 mov rdi, scanstring
 mov rax,1 ; number of arguments in SSE
 call scanf
 mov rdi, scanstringp
 mov rax, 1 ; number of arguments in SSE
 call printf
 pop rbp
 ret

SECTION .data

scanstring:  db "%lf",0
scanstringp: db "%lf",10,0


Se lo asembla con:

nasm -f elf64 -l sff.lst -o sff.o sff.asm
 gcc -o sff  sff.o

Muchas veces los registros "MMX" se pueden estar usando y es conveniente que la función "scanf" guarde los valores directamente en memoria, para esto usamos la siguiente variante del ejemplo anterior.


BITS 64
SECTION .text

extern printf
extern scanf

GLOBAL main
main:
 push    rbp
 mov qword  rax, 0    
 push qword 0  
 mov rdi, scanstring
 mov  rsi, rsp  
 mov rax,0 ; number of arguments in SSE
 call scanf
 movsd xmm0, [rsp]  
 pop rax  
 mov rdi, scanstring
 mov rax, 1 ; number of arguments in SSE
 call printf
 pop rbp
 ret

SECTION .data

scanstring: db "%lf",0
Se lo asembla con:

nasm -f elf64 -l sff.lst -o sff.o sff.asm
 gcc -o sff  sff.o

x86 Assembler printf

x86 Assembler printf


Podemos realizar el mismo programa "Hello World" pero llamando a funciones de "C". El llamado de funciones de "C" es diferente para arquitecturas de 32 bits y arquitecturas de 64 bits aquí nos ocuparemos de arquitecturas de 64 bits.
La convención para llamadas a rutinas de ¨C¨ desde asembler se pude ver aquí.
El siguiente programa escribe "Hello World" en pantalla usando la función de "C" printf



 SECTION .data
msg:      db "Hello, world,", 0
fmt:      db "%s", 10, 0

        SECTION .text
        extern printf
        global main
main:

        mov esi, msg    ; 64-bit Direccion comienzo de la cadena
        mov edi, fmt    ; Formato de la cadena
        mov eax, 0      ; printf is varargs,  EAX cuenta

                        ;el numero de argumentos no enteros pasados
        call printf

        mov ebx, 0      ; normal-exit code
        mov eax, 1      ; process-termination service
        int 0x80        ; linux kernel service
Se lo asembla con:

nasm -f elf64 hello.asm -o hello.o
gcc -o hello hello.o
Se lo ejecuta:

 ./hello
Hello, world,

Con la función "printf" no solo podemos escribir cadenas de caracteres sino números en una variedad de formatos, en el siguiente programa podemos ver un ejemplo de impresión de números enteros de punto flotante y hexadecimales.

; printf2_64.asm  use "C" printf on char, string, 
; int, long int, float, double
; 
; Assemble: nasm -f elf64 -l printf2.lst -o printf2.o printf2.asm
; Link:  gcc -m64 -o printf2  printf2.o
; Run:  ./printf2 > printf2.out
; Output: cat printf2.out
; 
; A similar "C" program   printf2_64.c 
; #include 
; int main()
; {
;   char      char1='a';            /* sample character */
;   char      str1[]="mystring";    /* sample string */
;   int       len=9;                /* sample string */
;   int       inta1=12345678;       /* sample integer 32-bit */
;   long int  inta2=12345678900;    /* sample long integer 64-bit */
;   long int  hex1=0x123456789ABCD; /* sample hexadecimal 64-bit*/
;   float     flt1=5.327e-30;       /* sample float 32-bit */
;   double    flt2=-123.4e300;      /* sample double 64-bit*/
; 
;   printf("printf2_64: flt2=%e\n", flt2);
;   printf("char1=%c, srt1=%s, len=%d\n", char1, str1, len);
;   printf("char1=%c, srt1=%s, len=%d, inta1=%d, inta2=%ld\n",
;          char1, str1, len, inta1, inta2);
;   printf("hex1=%lX, flt1=%e, flt2=%e\n", hex1, flt1, flt2);
;   return 0;
; }
        extern printf               ; the C function to be called

        SECTION .data               ; Data section

     ; format strings for printf
fmt2: db "printf2: flt2=%e", 10, 0
fmt3: db "char1=%c, str1=%s, len=%d", 10, 0
fmt4: db "char1=%c, str1=%s, len=%d, inta1=%d, inta2=%ld", 10, 0
fmt5: db "hex1=%lX, flt1=%e, flt2=%e", 10, 0
 
char1: db 'a'   ; a character 
str1:  db "mystring",0         ; a C string, "string" needs 0
len:   equ $-str1   ; len has value, not an address
inta1: dd 12345678  ; integer 12345678, note dd
inta2: dq 12345678900  ; long integer 12345678900, note dq
hex1:  dq 0x123456789ABCD         ; long hex constant, note dq
flt1:  dd 5.327e-30  ; 32-bit floating point, note dd
flt2:  dq -123.456789e300         ; 64-bit floating point, note dq

 SECTION .bss
  
flttmp: resq 1           ; 64-bit temporary for printing flt1
 
        SECTION .text                   ; Code section.

        global main          ; "C" main program 
main:            ; label, start of main program
 push    rbp   ; set up stack frame 
 fld dword [flt1]         ; need to convert 32-bit to 64-bit
 fstp qword [flttmp]          ; floating load makes 80-bit,
                                 ; store as 64-bit
 mov rdi,fmt2
 movq xmm0, qword [flt2]
 mov rax, 1   ; 1 xmm register
 call printf

 mov rdi, fmt3  ; first arg, format
 mov rsi, [char1]  ; second arg, char
 mov rdx, str1  ; third arg, string
 mov rcx, len  ; fourth arg, int
 mov rax, 0   ; no xmm used
 call printf

 mov rdi, fmt4  ; first arg, format
 mov rsi, [char1]  ; second arg, char
 mov rdx, str1  ; third arg, string
 mov rcx, len  ; fourth arg, int
 mov r8, [inta1]  ; fifth arg, inta1 32->64
 mov r9, [inta2]  ; sixth arg, inta2
 mov rax, 0   ; no xmm used
 call printf

 mov rdi, fmt5  ; first arg, format
 mov rsi, [hex1]  ; second arg, char
 movq xmm0, qword [flttmp]    ; first double
 movq xmm1, qword [flt2] ; second double
 mov rax, 2   ; 2 xmm used
 call printf
 
 pop rbp   ; restore stack 
        mov     rax, 0   ; exit code, 0=normal
        ret    ; main returns to operating system
Luego lo asemblamos y linkeamos con los siguientes comandos:

nasm -f elf64 -l printf2.lst -o printf2.o printf2.asm
 gcc -o printf2 printf2.o

Se lo ejecuta:

 ./printf2 
printf2: flt2=-1.234568e+302
char1=a, str1=mystring, len=9
char1=a, str1=mystring, len=9, inta1=12345678, inta2=12345678900
hex1=123456789ABCD, flt1=5.327000e-30, flt2=-1.234568e+302

domingo, 8 de marzo de 2015

Introducción X86 Asembler

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