Invocar Funções Da libc No Assembly

Introdução

Para quem está começando a programar em assembly no linux sabe como é difícil imprimir algo na tela usando somente a syscall write do sistema, principalmente quando se quer formatar a saída. Seria bom se pudéssemos usar funções como printf e scanf para fazermos IO em nossos programas.

No post de hoje iremos aprender como linkar a biblioteca libc em programas escritos em assembly.

Como compilar e linkar

O grande “mistério” está em como linkar o programa, pois é preciso indicar para o linker (o ld) qual será o linker dinâmico usado e também a inclusão da libc no aplicativo.

Supondo o código fonte programa.s, o processo de compilação e linkagem é feito assim:

$ as programa.s -o programa.o # cria o arquivo objeto
$ ld -dynamic-linker /lib/ld-linux.so.2 -o programa programa.o -lc # cria o executável
Se nenhum erro ocorreu basta executar o programa:
$ ./programa

Exemplos de programas em assembly

Irei colocar alguns programas simples em assembly para demonstrar como é feito essas chamadas às funções da libc.

1 – Lendo um número inteiro e o mostrando na tela

# [lernumero.s]
# Lê um número do teclado com scanf e em seguida o
# mostra na tela usando printf.
# Funções da libc utilizadas:
# - printf
# - scanf
#
# [Autor]
# Marcos Paulo Ferreira (Daemonio)
# undefinido gmail com
# https://daemoniolabs.wordpress.com
#
# [Como usar]
# $ as -o lernumero.o lernumero.s
# $ ld -dynamic-linker /lib/ld-linux.so.2 -o lernumero lernumero.o -lc
# $ ./lernumero
#
# Ter Nov 22 13:42:06 BRST 2011
#
.equ EXIT_SYSCALL, 1
.section .data
  # Mensagem inicial.
  msg1_printf: .asciz "Digite um numero: "
  # Mensagem final.
  msg2_printf: .asciz "O numero digitado foi: %d\n"
  # Formato usado por scanf.
  str_scanf: .asciz "%d"
  # Variável que guarda o número digitado.
  variavel: .long 0

.section .text
  .globl _start
  _start:
    # Mostra a mensagem inicial.
    pushl $msg1_printf
    call printf
    addl $4, %esp # "limpa" a pilha

    # Lê o número com scanf.
    # Repare que a ordem da passagem
    # argumentos é invertida.
    pushl $variavel
    pushl $str_scanf
    call scanf
    addl $8, %esp # "limpa" a pilha

    pushl variavel
    pushl $msg2_printf
    call printf
    addl $8, %esp # "limpa" a pilha

    # Invoca o Linux para terminar.
    # o programa.
  exit:
    movl $EXIT_SYSCALL, %eax
    xorl %ebx, %ebx
    int $0x80
#EOF

2 – Converter string lida em maiúscula

# [maiscula.s]
# Transforma uma string de entrada em maiúsculas.
# Funções da libc utilizadas:
# - printf
# - memset
# - toupper
#
# [Autor]
# Marcos Paulo Ferreira (Daemonio)
# undefinido gmail com
# https://daemoniolabs.wordpress.com
#
# [Como usar]
# $ as -o maiscula.o maiscula.s
# $ ld -dynamic-linker /lib/ld-linux.so.2 -o maiscula maiscula.o
# $ ./maiscula
#
# Ter Nov 22 14:44:07 BRST 2011
#

# Constantes
.equ BUFSIZE, 4096
.equ EXIT_SYSCALL, 1
.equ STDIN, 0

.section .data
str: .asciz "Digite uma string: \n"

# Declara nosso buffer de leitura
# no bss, assim ele não aumentará
# o tamanho do executável.
.section .bss
.lcomm mybuf, BUFSIZE

