Sony Vaio F111 Review (VPCF111FB)

Hardware

  • Modelo VPCF111-FB
  • Display LCD FullHD 1920×1080.
  • Câmera 640×480 em cima da tela.
  • 3 portas USB (1 delas é compartilhada com e-SATA).
  • 1 porta e-SATA (compartilhada com 1 USB – ou usa um ou o outro).
  • Express card.
  • Porta de rede.
  • Saída VGA.
  • Saída HDMI.
  • Porta S400 (FireWire 400 Mbits/s).
  • Chave para desligar rádios (wifi/bluetooth).
  • Touchpad.
  • Saída fone de ouvido.
  • Entrada Microfone
  • Saída áudio óptica (compartilhada com saida para fone de ouvido).
  • Entrada para cartões MagicGate
  • Entrada para cartões SD
  • LED’s NumLock/CapsLock/System Lock/Power (em cima do teclado)
  • LED’s HD, Posição da chave Wireless (rádios), Status da Bateria (na frente).
  • LED de acionamente da câmera.
  • Teclas especiais para desligar o display, iniciar/parar player.
  • Placa de vídeo Nvidia G310M.

Pontos positivos

  • Tamanho e resolução do display
  • Velocidade da CPU
  • Quantidade de memória
  • Pouco aquecimento
  • Teclado agradável
  • Teclado não esquenta

Pontos negativos

  • Peso
  • Tamanho da font
  • Com tanto espaço, a tecla Barra e Interrogação só podem ser usadas com AltGR.
  • As teclas poderiam ser um pouquinho maiores (há espaço).
  • Manual fraquíssimo. Nem pode ser chamado de manual. Nem explica qual tecla entra na configuração da BIOS.
  • Os sons escandalosos do Windows 7 em resposta à ações (é só desabilitar, gostaria que viesse assim).
  • Duração da bateria (2 horas)
  • Reconhecimento de fala do Windows 7 não funciona (diz que não está disponível para o idioma atual).
  • O pino da tomada está no novo padrão brasileiro, aquele que não encaixa em tomada nenhuma. Coisa da ABNT. Compre uma régua compatível.
  • A licença do Windows Live. Medo, muito medo.

Esquisitices

  • Não vem nenhum CD/DVD. Existe uma partição de restauração, possivelmente o Windows todo está lá. Não a remova.
  • Existe uma enorme área sem uso na parte de cima do teclado.
  • Demorei para encontrar onde habilitar dispositivos bluetooth, então vai a dica: Painel de controle, Dispositivos e Impressoras, Adicionar um dispositivo.
  • Demorei um pouco para acostumar com o touchpad (mouse) fora de centro.

Windows 7

Funcionou de cara

  • Câmera embutida
  • Bluetooth
  • Fone Nokia N82
  • Headset Nokia
  • USB
  • Porta HDMI em FullHD com Painel LG 47
  • Dual-Head (monitor nativo + monitor FullHD via HDMI)
    • Funcionou tanto na opção CLONE — quando os dois monitores mostram a mesma imagem– como na opção COMPOSTO, ou seja, os 2 monitores mostrando imagens independentes.
  • Rede (Roteador DI-524)
  • Rede WiFi (com WPA2)
  • Reprodução Blu-Ray
  • Porta VGA com Monitor LG 22
  • Impressora doméstica em rede com CUPS.

Funcionou após fazer download

  • Laserjet 1020 (para rede também precisa do driver, que não está no conjunto de drivers fornecidos).
  • Scanner Plustek OpticFilm 7300 – O driver antigo nem instala
  • Nokia PC suite

Outros programas alternativos  que funcionaram (eu sei, não depende do hardware, mas só para informar)

  • BrOffice
  • PDFCreator
  • Pidgin
  • Firefox

Não funcionou

  • Captura de TV, a PixelView ainda não tem uma versão do driver para Win7.

Linux (Ubuntu 9.10)

Atenção: estou apenas descrevendo o procedimento que eu executei em minha máquina, não há garantia de que isso vá funcionar ou mesmo danificar sua máquina. Se quiser tentar, será por sua conta e risco.

Na instalação original, o Windows ocupa o disco todo. Para instalar o Linux, a primeira coisa a fazer é diminuir a partição do Windows: vá em Painel de Controle, Ferramentas Administrativas, Gerenciamento do Computador.

No painel esquerdo, selecione Repositório/Gerenciamento de Disco. No Disco 0, clique na partição C: com o botão direito, e escolha a opção Diminuir Volume. Digite o tamanho desejado na caixa Digite o Espaço a Diminuir em MB. O processo vai demorar um pouco para ser concluído.

Um outro problema é que o HD é o dispositivo padrão de boot, como instalei de um CD, tive que alterar a ordem dos dispositivos de boot. Para isso, durante a inicialização, pressione F2.

Vá na página de dispositivos de boot e altere a ordem para CD em primeiro, HD em segundo. Agora é só seguir os procedimentos de instalação, normalmente.

Funcionou de cara

  • Wifi (A/B/G)
  • Bluetooth (incluindo perfil estéreo, perfil com microfone, celular)
  • Câmera
  • Ethernet
  • As 3 USBs
  • Saída VGA
  • Saída HDMI
  • Botões multimídia
  • Leitor CD/DVD
  • Gravador DVD
  • Monitor de bateria
  • Gerenciador de velocidade de CPU
  • Leitor SD card (10 MB/s)
  • Saída de fone
  • Entrada de microfone
  • Microfone embutido.

Não funcionou

  • O driver de vídeo nativo Nvidia não funcionou. O primeiro problema foi não reconhecer o próprio painel LCD! Consegui consertar isso alterando pegando o EDID (informações do monitor) no Windows e configurei o xorg.conf para usar o EDID lido. Apesar de aparecer imagens, não foi possĩvel ativar o desktop 3D. Além disso ficou instável, travou algumas vezes. (CONSERTADO). Após o conserto ativei o Desktop 3D Compiz Fusion, ficou muito bom. Adicione o Cairo Dock, versão OpenGL, o desktop fica muito bonito e o gasto extra de CPU é insignificante.
  • O som usando os falantes embutidos não funcionou (apesar da saída de fone de ouvido funcionar normalmente) (CONSERTADO).
  • As teclas ASSIST e VAIO não são reconhecidas (CONSERTADO).
  • O Blu-Ray player não funcionou no Linux. Tentei algumas gambiarras garimpadas na Internet, nenhuma deu certo.
  • Não consegui ler memory stick.
  • O ajuste de brilho da luz de fundo do display não funciona, fica sempre no máximo (CONSERTADO). Depois de consertado, a duração da bateria melhorou consideravelmente.
  • O sensor de luminosidade (uma bolinha acima da tecla F9) não funciona.
  • Dual Head.

Funcionou parcialmente

  • A função de rolar do touchpad não funcionou corretamente, parece que o touchpad é reconhecido como mouse PS∕2. A movimentação do ponteiro funcionou OK (apesar de a tela FullHD ser muito grande para ficar deslocando o ponteiro).

Não foi testado

  • saída eSATA
  • saída i400
  • ExpressCard

Fotos

Debug – User Mode – 2 (GDB)

