Introdução ao Ambiente UNIX I


Objectivo

  • Introdução ao desenvolvimento de aplicações em linguagem C no ambiente UNIX.
  • Familiarização com as ferramentas necessárias: gcc, gdb.

Material de Apoio

Introdução

Estude o ciclo de desenvolvimento de aplicações em linguagem C no ambiente UNIX. Descarregue e copie o ficheiro aula1.zip para a sua área de trabalho. Descomprima e extraia o seu conteúdo com o comando unzip:

$ unzip aula1.zip

1. Geração do executável

  1. Visualize e compreenda o conteúdo dos ficheiros.
  2. Compile os ficheiros utilizando o comando gcc (documentação completa). Verifique depois que os ficheiros objecto foram criados usando o comando ls através da interface operacional (shell/console).
    $ gcc -g -c list.c main.c
    
  3. Faça a ligação dos ficheiros objecto, de novo com o gcc, de modo a produzir o executável main.
    $ gcc -o main list.o main.o
    
  4. Execute a aplicação main e compreenda o resultado.
    $ ./main
    

2. Utilização do debugger gdb

  1. Execute a aplicação no debugger gdb:
    $ gdb ./main
    
  2. Utilize o comando break (abreviado b) para colocar um breakpoint na primeira instrução da função insert_new_account (ficheiro list.c e linha 37). Um breakpoint pode ser colocado numa linha de um ficheiro, ou numa função:
    (gdb) b list.c:37
    (gdb) b insert_new_account
    
  3. Execute a aplicação usando o comando run (abreviado r):
    (gdb) r
    
  4. A aplicação é executada normalmente. Quando chega ao breakpoint é interrompida pelo gdb. Pode ver onde o código parou de executar utilizando o comando list:
    (gdb) list
    
  5. Pode agora ver o valor das variáveis que estão no scope da função usando o comando print (abreviado p):
    (gdb) p item
    (gdb) p *item
    
  6. Qual a diferença entre os dois comandos anteriores?
  7. Pode agora executar a aplicação utilizando o comando step (abreviado s), que entra nas funções por onde passa, ou next (abreviado n) que salta as funções por onde passa. Pode ainda utilizar o comando continue (abreviado c) para resumir a execução do programa, parando no próximo breakpoint (ou quando o programa rebentar).
    (gdb) n
    (gdb) s
    
  8. Enquanto executa os comandos next ou step pode executar o comando print para mostrar o valor das variáveis ou executar apenas uma vez o comando display:
    (gdb) display *item
    
  9. Saia do gdb com quit ou premindo Ctrl-D:
    (gdb) q
    
  10. Execute o seguinte comando para colocar o limite do tamanho do ficheiro core a 10MB:
    $ ulimit -c 10000000
    
  11. Execute de novo o programa. O erro segmentation fault (core dumped) irá ocorrer.
  12. O gdb pode ser utilizado para analisar o programa após este terminar, como se fosse uma autópsia. Para isso, comece por listar os ficheiros que estão na directoria (ver nota no fim):
    $ ls
    
  13. Que observa?
  14. Use em seguida o gdb para saber onde ocorreu o erro executando:
    $ gdb main ovossoficheiro.core
    
  15. O gdb irá ficar parado na instrução onde ocorreu o erro. Para saber qual a instrução onde o erro ocorreu execute o comando backtrace (abreviado bt):
    (gdb) bt
    
  16. O comando backtrace mostra, de baixo para cima, a lista de funções que foram executadas até ao ponto em que o programa terminou. Por exemplo:
    #0  0x00007fc8b3912998 in __tz_convert () from /usr/lib/libc.so.6
    #1  0x00007fc8b3910ce9 in ctime () from /usr/lib/libc.so.6
    #2  0x0000000000400985 in lst_print (list=0x164e420) at list.c:55
    #3  0x0000000000400797 in main (argc=1, argv=0x7ffeea9883c8) at main.c:34
    
    Algumas das funções pertencem ao sistema. As que nos interessam são as nossas, lst_print e main. O primeiro número em cada linha indica o nível em que essa função está, começando pela função onde o programa rebentou. Para observar as variáveis que estão no nível da função lst_print deve mudar para esse nível executando o comando frame seguido do nível. Por exemplo:
    (gdb) frame 2
    
  17. Pode agora ver o conteúdo das variáveis que estão no scope da função lst_print:
    (gdb) p item
    
  18. Pode assim ver que o erro ocorreu porque a variável item é NULL. A partir daqui pode colocar um breakpoint no ciclo while, executar o programa de novo, e seguir passo a passo enquanto observa os valores das variáveis, até descobrir o que gerou o segmentation fault.
  19. Corrija o programa.

3. Memória dinâmica

  1. Implemente a função update_account_balance. A função update_account_balance recebe uma lista, um número de conta, e um saldo inicial, e atualiza o saldo da conta especificada.
  2. Adicionar um campo owner do tipo char *. Modifique a função main para exemplificar o uso dessa funcionalidade. Deve certificar-se que o programa liberta (utilizando a função free) toda a memória que alocou.
  3. Implemente a função delete_accounts_by_owner. A função recebe uma lista de contas e um nome, e remove da lista as contas cujo owner tenha esse nome.

Nota sobre o ponto 2.12

Em certos sistemas o ficheiro core não é gerado na directoria actual, sendo os coredumps geridos por um programa chamado systemd. O acesso aos coredumps é feito com coredumpctl:

$ coredumpctl gdb