.section .text
.globl _start
_start:
  # Zera o buffer que armazenara a string
  # digitada com a ajuda de memset.
  pushl $BUFSIZE
  pushl $0x0
  pushl $mybuf
  call memset
  addl $12, %esp

  # Mostra a mensagem inicial.
  pushl $str
  call printf
  addl $4, %esp

  # Melhor chamar a syscall read
  # do que a função fgets, por exemplo.
  # fgets usa FILE* stream que é um tipo
  # do C.
  movl $3, %eax
  movl $STDIN, %ebx
  movl $mybuf, %ecx
  movl $BUFSIZE, %edx
  decl %edx # retira uma posição para o NULL char.
  int $0x80

  # Chama a função de converter
  # uma string para maiúscula.
  pushl $mybuf
  call mystrupr
  addl $4, %esp

  # Mostra a string alterada na tela.
  pushl $mybuf
  call printf

exit:
  movl $EXIT_SYSCALL, %eax
  xorl %ebx, %ebx
  int $0x80

.type mystrupr, @function
mystrupr:
  pushl %ebp
  movl %esp, %ebp

  # Salva o endereço da string em %ebx.
  movl 8(%ebp), %ebx

  xorl %ecx, %ecx
  my_for:
    xorl %eax, %eax
    # Obtém um caractere da string.
    movb (%ebx, %ecx, 1), %al
    # Se for o NULL char, sai do for.
    testb %al, %al
    jz my_for_sair

    # Salva registradores utilizados.
    pushl %ebx
    pushl %ecx

    # Chama toupper da libc.
    pushl %eax
    call toupper

    # Restaura registradores.
    popl %ecx
    popl %ecx
    popl %ebx

    # Atualiza a string.
    movb %al, (%ebx, %ecx, 1)

    # Incrementa o contador.
    incl %ecx
    jmp my_for

  my_for_sair:
    popl %ebx
    ret
#EOF

3 – Alocar memória no heap

# [heap.s]
# Aloca memoria no heap usando malloc para armazenar
# string digitada.
# Funções da libc utilizadas:
# - memset
# - puts
# - malloc
# - free
#
# [Autor]
# Marcos Paulo Ferreira (Daemonio)
# undefinido gmail com
# daemoniolabs.wordpress.com
#
# [Como usar]
# $ as -o heap.o heap.s
# $ ld -dynamic-linker /lib/ld-linux.so.2 -o heap heap.o -lc
# $ ./heap
# Ter Nov 22 14:43:51 BRST 2011
#

# Constantes.
.equ BUFSIZE, 4096
.equ STDIN, 0
.equ EXIT_SYSCALL, 1

.section .data
bufpointer : .long 0
str: .asciz "** Digite uma string **"
str2: .asciz "** String digitada **"
erromalloc: .asciz "** Impossivel conseguir memoria. **"

.section .text
.globl _start
_start:
  # Chama malloc
  pushl $BUFSIZE
  call malloc
  cmp $0, %eax
  jnz seguir

  # Mostra mensagem de erro caso
  # malloc retorne erro.
  pushl $erromalloc
  call puts
  jmp exit

seguir:
  # Copia o ponteiro para bufpointer.
  movl %eax, bufpointer

  # Chama memset.
  pushl $BUFSIZE
  pushl $0
  pushl bufpointer
  call memset
  addl $12, %esp

  # Mostra a mensagem inicial.
  pushl $str
  call puts
  addl $4, %esp

  # Leitura da string.
  movl $3, %eax
  movl $STDIN, %ebx
  movl bufpointer, %ecx
  movl $BUFSIZE, %edx
  decl %edx # retira uma posição para o NULL char.
  int $0x80

  # Mostra a mensagem.
  pushl $str2
  call puts
  addl $4, %esp

  # Mostra a string na tela.
  pushl bufpointer
  call puts
  addl $4, %esp

  # free.
  pushl bufpointer
  call free

exit:
  movl $EXIT_SYSCALL, %eax
  xorl %ebx, %ebx
  int $0x80
#EOF

4 – Ler linhas de um arquivo