GDB (GNU Debugger) é um programa que ajuda a saber o que está acontecendo dentro de um outro processo (debuggee), acompanhar sua execução, parar a execução, ver e alterar seu estado interno. Resumindo: é um debugger :) . O GDB foi feito primariamente para debugar programas escritos em C/C++, mas possui suporte de nível variado para programas escritos em outras linguagens. O GDB funciona em vários Sistemas Operacionais e em várias arquiteturas de processadores, mas neste texto vou considerar o GDB debugando programas em C, executando em Linux, arquitetura x86. Não tenho aqui a pretensão de apresentar um guia abrangente e cobrir cada uma das peculiaridades e detalhes obscuros do GDB nem seu funcionamento interno, ao contrário, a intenção é mostrar como fazer o básico, através de exemplos práticos. Muitos dos resultados mostrados podem ser obtidos de formas diferentes da mostrada, ficando a escolha ao gosto do freguês. Por fim, os melhores lugares para encontrar informações detalhadas sobre todos os aspectos do GDB são:

  • # info gdb
  • # man gdb
  • Página do GDB.
  • Como qualquer debugger decente, o GDB possui as seguintes características:

  • Executar um programa, parando na primeira instrução ou linha de código.
  • Executar um programa passo a passo.
  • Criar e gerenciar breakpoints, inclusive breakpoints de expressões e breakpoints condicionais.
  • Alterar o estado da CPU, registradores, pilha.
  • Examinar e alterar o valor de variáveis, salvar trechos de memória para um arquivo.
  • Parar o programa em caso de erro (exceção).
  • Criar tracepoints
  • Atachar o debugger à um programa em execução.
  • Gerar/Debugar core dumps.
  • Criar scripts de debug usando Python.
  • Debugar um programa executando em um outro computador.
  • Des-debugar (é, eu inventei a palavra).
  • Gravar e reproduzir uma sessão.
  • Antes que seja tarde, um pequeno glossário para esclarecer alguns termos:

    Debugger Um programa auxiliar capaz de examinar/alterar o estado interno de um outro programa.
    Debuggee O programa que está sendo debugado pelo Debugger.
    GNU GNU is Not Unix.
    GDB GNU Debugger, o programa em estudo
    GCC GNU Compiler Collection, o compilador C/C++ usado nos exemplos
    ELF Executable and Linkable Format, uma especificação de formato para arquivos binários executáveis e bibliotecas. Padrão no Linux.
    COFF Common Object File Format, uma especificação de formato de arquivos binários executáveis e bibliotecas. Perdeu espaço no mundo Unix para o ELF, mais completo.
    DWARF Formato para armazenamento de símbolos de debug.
    Symbol, símbolo Um nome de variável ou função, normalmente dado pelo programador ao construir o código fonte. Debuggers modernos associam automaticamente endereços de memória a símbolos, facilitando o debug.
    Section, seção Um formato de representação de arquivo binário como ELF ou COFF divide o arquivo em seções, que contém tipos diversos de informação. Uma ou mais seções podem estar dedicadas a armazenar informações de debug, como símbolos.
    Session, sessão uma sessão de debug, uma rodada de execução de um programa que está sendo debugado.

    Vamos por logo a mão na massa: Usando o projeto esqueleto, fiz um programa para ser debugado, está aqui. Agora só precisamos iniciar uma sessão de debug usando GDB.

    Aqui está o código fonte, para facilitar o acompanhamento:

    /*
     *  Copyright (C) 2009 William Cesar de Oliveira
     *
     *  This program is free software: you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation, either version 2 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #define TEST_VALUE 100
    
    int v_global = TEST_VALUE;
    
    int main(int argc, char *argv[])
    {
    	int i;
    	FILE *my_out = NULL;
    
    	my_out = stdout;
    	for (i = 0; i < TEST_VALUE; i++, v_global--)
    		fprintf(my_out, "i=%d global=%d\n", i, v_global);
    	return 0;
    }
    

    Para começar, compile o programa usando
    # make clean release
    E, depois execute o debugger com
    # gdb ./gdb0
    Com essa linha de comando o GDB vai carregar, mostrar seu o prompt e ficar aguardando um comando. Para fazer o programa executar,
    gdb> run

    E pronto, o programa debugado está executando sob a tutela do GDB. Fácil, não? Tanto a entrada como a saída padrão do programa (para onde vão os printf) são as mesmas do GDB, portanto, ao término do programa, o controle retorna para o GDB, mas o console continua o mesmo.

    Quando qualquer condição anormal acontece no programa (um erro, fatal signal, exceção ou um breakpoint), o prompt do GDB retorna, e podemos examinar ou modificar o estado do programa debugado. Quando o programa debugado termina o controle também retorna para o GDB.

    Logo quando o GDB inicia, é mostrada uma mensagem sobre o carregamento dos símbolos, no caso desse primeiro teste, a mensagem é:
    Reading symbols from ./gdb0...(no debugging symbols found)...done.
    Isso significa que o GDB não foi capaz de encontrar as informações necessárias para associar nomes de variáveis, funções e linhas com o programa em execução. Na prática, quer dizer que tudo o que você vai ver são instruções assembler, conteúdo de registradores e endereços de memória, sem nenhuma associação com os nomes usados no código fonte do programa.

    O GDB ainda é útil para debugar programas que não contém os símbolos de debug, mas o processo fica muito mais difícil, exigindo conhecimentos da arquitetura do processador, um pouco do funcionamento interno do sistema operacional e muita paciência.

    Podemos examinar o conteúdo de um binário qualquer usando o utilitário objdump. No caso desse programa de testes gdb0:
    # objdump -h gdb0
    gdb0: file format elf64-x86-64
    Sections:
    Idx Name Size VMA LMA File off Algn
    0 .interp 0000001c 0000000000400200 0000000000400200 00000200 2**0
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    1 .note.ABI-tag 00000020 000000000040021c 000000000040021c 0000021c 2**2
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    2 .note.gnu.build-id 00000024 000000000040023c 000000000040023c 0000023c 2**2
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    3 .gnu.hash 0000001c 0000000000400260 0000000000400260 00000260 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    4 .dynsym 00000060 0000000000400280 0000000000400280 00000280 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    5 .dynstr 0000003f 00000000004002e0 00000000004002e0 000002e0 2**0
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    6 .gnu.version 00000008 0000000000400320 0000000000400320 00000320 2**1
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    7 .gnu.version_r 00000020 0000000000400328 0000000000400328 00000328 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    8 .rela.dyn 00000018 0000000000400348 0000000000400348 00000348 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    9 .rela.plt 00000030 0000000000400360 0000000000400360 00000360 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    10 .init 00000018 0000000000400390 0000000000400390 00000390 2**2
    CONTENTS, ALLOC, LOAD, READONLY, CODE
    11 .plt 00000030 00000000004003a8 00000000004003a8 000003a8 2**2
    CONTENTS, ALLOC, LOAD, READONLY, CODE
    12 .text 000001f8 00000000004003e0 00000000004003e0 000003e0 2**4
    CONTENTS, ALLOC, LOAD, READONLY, CODE
    13 .fini 0000000e 00000000004005d8 00000000004005d8 000005d8 2**2
    CONTENTS, ALLOC, LOAD, READONLY, CODE
    14 .rodata 00000020 00000000004005e8 00000000004005e8 000005e8 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    15 .eh_frame_hdr 00000024 0000000000400608 0000000000400608 00000608 2**2
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    16 .eh_frame 0000007c 0000000000400630 0000000000400630 00000630 2**3
    CONTENTS, ALLOC, LOAD, READONLY, DATA
    17 .ctors 00000010 00000000006006b0 00000000006006b0 000006b0 2**3
    CONTENTS, ALLOC, LOAD, DATA
    18 .dtors 00000010 00000000006006c0 00000000006006c0 000006c0 2**3
    CONTENTS, ALLOC, LOAD, DATA
    19 .jcr 00000008 00000000006006d0 00000000006006d0 000006d0 2**3
    CONTENTS, ALLOC, LOAD, DATA
    20 .dynamic 00000190 00000000006006d8 00000000006006d8 000006d8 2**3
    CONTENTS, ALLOC, LOAD, DATA
    21 .got 00000008 0000000000600868 0000000000600868 00000868 2**3
    CONTENTS, ALLOC, LOAD, DATA
    22 .got.plt 00000028 0000000000600870 0000000000600870 00000870 2**3
    CONTENTS, ALLOC, LOAD, DATA
    23 .data 00000004 0000000000600898 0000000000600898 00000898 2**2
    CONTENTS, ALLOC, LOAD, DATA
    24 .bss 00000010 00000000006008a0 00000000006008a0 0000089c 2**3
    ALLOC
    25 .comment 0000013f 0000000000000000 0000000000000000 0000089c 2**0
    CONTENTS, READONLY

    Nota-se que é um binário no formato ELF, 64 bits, para arquitetura x86_64. Vamos recompilar e debugar o mesmo programa novamente, agora usando
    # make clean debug
    Executando o objdump novamente, podemos ver que existem algumas seções que não estavam presente na versão release:
    ...
    26 .debug_aranges 00000030 0000000000000000 0000000000000000 000009eb 2**0
    CONTENTS, READONLY, DEBUGGING
    27 .debug_pubnames 0000001b 0000000000000000 0000000000000000 00000a1b 2**0
    CONTENTS, READONLY, DEBUGGING
    28 .debug_info 000000da 0000000000000000 0000000000000000 00000a36 2**0
    CONTENTS, READONLY, DEBUGGING
    29 .debug_abbrev 00000075 0000000000000000 0000000000000000 00000b10 2**0
    CONTENTS, READONLY, DEBUGGING
    30 .debug_line 00000214 0000000000000000 0000000000000000 00000b85 2**0
    CONTENTS, READONLY, DEBUGGING
    31 .debug_str 000000ba 0000000000000000 0000000000000000 00000d99 2**0
    CONTENTS, READONLY, DEBUGGING
    32 .debug_macinfo 000048d6 0000000000000000 0000000000000000 00000e53 2**0
    CONTENTS, READONLY, DEBUGGING
    33 .debug_pubtypes 00000012 0000000000000000 0000000000000000 00005729 2**0
    CONTENTS, READONLY, DEBUGGING

    Como mencionado, essas seções servirão para ajudar um debugger como o GDB a relacionar os símbolos com os endereços de memória e nomes de função. Embora as informações de debug não precisem estar necessariamente embutidas no próprio binário, vamos usar esse modelo por ser mais simples.

    Agora, veremos a seguinte mensagem quando o GDB carregar o programa:
    # gdb ./gdb0
    Reading symbols from ./gdb0...done.

    Essa singela mensagem significa que o GDB vai ser capaz de correlacionar os símbolos do código fonte com o programa em execução.

    Para construir um programa com os símbolos de debug, temos que instruir o compilador/linker para fazer isso. No caso do GCC, o comando -g está associado a debugging. Se usarmos o comando -ggdb, estaremos instruindo o GCC a adicionar informações de debug no formato ideal para o GDB. Outra opção que afeta o debugger é a otimização, o programa ideal para debug é o que foi compilado com todas as opções de otimização desligadas (no caso do GCC, -O0). Isso porque, durante a otimização, o compilador pode mover/remover varíaveis, funções e trechos de código, tornando mais difícil (mas não impossível) o processo de debug. Para mais informações, consulte o manual do GCC.

    Um programa normalmente faz uso de bibliotecas carregadas dinamicamente, as famosas libs, sendo que a libc é usada em 99.9% dos programas. O GDB é capaz de carregar os símbolos tanto para o executável propriamente dito como para as libs, bastando que os símbolos estejam disponíveis para serem automaticamente carregados.

    Voltando ao debug, usando start ao invés de run faremos com que o progama pare na primeira linha com side-effect do código fonte:
    (gdb) start
    Temporary breakpoint 1 at 0x400514: file main.c, line 28.
    Starting program: ./gdb0
    Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe1a8) at main.c:28
    28 FILE *my_out = NULL;

    O GDB colocou um breakpoint temporário na linha 28 antes de executar o programa. Um breakpoint é usado para indicar ao GDB que a execução do programa debugado deve ser suspensa e o controle deve ser passado ao GDB.

    Como o programa está suspenso, podemos examinar a pilha de chamadas para a função atual (main):
    (gdb) bt
    #0 main (argc=1, argv=0x7fffffffe1a8) at main.c:28

    Também podemos examinar a pilha incluindo as variáveis locais:
    #0 main (argc=1, argv=0x7fffffffe1a8) at main.c:28
    i = 0
    my_out = 0x400420

    Vemos que estamos na função main, que foi chamada com argc=1 e argv=0x7fffffffe1a8. Todo mundo sabe que argv é um array de ponteiros, mas será que o GDB sabe?
    (gdb) whatis argv
    type = char **
    (gdb) whatis argc
    type = int
    (gdb) whatis my_out
    type = FILE *
    (gdb) whatis i
    type = int
    (gdb) whatis v_global
    type = int
    (gdb) whatis TEST_VALUE
    type = int
    (gdb) p TEST_VALUE
    $1 = 100

    Como podemos ver, o GDB também identifica e mostra macros, se o formato de armazenamento dos símbolos permitir.

    Se quisermos informação detalhada sobre o tipo de uma variável, podemos usar o ptype:
    (gdb) ptype my_out
    type = struct _IO_FILE {
    int _flags;
    char *_IO_read_ptr;
    char *_IO_read_end;
    char *_IO_read_base;
    char *_IO_write_base;
    char *_IO_write_ptr;
    char *_IO_write_end;
    char *_IO_buf_base;
    char *_IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno;
    int _flags2;
    __off_t _old_offset;
    short unsigned int _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
    __off64_t _offset;
    void *__pad1;
    void *__pad2;
    void *__pad3;
    void *__pad4;
    size_t __pad5;
    int _mode;
    char _unused2[20];
    } *

    Note que apesar de FILE não ter sido definida em nosso código fonte, podemos ver a definição da struct.

    Se quisermos examinar o conteúdo ou o endereço de memória de uma variável, por exemplo, v_global:
    (gdb) p v_global
    $1 = 100

    O valor mostrado, 100, é o conteúdo de v_global. Para saber o endereço de v_global,
    (gdb) p &v_global
    $2 = (int *) 0x600924

    Será que a inversa é possível, a partir de um endereço saber o nome da variável?
    Para variáveis globais, a resposta é sim.
    (gdb) info symbol 0x600924
    v_global in section .data of gdb0

    Para variáveis locais, no entanto...
    (gdb) p &i
    $3 = (int *) 0x7fffffffe0a4
    (gdb) info symbol 0x7fffffffe0a4
    No symbol matches 0x7fffffffe0a4

    Agora que já examinamos algumas variáveis, agora vamos fazer o programa continuar para a próxima linha do código fonte. Uma linha de código fonte geralmente corresponde a várias instruções de máquina, mas não precisamos nos preocupar com isso, o GDB usa as anotações colocadas pelo compilador para calcular automaticamente onde o breakpoint será colocado.
    (gdb) step
    30 my_out = stdout;

    Esse comando instrue o GDB para executar uma ou mais linhas de código fonte, sendo o default a execução de 1 linha. A linha mostrada após o comando é a próxima a ser executada.
    Quando encontra uma chamada de função, o step segue para dentro daquela função. Existe um outro comando, o next, que funciona como o step, mas não segue uma função. Ao encontrar uma chamada de função, o next chama a função e para a execução após o retorno. Tanto step quanto next implementam a variação i, stepi e nexti, que funcionam como o comando original, mas executando uma instrução de máquina ao invés de uma linha de código.

    No nosso programa de teste, verificando os valores das variáveis, obteremos:
    (gdb) info locals
    i = 0
    my_out = 0x0

    Só para lembrar, ao comandarmos step, a linha executada foi
    28 FILE *my_out = NULL;
    Por isso observamos que o valor de my_out agora é NULL (0x0).

    Quando estamos executando um programa complexo, com muitos arquivos fonte e vários breakpoints, podemos ter problemas para lembrar qual o arquivo fonte que está relacionado ao contexto atual. Felizmente podemos resolver esse problema com:
    (gdb) info source
    Current source file is main.c
    Compilation directory is gdb0
    Located in ./gdb0/main.c
    Contains 34 lines.
    Source language is c.
    Compiled with DWARF 2 debugging format.
    Does not include preprocessor macro info.

    Agora que já lembramos o que estávamos fazendo, vamos mandar o programa parar logo após o loop, na linha 33. Sabendo em qual linha queremos definir um breakpoint, temos 2 formas de informar isso ao GDB. Se a linha estiver no mesmo arquivo, como é o caso aqui, podemos simplesmente:
    (gdb) break 33
    Breakpoint 2 at 0x40056b: file main.c, line 33.

    Isso significa que o GDB vai colocar um breakpoint na instrução que está no endereço 0x40056b, que corresponde à linha 33 do arquivo main.c.
    Quando o breakpoint a ser definido não está no arquivo atual, ou quando o programa ainda não está em execução (e possui vários fontes), temos que informar o arquivo fonte, além do número da linha:
    (gdb) break main.c:33
    Breakpoint 1 at 0x40056b: file main.c, line 33.

    Vamos então mandar o GDB continuar com a execução do programa:
    (gdb) c
    Continuing.
    i=0 global=100
    i=1 global=99
    i=2 global=98
    i=3 global=97
    ...
    i=98 global=2
    i=99 global=1
    Breakpoint 1, main (argc=1, argv=0x7fffffffe1a8) at main.c:33
    33 return 0;

    O GDB suspendeu a execução do programa ANTES de executar a linha marcada. Vamos examinar o valor das variáveis:
    (gdb) info locals
    i = 100
    my_out = 0x3cc7173780
    (gdb) p v_global
    $1 = 0

    É, parece que funciona. Antes de continuar a execução, no entanto, vamos mudar o fluxo de execução, fazendo o programa redefinir qual a próxima linha/instrução a ser executada:
    (gdb) jump 31
    Continuing at 0x400527.
    i=0 global=0
    i=1 global=-1
    i=2 global=-2
    ...
    i=99 global=-99
    Breakpoint 2, main (argc=1, argv=0x7fffffffe1a8) at main.c:33
    33 return 0;

    Veja que a variável v_global não foi reinicializada, por isso ficou com os valores negativos.
    Por último, vamos deixar o programa terminar em paz:
    (gdb) c
    Continuing.
    Program exited normally.

    Uma extensão interessante de breakpoint é o breakpoint condicional, que só pára o programa quando uma determinada condição for satisfeita. Por exemplo, vamos adicionar um breakpoint condicional na linha 32, para que o breakpoint funcione somente quando o valor de i for 50 e, em seguida, mandaremos o programa executar:
    (gdb) break main.c:32 if i == 50
    Breakpoint 3 at 0x400530: file main.c, line 32.
    (gdb) run
    Starting program: ./gdb0
    i=0 global=100
    ...
    i=49 global=51
    Breakpoint 3, main (argc=1, argv=0x7fffffffe1a8) at main.c:32
    32 fprintf(my_out, "i=%d global=%d\n", i, v_global);

    Examinando os valores das variáveis:
    (gdb) info locals
    i = 50
    my_out = 0x3cc7173780

    Vamos aproveitar o breakpoint para alterar o valor da variável i para 200:
    (gdb) set variable i=200
    (gdb) p i
    $2 = 200

    E agora mandamos o programa continuar:
    (gdb) c
    Continuing.
    i=200 global=50

    Se você não reiniciou o GDB, aquele breakpoint que colocamos na linha 33 continua lá...
    Breakpoint 1, main (argc=1, argv=0x7fffffffe1a8) at main.c:33
    33 return 0;

    Examinando as variáveis novamente, teremos:
    Breakpoint 1, main (argc=1, argv=0x7fffffffe1a8) at main.c:33
    33 return 0;
    (gdb) p i
    $3 = 201
    (gdb) p v_global
    $4 = 49

    E vamos terminar a execução normalmente:
    (gdb) c
    Continuing.
    Program exited normally.

    Mudamos o estado interno do programa durante a parada. O mecanismo de breakpoint permite fazer isso automaticamente, através da definição de uma lista de comandos a serem executados quando o breakpoint é ativado.

    Vamos começar removendo o breakpoint da linha 32:
    (gdb) info break
    Num Type Disp Enb Address What
    2 breakpoint keep y 0x000000000040056b in main at main.c:33
    3 breakpoint keep y 0x0000000000400530 in main at main.c:32
    stop only if i == 50

    Queremos remover o breakpoint #3:
    (gdb) delete 3
    Agora vamos adicioná-lo novamente, definindo a condição para quando o valor de v_global for 47:
    (gdb) break 32 if v_global == 47
    Breakpoint 4 at 0x400530: file main.c, line 32.

    Note que o slot 3 não é reutilizado. Poderíamos, ao invés de remover e adicionar, somente alterar as condições do breakpoint. Por exemplo, para alterar a condição do breakpoint 4 para parar quando v_global for 49:
    (gdb) condition 4 v_global == 49
    (gdb) info break
    Num Type Disp Enb Address What
    2 breakpoint keep y 0x000000000040056b in main at main.c:33
    breakpoint already hit 1 time
    4 breakpoint keep y 0x0000000000400530 in main at main.c:32
    stop only if v_global == 49

    Agora vamos fazer com que, quando o breakpoint 4 for ativado, o valor da variável i seja alterado para 200.
    (gdb) commands 4
    Type commands for when breakpoint 4 is hit, one per line.
    End with a line saying just "end".
    >set var i = 200
    >end

    E agora, vamos executar o programa para ver o resultado:
    (gdb) run
    Starting program: ./gdb0
    i=0 global=100
    ...
    i=50 global=50
    Breakpoint 4, main (argc=1, argv=0x7fffffffe1a8) at main.c:32
    32 fprintf(my_out, "i=%d global=%d\n", i, v_global);

    Olhando para os valores das variáveis:
    (gdb) p i
    $1 = 200
    (gdb) p v_global
    $2 = 49

    Podemos ver que o breakpoint executou os comandos que estavam previstos. Continuando com o programa:
    (gdb) c
    Continuing.
    i=200 global=49
    Breakpoint 2, main (argc=1, argv=0x7fffffffe1a8) at main.c:33
    33 return 0;

    Aquele breakpoint na linha 33 continua lá. Finalmente:
    (gdb) c
    Continuing.
    Program exited normally.

    Além de redefinir os valores de variáveis, também podemos chamar funções a partir lista de comandos associados ao breakpoint. Por exemplo, vamos alterar a lista de comandos do breakpoint da linha 32 e reiniciar o programa:
    (gdb) info break
    Num Type Disp Enb Address What
    2 breakpoint keep y 0x000000000040056b in main at main.c:33
    breakpoint already hit 1 time
    4 breakpoint keep y 0x0000000000400530 in main at main.c:32
    stop only if v_global == 49
    breakpoint already hit 1 time
    set var i = 200
    (gdb) commands 4
    Type commands for when breakpoint 4 is hit, one per line.
    End with a line saying just "end".
    >call puts("Hello Breakpoint!\n")
    >end
    (gdb) run
    Starting program: ./gdb0
    i=0 global=100
    i=1 global=99
    ...
    i=50 global=50
    Breakpoint 4, main (argc=1, argv=0x7fffffffe1a8) at main.c:32
    32 fprintf(my_out, "i=%d global=%d\n", i, v_global);
    Hello Breakpoint!
    $3 = 19

    Lembre-se de que a função é chamada dentro do contexto do debuggee, usando o stack frame corrente. O valor 19 é o retorno da função puts. Quaisquer funções podem ser chamadas, estejam em bibliotecas ou no programa debugado.

    É importante notar que a lista de comandos substituiu completamente a anterior, por isso, ao examinarmos o valor de i:
    (gdb) p i
    $4 = 51

    Frequentemente queremos que o breakpoint seja ativado somente após algumas vezes depois que o programa possou por ele. O GDB já mantém um contador de vezes que um breakpoint foi ativado, e o comando ignore serve para isso. Vamos limpar a lista de breakpoints, adicionar um breakpoint na linha 32 e fazer com que o programa seja suspenso após passar pelo breakpoint por 7 vezes:
    (gdb) delete breakpoints
    Delete all breakpoints? (y or n) y
    (gdb) break 32
    Breakpoint 4 at 0x400530: file main.c, line 32.
    (gdb) info break
    Num Type Disp Enb Address What
    4 breakpoint keep y 0x0000000000400530 in main at main.c:32
    (gdb) ignore 4 7
    Will ignore next 7 crossings of breakpoint 4.

    Executando o programa novamente:
    (gdb) run
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: ./gdb0
    i=0 global=100
    i=1 global=99
    i=2 global=98
    i=3 global=97
    i=4 global=96
    i=5 global=95
    i=6 global=94
    Breakpoint 4, main (argc=1, argv=0x7fffffffe1a8) at main.c:32
    32 fprintf(my_out, "i=%d global=%d\n", i, v_global);

    Veja também enable, disable, finish, until e advance.

    Chega de breakpoints, vamos limpar todos os breakpoints e reiniciar o programa. O assunto agora é watchpoints.

    (gdb) delete breakpoints
    Delete all breakpoints? (y or n) y
    (gdb) start
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Temporary breakpoint 5 at 0x400514: file main.c, line 28.
    Starting program: ./gdb0
    Temporary breakpoint 5, main (argc=1, argv=0x7fffffffe1a8) at main.c:28
    28 FILE *my_out = NULL;

    Um watchpoint nada mais é do que um "breakpoint de dados". O tipo de breakpoint que vimos até agora está relacionado à um endereço de memória de programa, e é ativado quando o registrador de instrução (IP na arquitetura x86) executa o endereço marcado (na verdade é mais tosco do que essa descrição poética, mas deixo essa descoberta para você). Um watchpoint funciona de modo parecido, mas é ativado pela avaliação de uma expressão, sem importar ONDE os termos da expressão foram alterados. Por exemplo:
    (gdb) watch v_global == 90
    Hardware watchpoint 2: v_global == 90
    (gdb) c
    Continuing.
    i=0 global=100
    i=1 global=99
    i=2 global=98
    i=3 global=97
    i=4 global=96
    i=5 global=95
    i=6 global=94
    i=7 global=93
    i=8 global=92
    i=9 global=91
    Hardware watchpoint 2: v_global == 90
    Old value = 0
    New value = 1
    0x0000000000400565 in main (argc=1, argv=0x7fffffffe188) at main.c:31
    31 for (i = 0; i < TEST_VALUE; i++, v_global--)

    Bom, você deve estar pensando: "eu poderia fazer o mesmo com um breakpoint condicional!". Como esse caso é muito simples, realmente isso poderia ser feito. Entretanto, imagine uma variável que pode ter o valor alterado em vários lugares diferentes, ou se você desejar que o watchpoint pare o programa somente quando uma certa variável for lida: nesse caso, watchpoint é a melhor (única?) opção.

    Fazendo o programa continuar:
    (gdb) c
    Continuing.
    i=10 global=90
    Hardware watchpoint 2: v_global == 90
    Old value = 1
    New value = 0
    0x0000000000400565 in main (argc=1, argv=0x7fffffffe188) at main.c:31
    31 for (i = 0; i < TEST_VALUE; i++, v_global--)

    E ele parou de novo, por causa da transição (houve uma referência à expressão).
    Vamos apagar todos os breapoints novamente, adicionar um rwatch no lugar e executar o programa:
    (gdb) delete breakpoints
    Delete all breakpoints? (y or n) y
    (gdb) rwatch v_global
    Hardware read watchpoint 3: v_global
    (gdb) run
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: ./gdb0
    Hardware read watchpoint 3: v_global
    Value = 100
    0x0000000000400536 in main (argc=1, argv=0x7fffffffe188) at main.c:32
    32 fprintf(my_out, "i=%d global=%d\n", i, v_global);

    Podemos ver que o programa parou na primeira vez que o valor de v_global foi lido (e não é no for :) ).
    Se quiser, continue com c até cansar. Note que o watchpoint é tratado como breakpoint quando se trata de remoção.

    Um detalhe importante é a diferença entre Hardware Watchpoint e Software Watchpoint. Quando usamos Hardware Watchpoint, o GDB é capaz de detectar e parar o programa na próxima instrução, funcionando mesmo que a referência/alteração seja feita em outras threads. Software Watchpoints, por outro lado, tem granularidade de statement. Entretanto, nada disso é grátis: Hardware watchpoints tem número limitado. Veja também tracepoint.

    Depois dessa overdose de breakpoints, vamos descansar um pouco e ver como verificar o conteúdo dos registradores da CPU. Mas antes vamos aproveitar e deixar tudo limpinho:
    (gdb) delete breakpoints
    Delete all breakpoints? (y or n) y
    (gdb) start
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Temporary breakpoint 5 at 0x400514: file main.c, line 28.
    Starting program: ./gdb0
    Temporary breakpoint 5, main (argc=1, argv=0x7fffffffe1a8) at main.c:28
    28 FILE *my_out = NULL;
    (gdb) info register
    rax 0x3cc7175f60 261038235488
    rbx 0x0 0
    rcx 0x0 0
    rdx 0x7fffffffe1b8 140737488347576
    rsi 0x7fffffffe1a8 140737488347560
    rdi 0x1 1
    rbp 0x7fffffffe0c0 0x7fffffffe0c0
    rsp 0x7fffffffe090 0x7fffffffe090
    r8 0x3cc7174300 261038228224
    r9 0x3cc6a0e4d0 261030470864
    r10 0x7fffffffdf20 140737488346912
    r11 0x3cc6e1ea20 261034732064
    r12 0x400420 4195360
    r13 0x7fffffffe1a0 140737488347552
    r14 0x0 0
    r15 0x0 0
    rip 0x400514 0x400514 <main+16>
    eflags 0x206 [ PF IF ]
    cs 0x33 51
    ss 0x2b 43
    ds 0x0 0
    es 0x0 0
    fs 0x0 0
    gs 0x0 0
    fctrl 0x37f 895
    fstat 0x0 0
    ftag 0xffff 65535
    fiseg 0x0 0
    fioff 0x0 0
    foseg 0x0 0
    fooff 0x0 0
    fop 0x0 0
    mxcsr 0x1f80 [ IM DM ZM OM UM PM ]

    Para ver o conteúdo de todos os registradores, especialmente os de multimedia (se houver), comandamos
    (gdb) info all-registers
    rax 0x3cc7175f60 261038235488
    rbx 0x0 0
    rcx 0x0 0
    rdx 0x7fffffffe1b8 140737488347576
    rsi 0x7fffffffe1a8 140737488347560
    rdi 0x1 1
    rbp 0x7fffffffe0c0 0x7fffffffe0c0
    rsp 0x7fffffffe090 0x7fffffffe090
    r8 0x3cc7174300 261038228224
    r9 0x3cc6a0e4d0 261030470864
    r10 0x7fffffffdf20 140737488346912
    r11 0x3cc6e1ea20 261034732064
    r12 0x400420 4195360
    r13 0x7fffffffe1a0 140737488347552
    r14 0x0 0
    r15 0x0 0
    rip 0x400514 0x400514 <main+16>
    eflags 0x206 [ PF IF ]
    cs 0x33 51
    ss 0x2b 43
    ds 0x0 0
    es 0x0 0
    fs 0x0 0
    gs 0x0 0
    st0 0 (raw 0x00000000000000000000)
    st1 0 (raw 0x00000000000000000000)
    st2 0 (raw 0x00000000000000000000)
    st3 0 (raw 0x00000000000000000000)
    st4 0 (raw 0x00000000000000000000)
    st5 0 (raw 0x00000000000000000000)
    st6 0 (raw 0x00000000000000000000)
    st7 0 (raw 0x00000000000000000000)
    fctrl 0x37f 895
    fstat 0x0 0
    ftag 0xffff 65535
    fiseg 0x0 0
    fioff 0x0 0
    foseg 0x0 0
    fooff 0x0 0
    fop 0x0 0
    xmm0 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0xff, 0x0 <repeats 15 times>}, v8_int16 = {0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0xff, 0x0, 0x0, 0x0}, v2_int64 = {0xff, 0x0}, uint128 = 0x000000000000000000000000000000ff}
    xmm1 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x2f <repeats 16 times>}, v8_int16 = {0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f}, v4_int32 = {0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f}, v2_int64 = {
    0x2f2f2f2f2f2f2f2f, 0x2f2f2f2f2f2f2f2f}, uint128 = 0x2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f}
    xmm2 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm3 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0xff00, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0xff00, 0x0, 0x0}, v2_int64 = {
    0xff0000000000, 0x0}, uint128 = 0x00000000000000000000ff0000000000}
    xmm4 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm5 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm6 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm7 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm8 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm9 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm10 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm11 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm12 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm13 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm14 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    xmm15 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}
    mxcsr 0x1f80 [ IM DM ZM OM UM PM ]

    Para examinar a FPU:
    (gdb) info float
    R7: Empty 0x00000000000000000000
    R6: Empty 0x00000000000000000000
    R5: Empty 0x00000000000000000000
    R4: Empty 0x00000000000000000000
    R3: Empty 0x00000000000000000000
    R2: Empty 0x00000000000000000000
    R1: Empty 0x00000000000000000000
    =>R0: Empty 0x00000000000000000000
    Status Word: 0x0000
    TOP: 0
    Control Word: 0x037f IM DM ZM OM UM PM
    PC: Extended Precision (64-bits)
    RC: Round to nearest
    Tag Word: 0xffff
    Instruction Pointer: 0x00:0x00000000
    Operand Pointer: 0x00:0x00000000
    Opcode: 0x0000

    Alternativamente, se quisermos examinar o valor de um registrador específico:
    (gdb) p/x $rax
    $7 = 0x3cc7175f60

    O /x nesse caso serve para que o resultado seja mostrado em formato hexadecimal. A especificação de formato pode ser usada em várias situações.

    Para alterar o valor de RAX para NULL:
    (gdb) set var $rax = 0
    Agora, se pedirmos para ver novamente o valor de rax:
    (gdb) p/x $rax
    $8 = 0x0

    Podemos alterar o valor de qualquer registrador. Claro que, dependendo do valor e do registrador, também poderemos provocar resultados catastróficos.

    Às vezes pode ser útil ver as instruções de máquina decodificadas de uma região do programa. Quando não temos informação de debug disponível, essa pode ser a única forma. Por exemplo, para ver as instruções que compoe a função main:
    (gdb) disas main
    Dump of assembler code for function main:
    0x0000000000400504 <main+0>: push %rbp
    0x0000000000400505 <main+1>: mov %rsp,%rbp
    0x0000000000400508 <main+4>: push %rbx
    0x0000000000400509 <main+5>: sub $0x28,%rsp
    0x000000000040050d <main+9>: mov %edi,-0x24(%rbp)
    0x0000000000400510 <main+12>: mov %rsi,-0x30(%rbp)
    0x0000000000400514 <main+16>: movq $0x0,-0x18(%rbp)
    0x000000000040051c <main+24>: mov 0x20040d(%rip),%rax # 0x600930 <stdout@@GLIBC_2.2.5>
    0x0000000000400523 <main+31>: mov %rax,-0x18(%rbp)
    0x0000000000400527 <main+35>: movl $0x0,-0x1c(%rbp)
    0x000000000040052e <main+42>: jmp 0x400565 <main+97>
    0x0000000000400530 <main+44>: mov 0x2003ee(%rip),%ecx # 0x600924 <v_global>
    0x0000000000400536 <main+50>: mov $0x400678,%ebx
    0x000000000040053b <main+55>: mov -0x1c(%rbp),%edx
    0x000000000040053e <main+58>: mov -0x18(%rbp),%rax
    0x0000000000400542 <main+62>: mov %rbx,%rsi
    0x0000000000400545 <main+65>: mov %rax,%rdi
    0x0000000000400548 <main+68>: mov $0x0,%eax
    0x000000000040054d <main+73>: callq 0x400410 <fprintf@plt>
    0x0000000000400552 <main+78>: addl $0x1,-0x1c(%rbp)
    0x0000000000400556 <main+82>: mov 0x2003c8(%rip),%eax # 0x600924 <v_global>
    0x000000000040055c <main+88>: sub $0x1,%eax
    0x000000000040055f <main+91>: mov %eax,0x2003bf(%rip) # 0x600924 <v_global>
    0x0000000000400565 <main+97>: cmpl $0x63,-0x1c(%rbp)
    0x0000000000400569 <main+101>: jle 0x400530 <main+44>
    0x000000000040056b <main+103>: mov $0x0,%eax
    0x0000000000400570 <main+108>: add $0x28,%rsp
    0x0000000000400574 <main+112>: pop %rbx
    0x0000000000400575 <main+113>: leaveq
    0x0000000000400576 <main+114>: retq
    End of assembler dump.

    Para descobrir onde está uma linha específica, por exemplo, a linha 32 do main.c:
    (gdb) info line 32
    Line 32 of "main.c" starts at address 0x400530 <main+44> and ends at 0x400552 <main+78>.

    Por fim, se quisermos que a função main seja decodificada juntamente com uma anotação mostrando onde cada linha começa:
    (gdb) disas /m main
    Dump of assembler code for function main:
    26 {
    0x0000000000400504 <main+0>: push %rbp
    0x0000000000400505 <main+1>: mov %rsp,%rbp
    0x0000000000400508 <main+4>: push %rbx
    0x0000000000400509 <main+5>: sub $0x28,%rsp
    0x000000000040050d <main+9>: mov %edi,-0x24(%rbp)
    0x0000000000400510 <main+12>: mov %rsi,-0x30(%rbp)
    27 int i;
    28 FILE *my_out = NULL;
    0x0000000000400514 <main+16>: movq $0x0,-0x18(%rbp)
    29
    30 my_out = stdout;
    0x000000000040051c <main+24>: mov 0x20040d(%rip),%rax # 0x600930 <stdout@@GLIBC_2.2.5>
    0x0000000000400523 <main+31>: mov %rax,-0x18(%rbp)
    31 for (i = 0; i < TEST_VALUE; i++, v_global--)
    0x0000000000400527 <main+35>: movl $0x0,-0x1c(%rbp)
    0x000000000040052e <main+42>: jmp 0x400565 <main+97>
    0x0000000000400552 <main+78>: addl $0x1,-0x1c(%rbp)
    0x0000000000400556 <main+82>: mov 0x2003c8(%rip),%eax # 0x600924 <v_global>
    0x000000000040055c <main+88>: sub $0x1,%eax
    0x000000000040055f <main+91>: mov %eax,0x2003bf(%rip) # 0x600924 <v_global>
    0x0000000000400565 <main+97>: cmpl $0x63,-0x1c(%rbp)
    0x0000000000400569 <main+101>: jle 0x400530 <main+44>
    32 fprintf(my_out, "i=%d global=%d\n", i, v_global);
    0x0000000000400530 <main+44>: mov 0x2003ee(%rip),%ecx # 0x600924 <v_global>
    0x0000000000400536 <main+50>: mov $0x400678,%ebx
    0x000000000040053b <main+55>: mov -0x1c(%rbp),%edx
    0x000000000040053e <main+58>: mov -0x18(%rbp),%rax
    0x0000000000400542 <main+62>: mov %rbx,%rsi
    0x0000000000400545 <main+65>: mov %rax,%rdi
    0x0000000000400548 <main+68>: mov $0x0,%eax
    0x000000000040054d <main+73>: callq 0x400410 <fprintf@plt>
    33 return 0;
    0x000000000040056b <main+103>: mov $0x0,%eax
    34 }
    0x0000000000400570 <main+108>: add $0x28,%rsp
    0x0000000000400574 <main+112>: pop %rbx
    0x0000000000400575 <main+113>: leaveq
    0x0000000000400576 <main+114>: retq
    End of assembler dump.

    Agora que já sabemos como colocar breakpoints, examinar e alterar o valor de variáveis, vamos ver como fazemos para bisbilhotar uma região de memória. Por exemplo, para examinar o valor da região de memória próxima à variável stdout, podemos usar o comando x:
    (gdb) x/16xb 0x3cc7173d70
    0x3cc7173d70 <stdout>: 0x80 0x37 0x17 0xc7 0x3c 0x00 0x00 0x00
    0x3cc7173d78 <stderr>: 0x60 0x38 0x17 0xc7 0x3c 0x00 0x00 0x00
    Veja que o GDB adiciona o nome do símbolo mais próximo da área examinada, se houver. Sabemos que stdout é um ponteiro, então os 8 primeiros bytes correspondem ao endereço da área onde está a struct (em little endian, invertido):
    0x80 0x37 0x17 0xc7 0x3c 0x00 0x00 0x00
    Colocando na ordem correta, temos:
    0000003cc7173780.

    Comparando com o valor de stdout:
    (gdb) p stdout
    $13 = (struct _IO_FILE *) 0x3cc7173780

    O comando x aceita alguns parâmetros, que especificam a quantidade (16), o tipo de formatação (x=hexadecimal) e o tamanho dos dados (b=byte) que queremos ver no dump.

    Os exemplos acima usaram o GDB para carregar e executar o programa, mas não tem de ser assim. Podemos fazer o GDB debugar um programa que já está em execução. Por exemplo, vamos fazer uma pequena alteração no programa gdb0, e chamá-lo de gdb1, para que fique assim:

    /*
     *  Copyright (C) 2009 William Cesar de Oliveira
     *
     *  This program is free software: you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation, either version 2 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #define TEST_VALUE 100
    
    int v_global = TEST_VALUE;
    
    int main(int argc, char *argv[])
    {
    	int i;
    	FILE *my_out = NULL;
    
    	sleep(60);
    	my_out = stdout;
    	for (i = 0; i < TEST_VALUE; i++, v_global--)
    		fprintf(my_out, "i=%d global=%d\n", i, v_global);
    	return 0;
    }
    

    Com essa alteração, o programa vai esperar 60 segundos antes de continuar. Assim, teremos tempo de executar o GDB (espero). Agora, executamos o programa em segundo plano:
    # ./gdb1 &

    O shell vai colocar o número do PID do programa. Se não o fizer, podemos descobrir com:
    # ps -C gdb1
    PID TTY TIME CMD
    13566 pts/3 00:00:00 gdb1

    Em seguida, usamos o PID do processo (nesse caso, 13566) para dizer ao GDB qual o processo queremos debugar:
    gdb --pid 13566

    O GDB vai iniciar e tomar conta do processo a ser debugado, interrompendo a execução na próxima instrução. A partir desse ponto, podemos continuar com o processo de debug como estávamos fazendo anteriormente, não há distinção entre as duas sessões.

    Normalmente quando um bug aparece não estamos lá para ver, muito menos rodando o programa no GDB. Com sorte, às vezes temos disponível um core dump para ser analisado, e podemos trabalhar com a chamada análise post-mortem. Quando ocorre um erro de execução, por exempo, se o sistema estiver corretamente configurado, um core dump poderá ser gerado. Podemos também obter um core dump de um programa que está em execução ou de um programa que está sendo debugado.
    Escrevi o exemplo 3 para provocar um core dump devido à um sinal não manipulado. Se quisermos provocar um core dump, podemos usar o programa gcore com o PID do processo que queremos obter o core dump. A última opção é o comando gcore dentro do gdb, o que pode ser útil quando queremos um instantâneo da sessão para análise posterior.
    Vamos ver o que acontece quando executamos o programa de testes:
    # ./gdb2
    Segmentation fault (core dumped)

    Por padrão o core dump será gerado no diretório corrente no momento da geração do core, com o nome core.PID.
    Agora vamos instruir o GDB para carregar o core dump e os símbolos, supondo que o PID do processo que gerou o core dump seja 17247:
    # gdb --core=core.17247 --symbols=./gdb2
    Reading symbols from ./gdb2...done.
    Core was generated by `./gdb2'.
    Program terminated with signal 11, Segmentation fault.
    #0 0x00000000004005f2 in main (argc=1, argv=0x7fff35aad778) at main.c:40
    40 *bug = 0;

    Com isso já sabemos em qual linha foi gerada a falha. Podemos inspecionar e alterar os valores das variáveis, dos registradores, etc.

    Para saber mais como o core é gerado e como controlar a geração do arquivo, veja man 5 core.

    Dicas
    Os nomes dos comandos podem ser abreviados, desde que não entrem em conflito com outro comando. Por exemplo, para o comando disassemble, podemos digitar somente disas.

    Podemos fazer com que um comando seja sempre executado quando o processo for interrompido, usando display. O comando display é muito parecido com o p (print), mas será executado automaticamente cada vez que o programa debugado for paralisado.

    Outro ponto interessante é como o GDB interpreta e localiza o nome de um símbolo, já que em C são permitidas variáveis com o mesmo nome em escopos diferentes. O contexto atual é o da função que está em execução (ou melhor, a pilha atual). Não é possível referenciar variáveis locais em funções que não estão em execução nem fazem parte da pilha de chamadas, mas é possível especificar o contexto, usando ::. Por exemplo, considere o seguinte trecho de código num arquivo chamado teste.c:

    int my_test = 1234;
    
    int f_test(int my_test)
    {
    	return my_test * 2;
    }
    
    int main(int argc, char *argv[])
    {
    	int my_test;
    	for (i = 0; i < 100; i++)
    		print(hello world (%d)\n", my_test, f_test(my_test));
    	return 0;
    }
    

    Supondo um breakpoint em f_test, se quisermos ver o valor da variável global my_test, devemos
    p &'teste.c'::my_test
    Por outro lado, para referenciar a variável local my_test, dentro da função main, podemos
    p &main::my_test
    Se solicitarmos informações sobre my_test sem definir o escopo, veremos o my_test da pilha atual (o my_test declado dentro de f_test):
    p &my_test

    De dentro do GDB, digite help para obter ajuda ou a descrição resumida de algum comando, por exemplo, para obter um breve descrição do comando break:
    (gdb) help break
    Set breakpoint at specified line or function.
    break [LOCATION] [thread THREADNUM] [if CONDITION]
    LOCATION may be a line number, function name, or "*" and an address.
    If a line number is specified, break at start of code for that line.
    If a function is specified, break at start of code for that function.
    If an address is specified, break at that exact address.
    With no LOCATION, uses current execution address of selected stack frame.
    This is useful for breaking on return to a stack frame.
    THREADNUM is the number from "info threads".
    CONDITION is a boolean expression.
    Multiple breakpoints at one place are permitted, and useful if conditional.
    Do "help breakpoints" for info on other commands dealing with breakpoints.

    Antes de finalizar, três assuntos interessantes para pesquisa: variáveis de conveniência, scripts e debug remoto.

    Por último, se você ainda não descobriu, para sair do GDB:
    quit

    Multithreading no Linux – User mode

    Bom, suponho que nessa altura do campeonato todos estão muito bem informados sobre o que é multithread, convencidos sobre seus benefícios e conscientes dos problemas que enfrentamos quando fazemos uso dessa técnica. Se você ainda tem dúvidas, dê uma olhada aqui.

    No Linux podemos usar pthreads para implementação de multithreading. Pthreads (POSIX threads) é um padrão de implementação de multithreading, composto por uma lib (libpthread) e um header (pthread.h).

    Só para lembrar, comunicação entre threads requer métodos específicos que vão além do uso de simples variáveis compartilhadas. Mutexes e variáveis condicionais são dois desses métodos: mutexes servem para limitar a execução de um trecho de código a uma thread por vez e condicionais são usadas para sinalizar uma condição para outras threads.

    As estruturas básicas encontradas em pthread.h são:

    pthread_t identifica uma thread
    pthread_mutex_t tipo usado para manipular mutexes
    pthread_cond_t tipo usado para manipular conditions

    Além disso, declarada em time.h,
    struct timespec
    também é frequentemente usada.

    As funções essenciais da API são:

    pthread_create cria uma thread. Uma thread é simplesmente uma função que é executada independemente do fluxo do código que chama pthread_create.
    pthread_join aguarda que uma thread finalize. Não há uso de CPU enquanto a thread estiver em espera.
    pthread_mutex_init inicializa um mutex.
    pthread_mutex_destroy deve ser chamado quando um mutex não for mais usado.
    pthread_mutex_lock “pega” o mutex. Somente uma thread por vez consegue executar essa função. Se uma outra chamada ocorrer, a thread que chamou será colocada em espera. Não há uso de CPU enquanto a thread estiver em espera.
    pthread_mutex_unlock devolve o mutex. Se houver alguma thread esperando em pthread_mutex_lock, a thread que estiver esperando é liberada. Se houver mais de uma thread esperando, somente uma será liberada, mas não é possível determinar com antecedência qual delas.
    pthread_cond_init inicializa uma condition vatriable.
    pthread_cond_destroy deve ser chamado quando uma condition variable não for mais ser usada.
    pthread_cond_wait aguarda pela sinalização de uma condition. Não há uso de CPU enquanto a thread estiver em espera.
    pthread_cond_signal Libera uma, e somente uma, das threads aguardando pela condition em pthread_cond_wait.
    pthread_cond_broadcast Libera todas as threads que estiverem aguardando pela condition em pthread_cond_wait.

    Talvez seja mais fácil de entender estudando alguns exemplos.

    Exemplo 1
    Criando threads.
    Usando o projeto esqueleto, criei um programa bem simples que cria 10 threads que mostram na saída padrão um número, que também é usado no retorno da thread, aguarda até que todas finalizem, mostrando o resultado retornado por cada thread.

    Exemplo 2
    Erros provocados por falta de sincronização.
    Este programa apresenta comportamento imprevisível. Essa é uma das falhas mais traiçoeiras, porque o programa não “explode”, ele até funciona, mas falha “inexplicavelmente” de vez em quando. Usando o projeto esqueleto criei um programa bem simples que demonstra como a utilização incorreta de multithreading pode causar dores de cabeça. Antes de entrar no maravilhoso mundo das múltiplas linhas de execução, porém, vou explicar porque tive usar variáveis qualificadas como volatile. Durante o processo de compilação, podemos instruir ao GCC, entre outras coisas, como queremos que o objeto criado a partir do código seja otimizado. Nesse exemplo estamos usando O3, o que significa que, entre outras coisas, o compilador pode eliminar um loop, se esse não houver efeitos colaterais. Por exemplo, ao encontrar

    int i;
    int j;
    for (j = 0, i = 0; i < 10; i++)
    	j++;
    

    o compilador pode otimizar para algo semelhante a

    i = 10;
    j = 10;
    

    desprezando o loop.

    Se fizermos questão que o loop seja executado, temos 2 opções:
    1) desligamos a otimização, usando -O0 (ou desligando somente a otimização que nos afeta).
    2) declaramos a variável como volatile, o que instrui o compilador a acessar o conteúdo da variável a cada acesso (ou, em português claro, não otimizar código em que a variável volatile estiver envolvida).
    Então, usando volatile, vamos forçar o loop a ser executado, fazendo o erro aparecer.

    Vamos supor que a CPU não consiga fazer uma soma de inteiros em uma única operação nem diretamente na memória, mas tenha que usar um armazenamento temporário em um registrador para fazer a soma e, só então, transferir o resultado de volta para a memória. Essa simples operação pode resultar em erro se estivermos em execução paralela. O erro ocorre porque, com as threads rodando em paralelo, pode ser que uma esteja somando o valor da variável e, antes que o valor seja atribuído à variável, outra thread altere o valor. Nesse caso, o valor do último cálculo será perdido, e o resultado não será o esperado. Talvez seja mais fácil entender usando uma figura:

    Exemplo 3
    Sincronizando o acesso do Exemplo 2 usando mutex.
    Uma das formas de consertar o defeito é utilizar um mutex, restringindo o acesso à um determinado trecho de código a uma única thread. Se alguma outra thread solicitar o ingresso no mesmo trecho, terá que esperar até a thread inicial "devolva" a permissão. A mesma figura anterior, agora com o mutex:

    Infelizmente, embora funcione corretamente, o programa vai executar muito mais lentamente. Frequentemente é possível melhorar o desempenho alterando a forma como o programa foi projetado. Assunto para um outro texto.

    Exemplo 4
    Este exemplo mostra como utilizar uma conditional variable para esperar a ocorrência de um evento, usando pthread_cond_signal, e seus efeitos quando muitas threads estão aguardando o mesmo evento.

    Exemplo 5
    Este exemplo mostra como utilizar uma conditional variable para esperar a ocorrência de um evento, usando pthread_cond_broadcast, e seus efeitos quando muitas threads estão aguardando o mesmo evento.

    Exemplo 6: TODO: deadlocks.

    Audio Streaming – Comunicação Serial

    Este texto faz parte da série sobre ALSA Streaming, apesar de não ter nada a ver com ALSA nem com streaming. É que a comunicação usando a porta serial serve para controlar o scanner Uniden, enviando e recebendo comandos a partir dele. Como ese foi o motivo dessa série…

    Para encontrar informações sobre o padrão que define o funcionamento do ponto de vista elétrico, pinagem, UART, etc, fora do escopo desse texto, consulte esse site, muito bom. Ou aqui, Wikipedia. Também fora do escopo do texto, informações sobre como o kernel trata a porta serial e tty em geral, consulte, no diretório $LINUX/Documentation, os arquivos serial/driver e tty.txt.

    Em user mode, o acesso à porta serial no Linux é feito através de um device file de caractere (oposto ao device file de bloco), e usa a estrutura de terminal. Apesar de poder aparecer em qualquer lugar, o device da porta serial normalmente está dentro do diretório /dev, com um nome que ttyS mais um dígito, geralmente indicando o número da porta. Por exemplo, /dev/ttyS0 para a primeira porta serial, /dev/ttyS1 para a segunda porta, e assim por diante (conversores USB/RS232 podem aparecer com nome diferente) . Basta abrir o device file, por exemplo, com open(), para transmitir/ler dados e através de IOCTLs podemos controlar os parâmetros de funcionamento da porta serial, como baud rate, paridade, etc., mas antes de entrar em detalhes, vamos ver as ferramentas existentes no sistema operacional que podem ser usadas para controlar, enviar e receber dados das portas seriais.

    Para Exemplo
    mandar uma string echo “hello world” > /dev/ttyS0
    mandar um arquivo cat file.txt > /dev/ttyS0
    ler e mostrar no terminal cat /dev/ttyS0
    mostrar a configuração stty -F /dev/ttyS0
    para configurar para 9600 8N1 stty -F /dev/ttyS0 9600 -clocal cread -crtscts cs8 -cstopb -parenb
    outras configurações específicas de porta serial setserial

    stty serve para controlar as características gerais de um terminal, incluindo eco local, interpretação de new line, etc, por isso algumas das opções de configuração parecem ter pouco a ver com uma porta serial e mais com o console.

    A libc fornece algumas funções para configurar terminais e também portas seriais. Quase todas as funções usam uma estrutura chamada termios:

    #define NCC 8
    struct termio {
            unsigned short c_iflag;         /* flags de entrada */
            unsigned short c_oflag;         /* flags de saída */
            unsigned short c_cflag;         /* flags de controle */
            unsigned short c_lflag;         /* flags de controle local */
            unsigned char c_line;           /* line discipline */
            unsigned char c_cc[NCC];        /* caracteres de controle */
    };
    

    A API:
    Todas as funções da API usam um file descriptor, daqueles retornados normalmente por “open”.

    	/* preenche uma estrutura termios com a configuração do terminal indicado por fd */
           int tcgetattr(int fd, struct termios *termios_p);
    	/* configura o terminal indicado por fd de acordo com os parâmetros definidos na estrutura termios */
           int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
    	/* coloca o terminal em condição de break */
           int tcsendbreak(int fd, int duration);
    	/* aguarda até o buffer de saída ser transmitido */
           int tcdrain(int fd);
    	/* descarta tudo que estiver pendente tanto para recepção como para transmissão */
           int tcflush(int fd, int queue_selector);
    	/* usado quando o fluxo estiver sendo controlado por software, envia start/stop e/ou para/inicia a recepção */
           int tcflow(int fd, int action);
    

    As funções a seguir ajudam a manipular a estrutura termios.

    	/* configura termios para modo "raw": entrada caracter por caracter (ao contrário de linha a linha), eco local desligado, sem processamento especial de caracteres de controle:
               termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
               termios_p->c_oflag &= ~OPOST;
               termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
               termios_p->c_cflag &= ~(CSIZE | PARENB);
               termios_p->c_cflag |= CS8; */
           void cfmakeraw(struct termios *termios_p);
    	/*
             * retorna o baud rate codificado para entrada (cfgetispeed) ou saída (cfgetospeed).
             * RS232 usa o mesmo baud rate para entrada e saida.
             */
           speed_t cfgetispeed(const struct termios *termios_p);
           speed_t cfgetospeed(const struct termios *termios_p);
    	/* define o baud rate para entrada (cfsetispeed), saída (cfsetospeed) ou entrada e saída (cfsetspeed) */
           int cfsetispeed(struct termios *termios_p, speed_t speed);
           int cfsetospeed(struct termios *termios_p, speed_t speed);
           int cfsetspeed(struct termios *termios_p, speed_t speed);
    

    O baud rate em termios está codificado e não deve ser usado pelo seu valor absoluto, mas sim através das seguintes macros:

    Nome Baud Rate
    B0 Hang up
    B50 50
    B75 75
    B110 110
    B134 134
    B150 150
    B200 200
    B300 300
    B600 600
    B1200 1200
    B1800 1800
    B2400 2400
    B4800 4800
    B9600 9600
    B19200 19200
    B38400 38400
    B57600 57600
    B115200 115200
    B230400 230400

    Para mostrar como é fácil lidar com a porta serial no Linux, fiz um programinha de exemplo, usando o projeto esqueleto, que configura a porta serial, manda uma string e aguarda uma resposta por até 3 segundos, terminada em LINE FEED.

    No próximo texto vou implementar o protocolo completo do BCT-8.

    Debug – User mode – 1

    Nessa série de textos vou tentar mostrar algumas técnicas/programas básicos para mapear o funcionamento e identificar problemas em programas executando em modo de usuário, ou, para alguns, debugar. Existem várias técnicas e situações diferentes, e para cada caso existe uma ou mais ferramentas.
    Vou começar então pelas ferramentas disponíveis no Linux que podem ajudar nessa tarefa.

    /proc
    O /proc contém muitas informações valiosíssimas sobre o estado do sistema, dos drivers e de cada processo em execução. As informações sobre um determinado processo estão em um diretório do /proc cujo nome coincide com o PID do processo. De acordo com $LINUX/Documentation/filesystem/procfs.txt e os fontes em $LINUX/fs/procfs/, as entradas dentro do diretório de um processo em /proc são (no 2.6.31):

    Entrada Tipo Descrição
    attr diretório Atributos relacionados à extensão de segurança (SELinux, AppArmor, etc)
    auxv somente leitura O aux vector do processo
    cgroup somente leitura Control group
    clear_refs somente escrita Limpa o campo “Referenced” do smaps, para ajudar a monitorar
    cmdline somente leitura Argumentos da linha de comando
    coredump_filter leitura/escrita Determina/verifica quais páginas de memória serão gravadas em disco em caso de coredump.
    cpuset somente leitura Quais CPUs estão disponíveis para esse processo (/=todas)
    cwd link para diretório link para o diretório atual
    environ somente leitura Valores das variáveis de ambiente
    exe link para arquivo aponta para o arquivo executável. Pode ser inválido se o processo foi criado pelo kernel.
    fd diretório Contém todos os file descriptors, em forma de link para o arquivo real. Mostra também sockets e pipes.
    fdinfo diretório Contém informações sobre o estado de cada file descriptor (2.6.31: flags de abertura/criação e posição do cursor para próxima operação de IO).
    io somente leitura Informações sobre IO (leitura, escrita, etc).
    latency somente leitura Ajuda a descobrir onde o processo está gastando mais tempo
    limits somente leitura Valores limite (nice, memória, penging signals, etc)
    loginuid leitura/escrita id do usuário owner do processo
    maps somente leitura Mostra onde estão carregados as dependências e os executáveis, além dos atributos de cada seção.
    mem leitura/escrita Memória em uso
    mountinfo somente leitura Informações adicionais sobre filesystems montados
    mounts somente leitura Filesystems montados visíveis
    mountstats somente leitura subset de mounts, formatado
    net diretório Estatísticas e informações relacionadas à rede.
    numa_maps somente leitura Páginas alocadas por node e memory policy
    oom_adj leitura/escrita Ajusta o score para escolha do processo a ser matado se o kernel estive em condição de pouca memória
    oom_score somente leitura Score para escolha do processo a ser matado se o kernel estive em condição de pouca memória
    pagemap somente leitura Estado da paginação de memória (para cada página no espaço de endereçamento)
    personality somente leitura ABI em uso, veja os valores em $LINUX/include/linux/personality.h (0×00000000=Linux)
    root link Link para o diretório que esse processo considera como root
    sched somente leitura Info do scheduler
    schedstat somente leitura Mais info do scheduler
    sessionid somente leitura o task->session_id, usado em audit.
    smaps somente leitura extensão de maps, mostra dados de utilização de memória de cada mapeamento.
    stack somente leitura Stack trace
    stat somente leitura Informações diversas, consulte a função do_task_stat em $LINUX/fs/proc/array.c
    statm somente leitura Estatísticas de memória, no 2.6.31: size, resident, shared, text, lib, data, NULL
    status somente leitura Quase o mesmo que stat, mas formatado.
    syscall somente leitura Mostra os parâmetros do último syscall. O primeiro ítem é o número do syscall, consulte $LINUX/include/asm-generic/unistd.h
    task diretório Informações de cada thread
    wchan somente leitura Caso o processo esteja em sleep, o símbolo ou o endereço da função onde está

    ldd
    quase todos os programas usam bibliotecas. Sem entrar no mérito, o fato é que muitas vezes o programa não funciona, ou porque não consegue resolver suas dependências de biblioteca dinâmica ou porque carrega a biblioteca incorreta. ldd nos ajuda a descobrir quais são as bibliotecas dependentes e as versões que serão carregadas quando um programa for executado. Entretanto, ldd não vai mostrar as bibliotecas carregadas por demanda, ou seja, somente quando o programa decide carregá-las, sem que a dependência seja explícita. Por exemplo:
    $ ldd /sbin/echo
    linux-vdso.so.1 => (0x00007fff9ffa8000)
    libc.so.6 => /lib64/libc.so.6 (0x00000034d0400000)
    /lib64/ld-linux-x86-64.so.2 (0x00000034d0000000)

    significa que o programa echo depende das bibliotecas linux-vdso.so.1, libc.so.6 e /lib64/ld-linux-x86-64.so.2 para ser executado.

    Outro caso:
    $ ldd ./zvent
    linux-gate.so.1 => (0x00d0c000)
    liblotes.so => not found
    libpthread.so.0 => /lib/libpthread.so.0 (0x00a63000)
    libresolv.so.2 => /lib/libresolv.so.2 (0x00269000)
    libdl.so.2 => /lib/libdl.so.2 (0x00a30000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a7f000)
    libm.so.6 => /lib/libm.so.6 (0x00a37000)
    libc.so.6 => /lib/libc.so.6 (0x008b9000)
    libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00ba6000)
    /lib/ld-linux.so.2 (0x00897000)

    Nesse caso, a dependência liblotes.so não pode ser resolvida, e o programa provavelmente não vai carregar.

    Usando o /proc para inspecionar um processo em execução podemos ver que as libs efetivamente carregadas nem sempre são as reportadas por ldd. Faça uma experiência com um processo complexo, como firefox, pidgin ou java, por exemplo. Supondo que você conheça o PID de um processo e queira ver o que o ldd reporta como dependência:
    $ ldd /proc/${THE_PID}/exe

    Para saber quais são as libs efetivamente mapedas:
    $ cat /proc/${THE_PID}/maps | sed -ne 's/[^\/]*\(.*\.so\)/\1/p' | sort -u

    ps
    Comando simples, mas muito útil, ajuda a responder como, quando, por quem e quais recursos estão sendo usados por um processo. Alguns exemplos:

    ps -C ps Mostra todos as instâncias atualmente em execução do cujo executável tem o nome “ps”
    ps -U 1234 Mostra os processos associados ao userid 1234
    ps -fp 1 Mostra os detalhes do processo init
    ps -Af Lista detalhes de todos os processos em execução

    kill
    kill envia um signal para um processo, cada signal tem uma função diferente, alguns deles são interpretados de forma específica por um programa específico. O kill é usado para simplesmente “matar” um processo, mas ele pode fazer mais. Para saber quais os signals disponívels, execute
    $ kill -l
    Esta é uma tabela com os principais signals (SIGTERM é o default do kill se você não especificar qual o signal).

    Signal Descrição
    SIGHUP Hangup (POSIX).
    SIGINT Interrupt (ANSI).
    SIGQUIT Quit (POSIX).
    SIGILL Illegal instruction (ANSI).
    SIGTRAP Trace trap (POSIX).
    SIGABRT Abort (ANSI).
    SIGIOT IOT trap (4.2 BSD).
    SIGBUS BUS error (4.2 BSD).
    SIGFPE Floating-point exception (ANSI).
    SIGKILL Kill, unblockable (POSIX).
    SIGUSR1 User-defined signal 1 (POSIX).
    SIGSEGV Segmentation violation (ANSI).
    SIGUSR2 User-defined signal 2 (POSIX).
    SIGPIPE Broken pipe (POSIX).
    SIGALRM Alarm clock (POSIX).
    SIGTERM Termination (ANSI).
    SIGSTKFLT Stack fault.
    SIGCLD Same as SIGCHLD (System V).
    SIGCHLD Child status has changed (POSIX).
    SIGCONT Continue (POSIX).
    SIGSTOP Stop, unblockable (POSIX).
    SIGTSTP Keyboard stop (POSIX).
    SIGTTIN Background read from tty (POSIX).
    SIGTTOU Background write to tty (POSIX).
    SIGURG Urgent condition on socket (4.2 BSD).
    SIGXCPU CPU limit exceeded (4.2 BSD).
    SIGXFSZ File size limit exceeded (4.2 BSD).
    SIGVTALRM Virtual alarm clock (4.2 BSD).
    SIGPROF Profiling alarm clock (4.2 BSD).
    SIGWINCH Window size change (4.3 BSD, Sun).
    SIGPOLL Pollable event occurred (System V) – Mesmo que SIGIO.
    SIGIO I/O now possible (4.2 BSD).
    SIGPWR Power failure restart (System V).
    SIGSYS Bad system call.

    Se tudo o que você quer é matar o processo de qualquer jeito, tente
    $ kill -9 PID_DO_PROCESSO
    Se isso não funcionar, só boot.

    lsof
    lsof é fantástico: ele mostra quais são os arquivos que estão abertos no sistema todo ou por um determinado processo (ou ainda usando outros critérios). O básico do lsof:

    Exemplo Descrição
    lsof Lista todos os arquivos abertos por todos os processos (pode haver restrições de acesso, para ver tudo somente como root)
    lsof -p 1 Lista os arquivos abertos pelo processo 1 (init)

    strace
    Esse é um comando poderosíssimo, e muito útil: ele monitora as chamadas de sistema (syscalls) feitas por um processo. Ele é capaz de decodificar a chamada e a resposta, mostrando os dados em forma simbólica, quando é o caso (flags, por exemplo, são mostrados por nome ao invés de um número). Ele ainda pode monitorar os processos-filho criados pelo processo monitorado.
    Veja um exemplo:

    $ strace echo "hello world"
    execve("/bin/echo", ["echo", "hello world"], [/* 46 vars */]) = 0
    brk(0)                                  = 0x1a08000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23e5fbd000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=143645, ...}) = 0
    mmap(NULL, 143645, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f23e5f99000
    close(3)                                = 0
    open("/lib64/libc.so.6", O_RDONLY)      = 3
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\354A\3204\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0755, st_size=2404376, ...}) = 0
    mmap(0x34d0400000, 3635368, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x34d0400000
    mprotect(0x34d056f000, 2093056, PROT_NONE) = 0
    mmap(0x34d076e000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16e000) = 0x34d076e000
    mmap(0x34d0773000, 18600, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x34d0773000
    close(3)                                = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23e5f98000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23e5f97000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23e5f96000
    arch_prctl(ARCH_SET_FS, 0x7f23e5f97700) = 0
    mprotect(0x34d076e000, 16384, PROT_READ) = 0
    mprotect(0x34d021d000, 4096, PROT_READ) = 0
    munmap(0x7f23e5f99000, 143645)          = 0
    brk(0)                                  = 0x1a08000
    brk(0x1a29000)                          = 0x1a29000
    open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=98765792, ...}) = 0
    mmap(NULL, 98765792, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f23e0165000
    close(3)                                = 0
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23e5fbc000
    write(1, "hello world\n", 12hello world)           = 12
    close(1)                                = 0
    munmap(0x7f23e5fbc000, 4096)            = 0
    close(2)                                = 0
    exit_group(0)                           = ?
    

    top
    O top mostra uma lista de processos classificados de acordo com a utilização dos recursos do sistema. Ao contrário do ps, que é um instantâneo do estado do sistema, top monitora continuamente as processos em execução. É útil se você quer, por exemplo, monitorar picos de utilização de CPU ou memória e descobrir os responsáveis. Além disso, top pode matar um processo ou modificar sua prioridade (um assunto para outra hora). top é um programa interativo, você pode controlá-lo através de teclas de atalho. A mais importante é ‘q’ para sair :)
    Aviso: top consome CPU ;-)
    Por exemplo, execute o top, tecle ’s’ e digite 0.1 – refresh em .1 segundos. O top irá aparecer… no topo, ou quase lá.

    Nos próximos textos, não perca o espetacular gdb e o sensacional valgrind.

    Listas no Kernel, 101

    OK, parece que a primeira versão desse texto estava meio “criptografada”, aí resolvi seguir a sugestão da minha avó: “quando estiver difícil de explicar, faça um desenho!”. Fiz vários.

    struct list_head {
            struct list_head *next, *prev;
    };
    
    struct list_head

    struct list_head

    Pedantic mode enabled.
    Essa struct define o header e os elementos de uma lista. O header, sempre necessário, aponta para ele mesmo quando a lista está vazia. Quando existem itens na lista, header->next aponta para o primeiro ítem e header->prev aponta para o último ítem (podem ser o mesmo se houver apenas 1 ítem na lista). Num node qualquer, node->next é o próximo ítem e node->prev é o ítem anterior, quando node for o último ítem, no entanto, node->next aponta para o header. Quando for o primeiro, node->prev aponta para o header. Ufa.

    Para simplificar, uma tabela mostrando para onde apontam prev e next, caso a caso:

    lista vazia 1 elemento n elementos
    header->next header primeiro primeiro
    header->prev header primeiro último
    primeiro->next - header segundo
    primeiro->prev - header header
    último->next - header header
    último->prev - header penúltimo

    Para quem gosta de figuras:
    Lista Vazia

    Lista Vazia

    Lista Vazia


    Com 1 Elemento
    Lista com UM elemento

    Lista com UM elemento


    Com 2 Elementos
    Lista com DOIS elementos

    Lista com DOIS elementos


    Para que ninguém precise reimplementar listas a cada novo driver, aumentando as chances de erro, além de ser chatíssimo, o kernel provê tipos, funções e macros que ajudam na manipulação da listas, a maioria das definições está em $KERNEL/include/linux/list.h. Aqui está o que eu considero mais importante, com os comentários traduzidos:

    /**
     * INIT_LIST_HEAD - inicializa struct  list_head *list para ser... o head de uma lista.
     */
    static inline void INIT_LIST_HEAD(struct list_head *list)
    
    /**
     * LIST_HEAD_INIT - inicializa um head estaticamente.
     */
    #define LIST_HEAD_INIT(name) { &(name), &(name) }
    
    /**
     * list_add - adiciona um novo ítem, antes de um ítem de referência.
     * @new: novo item a ser adicionado.
     * @head: item que virá depois do qual está sendo adicionado.
     *
     * Insere um novo ítem antes do head passado.
     * Boa para stacks.
     */
    static inline void list_add(struct list_head *new, struct list_head *head)
    
    /**
     * list_add_tail - adiciona um novo ítem, depois de um ítem de referência.
     * @new: novo item a ser adicionado.
     * @head: item que virá antes do qual está sendo adicionado.
     *
     * Insere um novo ítem depois do head passado.
     * Boa para filas.
     */
    
    static inline void list_add_tail(struct list_head *new, struct list_head *head)
    /**
     * list_del - deleta um ítem da lista.
     * @entry: o elemento a ser removido da lista.
     * Nota: chamar list_empty() depois dessa função usando o mesmo entry não retorna "true", entry fica em estado indefinido.
     */
    static inline void list_del(struct list_head *entry)
    

    E tem muitas outras funções, é claro. Até aí, nada que você não tenha aprendido nos primeiros 5 minutosna primeira semana de escola. Você deve estar se perguntando “mas onde raios está o payload?”. Uma lista, por si só, não faz sentido, ela precisa pelo menos apontar para alguma coisa útil. Essa implementação do kernel é interessante porque é genérica, permitindo que o programador adicione quantos struct list_head ele quiser na definição de uma struct, sem se preocupar com o tipo da struct usada, nem com alocação de memória (somente a que já teria que ser feita para a struct em si), nem com a ordem em que os struct list_head vão aparecer. É aí que entram as macros.

    Uma macro muito usada na manipulação das listas no kernel é a container_of:

    /**
     * container_of - converte um ponteiro para um membro de uma struct para um ponteiro para a struct onde ele está contido.
     * @ptr:        ponteiro para o membro da struct
     * @type:       o tipo da struct
     * @member:     o nome do membro dentro da struct
     *
     */
    #define container_of(ptr, type, member) ({                      \
            const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
            (type *)( (char *)__mptr - offsetof(type,member) );})
    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    

    O funcionamento é muito simples: a partir do endereço de um membro qualquer de uma struct, calcula o endereço base dessa instância da struct, usando simples aritmética de ponteiros.
    Por exemplo, suponha a struct

    struct abc {
    	int a;
    	int b;
    	int c;
    };
    

    Graficamente

    struct abc

    struct abc

    Se tivéssemos que calcular o endereço base da struct a partir do membro c, desconsiderando o problema do alinhamento –um dia falo sobre isso– , bastaria
    BASE = END(c) - (sizeof(abc::a) + sizeof(abc::b))
    ou, bem melhor
    BASE = END(c) - OFFSET(c)

    Como é calculado o offset de um membro de uma struct

    Como é calculado o offset de um membro de uma struct


    OFFSET(c) pode ser entendido como “quantos bytes até o membro c”, em C fica
    &((struct abc *)0)->c
    Logo, o endereço base da instância da struct que contém o membro c apontado por PTR, seria
    ((char *)PTR) - &((struct abc *)0)->c

    E é exatamente isso que a macro container_of faz.

    Calculando o endereço base da struct a partir do membro c

    Calculando o endereço base da struct a partir do membro c

    Ah sim, quase esqueci do typeof. O typeof é um extensão do GCC que retorna o tipo do argumento, sendo que o argumento pode ser uma expressão ou um tipo, por exemplo:

    char *cptr;
    typeof(cptr) tptr;
    

    Declara tprt como sendo do tipo char *.

    Ou essa, mais esquisita:

    typeof (typeof (char *)[4]) y;
    

    Equivalente a

    char *y[4];
    

    Usar typeof ajuda a escrever macros sem que se conheça previamente os tipos dos parâmetros que serão passados para a macro, como é o caso da macro container_of. Uma última observação: typeof NÃO é (nem se comporta como) uma macro, compare com sizeof.

    Voltando às macros de manipulação das listas, com a ajuda do container_of e adicionando um membro do tipo struct list_head numa struct qualquer, podemos manipular a lista sem nos importar com o nome ou a ordem em que o membro aparece na struct. As macros principais:

    /**
     * list_entry - obtém o ponteiro para a struct a partir de um list_head.
     * @ptr:        ponteiro para &struct list_head.
     * @type:       o tipo de struct a que pertence o list_head.
     * @member:     o nome do membro struct list_head dentro da struct.
     */
    #define list_entry(ptr, type, member) container_of(ptr, type, member)
    
    /**
     * list_first_entry - obtém um ponteiro para a primeira struct, dado um head.
     * @ptr:        O list_head.
     * @type:       o tipo da struct.
     * @member:     o nome do membro list_struct dentro da struct.
     *
     * Note que a lista não pode estar vazia.
     */
    #define list_first_entry(ptr, type, member) \
            list_entry((ptr)->next, type, member)
    
    /**
     * list_for_each - percorre a lista, item a item, não é permitido remover o cursor.
     * @pos:        Um struct list_head para ser usado como cursor.
     * @head:       O list_head.
     */
    #define list_for_each(pos, head) \
            for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                    pos = pos->next)
    
    /**
     * list_for_each_safe - percorre a lista, item a item, permite list_del no cursor.
     * @pos:        Um struct list_head para ser usado como cursor.
     * @n:          Outro struct list_head, usado como temporário.
     * @head:       O list_head.
     */
    #define list_for_each_safe(pos, n, head) \
            for (pos = (head)->next, n = pos->next; pos != (head); \
                    pos = n, n = pos->next)
    /**
     * list_for_each_entry - percorre uma lista de structs de um certo tipo.
     * @pos:        Um ponteiro para a struct que contém o list_head, para ser usado como cursor.
     * @head:       O list_head.
     * @member:     o nome do membro list_struct dentro da struct.
     */
    #define list_for_each_entry(pos, head, member)                          \
            for (pos = list_entry((head)->next, typeof(*pos), member);      \
                 prefetch(pos->member.next), &pos->member != (head);        \
                 pos = list_entry(pos->member.next, typeof(*pos), member))
    

    Fiz um driverzinho comentado com exemplos, usando o driver beabá como base, os fontes estão aqui. Descompacte num diretório de sua preferência, entre lá e digite
    $ make

    Depois, para carregar, basta
    $ sudo insmod tstlist.ko

    Para descarregar,
    $ sudo rmmod tstlist

    Aí é só
    $ dmesg

    para ver as mensagens geradas pelo driver.

    ps: GCC tem a extensão __builtin_offsetof.

    Audio Streaming – Criando ondas

    No texto sobre ALSA construí uma aplicação besta que obtém uma stream da entrada de captura da placa de som e a reproduz sem modificações, mas com um pequeno atraso. Naquele exemplo não entrei no mérito de como o buffer PCM é “por dentro”.

    Depois de ver aquela aplicação, aposto que você, como eu, exclamou: “mal posso esperar para fazer um sintetizador!”. Calma, para isso é preciso entender como montar um buffer PCM que será direcionado ao dispositivo de saída. Felizmente isso é muito simples: basta variar o nível da amostra de acordo com a frequência desejada, sem esquecer de levar em conta a frequência de varredura.

    Como sempre, é mais fácil fazer do que falar, mas vou tentar explicar. Vamos supor que definimos a frequência de varredura em 10 KHz e precisamos gerar uma frequência de saída de 1 KHz (para facilitar, vamos usar somente dois níveis na saída). O cálculo de como preencher o buffer é simples: pega-se a frequência de varredura e divide-se pela frequência desejada. O valor será o período (ou ciclo), em amostras. Por exemplo, para gerar 1 Khz numa taxa de amostragem de 10 Khz:

    10000/1000=10 amostras, portanto, 10 amostras é a duração do ciclo.

    Como estamos falando de uma onda quadrada (só tem dois níveis), seguramos o nível de tensão alto por meio período e depois baixamos o nível :D até completar o ciclo, ou seja, 5 amostras com nível alto e 5 com nível baixo. Aí é só repetir pelo tempo desejado. Por exemplo, para gerar um buffer de 1 segundo na varredura de 10 KHz, precisaremos de 10000 amostras.

    Sintetizando com PCM

    Fiz um programa exemplo a partir do programa de captura mencionado acima, modificando-o para gerar uma onda quadrada na saída de som, usando até 2 frequências, alternadamente. Essas são as saídas vistas num osciloscópio, para 100 Hz, 1000 Hz e 10000 Hz na frequência de varredura de 44.1 KHz. A ponta de prova foi ligada diretamente à saída de som.

    ./pcmbeep -F 100 -f 100
    100hz

    ./pcmbeep -F 1000 -f 1000
    1k

    ./pcmbeep -F 10000 -f 10000
    10k

    Os fontes do programa estão aqui. Para compilar, o de sempre: entre no diretório onde descompactou os arquivos e digite
    $ make

    Ao executar esse maravilhoso, fantástico, espetacular e sensacional programa sem parâmetros, você transforma o seu caríssimo computador em uma excelente campainha de telefone. Isso é que eu chamo de um programa dos bons.

    Solte a imaginação e divirta-se, modificando o programa para gerar outros tipos de ondas, por exemplo, senoidal, mais parecida com som natural, harmônicas, que dão enjôo, sintetizadores de voz, gerador de tons DTMF, etc, etc, etc…

    ATENÇÃO: Cuidado, o som pode ser muito alto. Nunca execute o programa com fones de ouvido.

    Audio Streaming – ALSA

    Por causa desse projeto de Audio Streaming, decidi entender melhor como funciona a manipulação de som no Linux usando ALSA. Parece complexo, mas não é.

    Todo mundo está surdo de tanto ouvir que som nada mais é que a uma vibração mecânica transmitida em um determinado meio material, que se propaga em forma de ondas e que em sua maior parte acaba percebida por órgãos especializados, os ouvidos (se eu errei na definição, me avisem). Existem dispositivos que convertem vibrações sonoras em sinais elétricos e os que fazem o contrário (exemplo: microfones/alto-falantes). A conversão de som em sinais elétricos é apenas uma parte do processo, precisamos de um modo de analisar e armazenar sons (ou melhor, sua representação) digitalmente, em uma stream de bytes. Ainda bem que existem os conversores A/D e D/A (analógico para digital e vice-versa) integrados ao hardware, que convertem o sinal elétrico analógico do dispositivo de captura (por exemplo, um microfone) em digital, só temos que ler um valor já devidamente convertido para digital.

    Para simplificar, das duas características de uma onda sonora, vamos eliminar a amplitude (intensidade) e assumir que só existe a frequência, ou seja, som ou silêncio. Para capturar, basta ler a entrada onde supomos que o sinal elétrico está presente, numa taxa constante. Se lermos a entrada mais rapidamente do que a frequência do som capturado, vamos obter uma representação aproximada do sinal original. Para fazer o inverso, ou seja, “tocar” nossa gravação, é só ler o valor armazenado na captura do sinal e enviar para o dispositivo de reprodução, tomando o cuidade de enviar para a saída usando a mesma velocidade da captura. Não falei que era simples? O valor lido da entrada é uma amostra (ou sample), e a frequência com que lemos essa amostra é a taxa de amostragem (ou sampling rate).

    Não é preciso ser nenhum gênio da matemática para deduzir que quanto maior a taxa de amostragem, melhor a representação do sinal. A partir de uma certa taxa, no entanto, deixa de fazer diferença, já que o ouvido humano normal não é capaz de perceber frequências superiores a 20 KHz. Mesmo assim, ainda resta a pergunta: qual a taxa ideal? O teorema da amostragem de Nyquist-Shannon mostra que, se a taxa de amostragem for o dobro da maior frequência a ser codificada, todas as informações do sinal serão capturadas, permitindo uma reconstrução perfeita. Qualquer placa de som doméstica chinfrim consegue capturar com taxa de amostragem de 48 Khz por canal, algumas muito além disso. Por diversos motivos, incluindo distorções causadas pelos próprios dispositivos de captura/reprodução e pelos conversores A/D e D/A, o sinal de saída não vai ser idêntico ao sinal capturado, mas vai ser parecido o suficiente para “enganar” o ouvido, mesmo com baixas taxas de amostragem. O ALSA cuida da interface com o hardware e lida com as coisas mais extravagantes, como taxa de amostragem, canais, etc, deixando pouca coisa para a gente se divertir.

    Agora que sabemos TUDO sobre som, vamos ver como é que fazemos na prática para capturar e reproduzir (sons). Acho que o mais óbvio é fazer um programa que captura da entrada padrão, aguarda um tempo e coloca o que foi capturado na saída. Isso também vai ajudar a comparar o efeito do uso de diferentes sampling rates.

    Vou usar o programa esqueleto de user mode, ou seja, é só um diretório com os arquivos Makefile e main.c lá dentro. Para compilar é só entrar no diretório e digitar
    $ make
    De dentro do diretório, execute o programa com
    $ ./pcmreplay
    Tente variar os parâmetros do programa para ver as diferenças. Como eu não tenho um gerador de funções, não vou poder analisar e fotografar a influência da taxa de amostragem na captura dos sinais vista no osciloscópio, mas os links abaixo tem alguns gráficos explicativos.

    Os fontes do programa estão razoavelmente comentados em português, divirta-se.

    Por último, para quem se interessa, a wikipedia tem um artigo sobre o teorema da amostragem e um link para um paper interessante (com figuras, que inveja!).

    Projeto Esqueleto – Kernel mode

    Depois de encontrar um esqueleto de usuário, agora é a vez de um esqueleto de kernel. Esse é um driver simples que pode ser usado como base para experimentação. Ele é tão simples, tão simples, mas tão simples, que não faz absolutamente nada além de ocupar espaço.

    O kernel Linux possui um conjunto de ferramentas para auxiliar a compilação, o kbuild, por isso é necessário ter um Makefile compativel, ou seja, o projeto mínimo tem que conter pelo menos o Makefile, que deve definir obrigatoriamente algumas variáveis.

    Esta é a Lista de Material, supondo que você vai compilar o driver para ser carregado na própria máquina:

  • Compilador C, linker, make, etc compatível com o seu kernel (provavelmente o GCC toolchain já deve estar instalado em sua máquina de testes).
  • Kernel headers e kbuild tools – normalmente existe um pacote kernel-devel que instala os arquivos necessários. É claro que você também poderia instalar o kernel completo, mas essa é outra história.
  • Arquivos do projeto (veja abaixo).
  • Arquivos:
    Makefile:

    # este e o nome do modulo
    MOD_NAME:=beaba
    # supondo que voce vai compilar sempre para o kernel em uso
    KERNELDIR?=/lib/modules/$(shell uname -r)/build
    # daqui para baixo dificilmente sera necessario mexer
    PWD:=$(shell pwd)
    INSMOD:=/sbin/insmod
    RMMOD:=/sbin/rmmod
    
    $(MOD_NAME)-objs:=main.o
    obj-m:=$(MOD_NAME).o
    EXTRA_CFLAGS+=-DMOD_NAME="$(MOD_NAME)"
    
    all: $(MOD_NAME)
    
    $(MOD_NAME):
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    
    clean:
    	rm -fr $(MOD_NAME).ko *.o *.mod.c *.symvers .tmp_versions .*.{o,ko}.cmd 
    
    load:
    	$(INSMOD) $(PWD)/$(MOD_NAME).ko
    
    unload:
    	$(RMMOD) $(MOD_NAME)
    

    main.c:

    /* no minimo esse include e necessario */
    #include <linux/module.h>
    
    /* callback chamado quando o modulo estiver sendo carregado.
     * Somente completa o carregamento se retornar 0.
     */
    static int __init beaba_init(
        void
    )
    {
    	printk("%s:%s Hello World, from Kerneland\n", MOD_NAME, __FUNCTION__);
    	return 0;
    }
    
    /* funcao chamada antes do modulo descarregar */
    static void __exit beaba_exit(
        void
    )
    {
        printk("%s:%s\n", MOD_NAME, __FUNCTION__);
    }
    
    module_init(beaba_init);
    module_exit(beaba_exit);
    
    /* define o tipo de licenca para este modulo */
    MODULE_LICENSE("GPL");
    

    Instruções de uso
    Crie um diretório para o projeto, por exemplo, beaba:
    $ mkdir beaba

    Crie/copie os arquivos do projeto, main.c e Makefile. Daí é só entrar no diretório e digitar “make”:
    $ cd beaba
    $ make

    Se não ocorrerem erros, o resultado principal do make vai ser o módulo “beaba.ko”. Para o seu módulo fazer parte do kernel, use o utilitário “insmod” (necessário permissão de root):
    $ sudo insmod beaba.ko

    Verifique se o módulo aparece na lista de módulos, usando o utilitário “lsmod” e “grep”:
    $ lsmod | grep beaba

    Para remover o módulo do kernel, use “rmmod”:
    $ sudo rmmod beaba

    Se quiser ver as mensagens emitidas pelo módulo, use “dmesg”:
    $ dmesg

    ps: o código acima serve só para referência, porque na formatação pode ser que alguma coisa desapareça. Por favor, use os fontes originais.
    Divirta-se!

    Audio Streaming

    Essa história começou quando eu me interessei por rádio escuta e comprei um scanner Uniden BCT-8, algumas semanas atrás. Depois de brincar um tempo com ele, cheguei à conclusão de que o aparelho é bom, mas não é muito prático: é grande, precisa de uma antena de meio metro (neste caso, tamanho é documento, e está inversamente relacionado à frequência das ondas, como aprendemos na escola) e não é lá muito fácil de usar, já que ele não tem alpha-tags (um nome associado à uma frequência), fazendo com que você tenha que se lembrar que a frequência 462,562 MHz é a frequência do canal 1 dos Talkabout.

    E se fosse possível acessar o scanner via Internet? Bastaria usar um cabo de som simples para conectar a saída de som do scanner à placa de som de um PC, e eu poderia usar o Internet Radio do meu jurássico N82 para ouvir o scanner, onde quer que eu estivesse.

    Passei a procurar um jeito de fazer isso, e lembrei do Shoutcast, feito pela Nullsoft, comprada pela AOL. O Shoutcast é popular para streaming de audio, está testado e aprovado, rodando há tempos. Meus problemas se acabaram-se! Fiz o download e instalei. Apesar de ter sido feito originalmente para “tocar” streams usando uma playlist de arquivos mp3, ele é capaz de usar a captura da placa de aúdio como fonte. Até que foi relativamente fácil de configurar e por no ar no meu poderoso servidor Linux. Mas…

    O scanner, como o nome já diz, fica varrendo uma faixa de frequências ou uma lista de frequências até encontrar uma transmissão. Detectada a transmissão, ele trava naquela frequência até que a transmissão seja interrompida. O BCT-8 tem um display que mostra quando está ele está procurando alguma transmissão e, quando encontra, mostra a frequência detectada no visor do painel. Óbvio. Muito legal, mas se você estiver ouvindo remotamente… Vai ouvir o som da transmissão, só não vai saber qual a frequência a origem.

    Lendo o manual (sim, eu confesso que leio os manuais antes de usar), vi que o scanner pode ser controlado de duas formas: pelo painel frontal ou via porta serial. O protocolo de comunicação é uma simples sequência de comando/resposta em ASCII, finalizados por um carriage return (quase sempre equivalente ao ENTER). Pela porta serial, além de aceitar comandos para programação da lista de frequências para varredura, ele também pode ser configurado notificar mudanças no estado do Squelch (ou seja: início/fim de uma transmissão).

    Quando estamos ouvindo um streaming, geralmente o player mostra o título da música que está tocando naquele momento. A solução óbvia: unir o controle remoto via porta serial do BCT-8 com o shoutcast e usar o título da música para informar a frequência. Juntar um com o outro seria facílimo, se… se eu tivesse o código fonte do shoutcast! Ops, só aí me lembrei de que o shoutcast é freeware, mas não é open source.

    Bom, de volta ao Google às pesquisas, achei o Icecast. O Icecast também é grátis, tem as mesmas funcionalidades do Shoutcast, só que é open source. Agora sim, meus problemas se acabaram-se! Fiz o download, configurei, testei… funcionou. Mas…

    O Icecast é dividido em 2 partes: o icecast, um servidor que recebe as solicitações de streaming dos clientes e é capaz de gerenciar vários streams simultâneos, além de fazer um montão de coisas muito legais mas que não me interessam, e um outro programa, o ices, que envia um stream para o servidor. Assim, é possível ter um servidor em uma máquina e vários geradores de streams em outra(s) máquina(s). Existem 2 versões “oficiais” do ices: a 0.4 e a 2. A mais nova e com numeração maior deve ser a melhor, certo? Bem… não exatamente. A versão 0.4 é capaz de ler e enviar streams em diferentes formatos: AAC, MP3, OGG etc. A versão 2 só funciona com OGG, suponho que devido à bagunça das patentes relacionadas ao MP3. Uma complicação extra é que pretendo usar o icecast de um modo um pouco diferente para o qual ele foi projetado, a partir de uma fonte contínua (a captura da placa de som), mas trocando o título da “música”.

    Infelizmente o N82 não consegue reproduzir o formato ogg/vorbis, e colocar o controle remoto do scanner no ices 0.4 mostrou-se um pesadelo. Já que eu posso escolher, escolho o ices 2 com suporte à MP3, que obviamente vou ter, eu mesmo, que adicionar. Quanto ao licenciamento do MP3, apesar da confusão das patentes, o uso é grátis sob certas condições, especialmente o uso sem fins lucrativos. Eu também tentei algumas alternativas para o ices, como o darkice, mas nenhuma conseguiu atender as necessidades, ou seja: capturar da placa de aúdio, codificar em mp3 e permitir integração com o scanner para alterar o nome da “música” programaticamente.

    No próximos posts vou detalhar as pesquisas e as modificações que eu fiz no ices.