# [ccat.s]
# Simula o aplicativo cat.
# Uso: ccat
#
# [Autor]
# Marcos Paulo Ferreira (Daemonio)
# undefinido gmail com
# https://daemoniolabs.wordpress.com
#
# Funções da libc utilizadas:
# - memset
# - malloc
# - free
#
# [Como usar]
# $ as -o ccat.o ccat.s
# $ ld -dynamic-linker /lib/ld-linux.so.2 -o ccat ccat.o -lc
# $ ./ccat
#

# Constantes
.equ BUFSIZE       , 4096
.equ EXIT_SYSCALL  , 1
.equ READ_SYSCALL  , 3
.equ WRITE_SYSCALL , 4
.equ OPEN_SYSCALL  , 5
.equ CLOSE_SYSCALL , 6
.equ O_RDONLY      , 0
.equ END_OF_FILE   , 0
.equ STDOUT        , 1

.section .data
bufpointer : .long 0
filefd :     .long 0
erromalloc:  .asciz "** Impossivel conseguir memoria. **"
erroopen:    .asciz "** Impossivel abrir arquivo. **"
use:         .asciz "[uso] ccat "

.section .text
.globl _start
_start:

# Testa argc.
cmp $2, (%esp)
jge argvok

# Uso incorreto.
pushl $use
call puts
jmp exit

argvok:

# Abre arquivo.
popl %ebx # argc
popl %ebx # argv[0]
popl %ebx # argv[1]
movl $O_RDONLY, %ecx
movl $0666, %edx
movl $OPEN_SYSCALL, %eax
int $0x80

cmp $0, %eax
jg openok

# Impossível abrir arquivo.
pushl $erroopen
call puts
jmp exit

openok:
movl %eax, filefd

# Chama malloc.
pushl $BUFSIZE
call malloc
cmp $0, %eax
jnz mallocok

# Erro se malloc retornar NULL.
pushl $erromalloc
call puts
jmp exit

mallocok:
  # Copia o ponteiro para bufpointer.
  movl %eax, bufpointer

nextline:
  # Chama memset.
  pushl $BUFSIZE
  pushl $0
  pushl bufpointer
  call memset

  # Leitura da linha.
  movl $READ_SYSCALL, %eax
  movl filefd, %ebx
  movl bufpointer, %ecx
  movl $BUFSIZE, %edx
  decl %edx # retira uma posição para o NULL char
  int $0x80

  # Verifica se chegou ao fim do arquivo.
  cmpl $END_OF_FILE, %eax
  jle end_loop

  # Mostra a linha na tela.
  movl %eax, %edx
  movl $WRITE_SYSCALL, %eax
  movl bufpointer, %ecx
  movl $STDOUT, %ebx
  int $0x80

  jmp nextline

  # Fecha o fd com close e chama free.
end_loop:
  movl $CLOSE_SYSCALL, %eax
  movl filefd, %ebx
  int $0x80

  # free.
  pushl bufpointer
  call free

exit:
  movl $EXIT_SYSCALL, %eax
  xorl %ebx, %ebx
  int $0x80
#EOF

Conclusão

Esse post foi para mostrar como usar as funções padrões do C, presentes na biblioteca dinâmica libc, em programas escritos em assembly. Embora isso tenha uma grande utilidade, eu acho que não faz sentido algum usar muitas funções prontas das bibliotecas do C, pois seria muito mais fácil programar em C diretamente.

Referências

[1] Programming from the Ground Up  by Jonathan Bartlett (Acessado em: Novembro/2011)
http://florida.theorangegrove.org/og/file/6d9fe012-ffe1-1469-3a77-f65d56c0e41b/1/ProgrammingGroundUp-1-0-pdf.pdf

[2] IA-32 Assembly no Linux by Prof. Rossano Pablo Pinto, MSc.  (Acessado em: Novembro/2011)
http://rossano.pro.br/fatec/cursos/sistcomp/apostilas/assembly-language-br.html

Um pensamento sobre “Invocar Funções Da libc No Assembly

Deixe um comentário