.:: Jorge Pereira ::.

"UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity."

– Depuração: Parte 1

3 comments

Seja Elegante

Talvez possa ser algo que passe despercebido por vários desenvolvedores, porém dúvido quem nunca tenha se deparado com mensagens de “log” vagas ou sem nenhum nexo ou bem pior, como mensagens idênticas replicadas por várias partes do código! :shock:
A algum tempo atrás estava trabalhando em um projeto em que um respectivo desenvolvedor da equipe possuia o costume de espalhar ou replicar centenas de printf() pelo código com mensagens tipo!

printf(“!!! FULANO – Aqui!!!”);

ou

printf(“!!! FULANO – Arquivo.cpp:  Aqui!!!\n”);

Precisa falar que isto e pessímo? Certo que e preciso um bom senso com o uso de mensagens de depuração pois dependendo ao invés de ajudar podem só atrapalhar. principalmente quando você necessita ter controle sobre tais mensagens, como por exemplo obter a localização da mensagem através do nome da função, arquivo e linha.

Lembrando que dependendo da situação e contexto eu sou a favor de utilizar o pragma no compilador ativando o “poison” desativando dentre várias funções o printf (), evitando de ter espalhada pelo sistema! Futuramente um post sobre o poison.

O pré-processador do GCC oferece (*) várias macros, as que irei utilizar em questão são:

  • __FILE__ : Substituída pelo nome do arquivo.
  • __PRETTY_FUNCTION__ : Substituídas pelo nome nome da função.
  • __LINE__ : Substituída pelo número da linha de código.

(*) Mais detalhes sobre as macros do GCC.

A utilização dessas macros irá facilitar a localização das mensagens de depuração, Abaixo um simples exemplo demonstrado em debug1.c.

/**
 * By Jorge Pereira <jpereiran@gmail.com>
 * Date: Tue Jun 29 02:51:44 BRT 2010
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

// Macro condicional
#ifdef DEBUG
#	warning "Debug is enabled"
#	define TRACE_DEBUG(fmt, ...) \
	fprintf(stderr, " ** DEBUG: %s:%d %s(): "fmt"\n", \
	__FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
#else
#	warning "Debug is disabled"
#	define TRACE_DEBUG(fmt, ...)
#endif

#define MAX_STRING 5 /* tamanho maximo de uma string */

// Função simples de exemplo...
void
show_name (const char* name,
		   size_t len)
{
	size_t offset = len;

	// Verificação qualquer...
	if (len > MAX_STRING)
	{
		// Abaixo mensagem de depuração, perceba que ela será ativada apenas
                // quando em tempo de compilação você ativar a macro "DEBUG"
		TRACE_DEBUG ("Ooops! Tamanho da string(%d) e excedeu o limite(%d)...",
			len, MAX_STRING);
		offset = MAX_STRING;
	}

	// Manipulação qualquer...
	write (STDOUT_FILENO, name, offset);
	write (STDOUT_FILENO, "\n", 1);
}

int
main (int argc, char* argv[])
{
	if (argc < 2)
	{
		printf ("Usage: %s <string>\n", argv[0]);
		exit (1);
	}

	show_name (argv[1], strlen (argv[1]));

	return 0;
}

Com este exemplo básico em mãos, vamos compilar e executar.

[jpereira@miracleworld Codes]$ gcc -Wall -o debug1 debug1.c
debug1.c:18:3: warning: #warning "Debug is disabled"
[jpereira@miracleworld Codes]$ ./debug1 "Jorge"
Jorge
[jpereira@miracleworld Codes]$ ./debug1 "Jorge Pereira"
Jorge
[jpereira@miracleworld Codes]$

Imagine que seu sistema possui inúmeros arquivos e você precisa em determinados pontos obter o máximo de informação sobre as mensagens de depuração, talvez seja interessante ter além de uma mensagem objetiva obter juntamente o nome da função, arquivo e linha de onde foi invocada tal mensagem.

[jpereira@miracleworld Codes]$ gcc -Wall -DDEBUG -o debug1 debug1.c
debug1.c:13:3: warning: #warning "Debug is enabled"
[jpereira@miracleworld Codes]$ ./debug1 "Jorge"
Jorge
[jpereira@miracleworld Codes]$ ./debug1 "Jorge Pereira"
 ** DEBUG: debug1.c:36 show_name(): Ooops! Tamanho da string(13) e excedeu o limite(5)...
Jorge
[jpereira@miracleworld Codes]$

Agora veja situação que você está trabalhando em um sistema com várias checagens complexas de cenários “nada convencionais” em que você não tem certeza se vai acontecer sempre ou não.
Sem falar que torna-se um atrativo a possibilidade de você poder ativar/desativar o simples mecanismo, como por exemplo poder criar macros para depuração de componentes específicos (ex.: relacionadas a sockets, I/O, sgdb, …), isto poderá facilitar a identificação e remoção de bugs.

Lembrando que este e o primeiro post entre vários que pretendo publicar relacionados a sugestões e técnicas de depuração. Fique de olho na segunda parte desta saga em que irei explicar uma mais dinâmica do mesmo assunto abordado neste post associando ao uso de variáveis de ambiente.
Dúvidas e Sugestões, são sempre bem vindas! ;)

Autor: Jorge Pereira

Sempre tive preferência pelos produtos produzidos pela Sony, em especial os notebooks. O meu antigo VGN-FS750 começou a apresentar alguns problemas após eu ter dado um tombo nele, desde então nunca mais foi o mesmo. :roll:

Porém no mês passado resolvi comprar um novo notebook que fosse preferencialmente da Sony, e após várias buscas cheguei ao modelo VPC-EB15FB. Então fui ao shopping e comprei diretamente na loja da SonyStyle o brinquedinho abaixo.

Assim que cheguei em casa fiz o download do Ubuntu 9.10 e instalei, após o primeiro boot que iniciei a configuração e recuperação dos meus backups do notebook antigo percebi que algumas teclas de atalhos não funcionaram, estranho né? Abaixo imagem das teclas na qual estou me referindo.

Obviamente que não fiquei de braços cruzados, fiz checkout diretamente pelo GIT do Kernel o modulo “platform-drivers-x86” e após alguns ajustes já estava adicionado o suporte das novas teclas da série EB de notebooks da Sony ao Kernel do Linux.

Resumindo, suporte adicionado seguido de patch gerado e submetido ao Kernel. Agora e aguardar a próxima versão do Kernel para termos por padrão o suporte a tais teclas! ;)

Patch gerado a partir do Kernel ??2.6.35-rc3

Olá,

Caso você tenha necessidade ou curiosidade em relação a Device Drivers no Linux, este com certeza será um bom material para iniciar-se na “brincadeira”.

Introdução aos Linux Device Drivers (ILDD) é um curso que tem por objectivo apresentar os princípios básicos do desenvolvimento de device drivers no Linux kernel. Pretende-se com este curso, que o leitor tenha contacto com várias ferramentas e sub-sistemas existentes no kernel, adquirindo assim as bases que lhe irão permitir desenvolver o suporte para a grande maioria dos dispositivos. Alguns dos temas introduzidos são: estrutura básica de um device driver, comunicação com o userspace, memória dinâmica, eventos assíncronos, primitivas de sincronização e comunicação com o hardware.

Este curso aborda o tema de uma perspectiva pedagógica, através da sistematização dos conceitos em conjunto com uma forte componente prática, na qual se convida o leitor à implementação gradual de um device driver que dará suporte a um dispositivo especificamente concebido para o efeito. O leitor poderá ainda consultar a literatura de referência, de onde se destacam os excelentes títulos: Linux Device Drivers, Understanding the Linux Kernel e Linux Kernel in a Nutshell.

O autor, com a ajuda dos seus revisores, investiu mais de um ano de trabalho na elaboração e preparação deste curso, no sentido de oferecer à comunidade, não só um manual de aprendizagem abrangente, mas também uma importante referência futura. Caso encontre alguns erros ou tenha sugestões que visam melhorar ou complementar este trabalho, não hesite em contactar o autor.

Download aqui

Para descontrair, abaixo segue algumas imagens que registrei durante o passar do tempo.

(*) Go Horse Process

Recentemente li The Python Paradox, de Paul Graham, e sua tradução O Paradoxo Python no #!SouNerd.

O texto faz todo sentido: quem aprende a fazer qualquer coisa por gosto tende a fazer melhor do que quem aprende a fazer por dinheiro. É uma verdade.

Assim, quem aprende a programar porque gosta de programar tende a programar melhor do que quem aprende para ganhar dinheiro e a grande maioria de quem aprende a programar Java só o faz por dinheiro (claro que há lá suas exceções), enquanto quem aprende linguagens mais esóticas o faz por prazer.

Então por que as empresas teimam em contratar programadores mercenários?

Pensando sobre nisso e observando a comunidade, pensei em uma resposta…

Eu vejo três tipos de programadores, como três vértices de um triângulo cromático, e cada um está em uma região dessa área, mais próximo ou distante de cada vértice.

Esses três tipos são: amador, mercenário e empolgado (por falta de palavra melhor).

Mercenário

O mercenário é aquele que quer dinheiro e lucro é tudo o que o impulsiona. Geralmente não tem escrúpulos e faz tudo o que lhe mandam. Seu mantra é: «Pagando bem, que mal tem?»

Quem é 100% mercenário geralmente acaba largando a profissão por algo que dê mais dinheiro, mas basta estar um pentelésimo mais para o centro do triângulo que permanece na profissão.

Normalmente aprende uma linguagem que dê muitas opções de emprego – e que, por consequência, tenha de competir com muita gente –, como Java, C# ou PHP, e rejeita todas as demais. Quando as trata como inferiores, é apenas para tentar justificar a própria incompetência, resultante da falta de gosto pelo que faz.

Amador

O amador é aquele que gosta de verdade do que está fazendo, ele ama programar, daí amador.

Quem é 100% amador quase sempre se perde aprendendo coisas inúteis e não consegue ganhar dinheiro porque muitas vezes perde o foco do que precisa fazer, distraindo-se com besteiras.

Normalmente aprende linguagens bem diferentes, que poucos sabem, como Lisp, Smalltalk ou Fortran – talvez LOLCODE –, e não é incomum que aprenda uma penca de linguagens, nesse caso, também os hypes, como Python e Ruby.

Empolgado

O empolgado é aquele que vai atrás de algum hype, linguagem da moda. Há uns anos eram de Java, mas os empolgados da atualidade querem Python e/ou Ruby.

Quem é 100% empolgado sabe tudo que acontece na comunidade, mas quase nada de programação. Conhece todas as metodologias em alta, sabe todos os macetes e design patterns de sua linguagem que precisa para impressionar os iniciantes, mas não é lá muito eficiente. É tão preocupado com a auto-imagem e o que acontece na comunidade que se esquece que há todo um background a ser aprendido por trás da Computação.

**

Felizmente (quase) ninguém é 100% alguma coisa.

Depois de toda esta dissertação, por que diabos as empresas mais engessadas preferem os mercenários?

Por causa de seu mantra! Basta pagar o que o mercenário acha muito – o que muitas vezes é quase nada pra empresa – e ele faz qualquer coisa que mandarem, torna-se um cão bem adestrado. Essas empresas não querem eficiência, querem obediência.

O que as empresas não veem é que esses cães não são tão ágeis quanto os gatos que pulam pelos telhados: aqueles que, apesar de em nenhum extremo, se encontram mais próximos do vértice do amador.

Na verdade as metodologias ágeis não são práticas de trabalho, mas técnias sociais para atrair programadores que, apesar de ainda um pouco mercenários, sejam muito mais amadores – ser um pouco empolgado também ajuda, já que as linguagens hype facilitam pela grande mobilidade da comunidade. As empresas mais espertas e as start-up viram isso e usam as metodologias ágeis para criar equipes enxutas e mais eficientes do que qualquer equipe de mercenários jamais conseguirá ser.

Fonte

Muitas pessoas utilizam, utilizaram ou vão utilizar a variável LD_PRELOAD, e por sua vez nem sempre sabem para que ela serve! Caso este seja o seu problema, problema este não mais será!! :P

O que acontece basicamente e que o linker dinâmico do Linux (assim como em tantos outros sistemas operacionais) utiliza diversas formas, alguma delas sendo através de variáveis de ambiente para controlar seu comportamento. Sendo que neste caso a variável LD_PRELOAD informa ao linker dinâmico que carregue as bibliotecas listadas nela antes de carregar quaisquer outras bibliotecas necessárias, enquando LD_LIBRARY_PATH especifica um caminho alternativo para usar ao procurar bibliotecas que serão carregadas.

Partindo deste principio podemos fazer com que um programa a ser executado seja “hijacked” por outro programa, ou seja. Podemos fazer por exemplo que a função hehe() previamente chamada pelo programa “A” tenha seu comportamento alterado sem precisar fazer quaisquer alteração no programa “A”. Um pouco complexo? talvez! Mais vamos por a mão na massa! hands on!

Digamos que você tem o programa “main” conforme o código de exemplo abaixo, perceba que o código e super simples. apenas declaro um ponteiro de caracteres, aloco memória e em seguida copio uma sequência de caracteres para o ponteiro previamente alocado. Simples, certo?

1) Abaixo código de exemplo de nosso “main.c” ou clique aqui para download.

/*
 *  Filename: hijack_main.c
 *  Created: Wed Jun  9 22:11:12 BRT 2010
 *  Author: Jorge Pereira <jpereiran@gmail.com>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int 
main (int argc, 
      char* argv[])
{
  char* nome = (char*)malloc (100);

  strcpy (nome, "Jorge Pereira");
  printf ("NOME: %s\n", nome);

  free (nome);
  return 0;
}

# Vamos compilar e executar o nosso exemplo “main.c”

$ gcc -Wall -o main main.c
$ ./main
NOME: Jorge Pereira
$

Até aqui tudo bem, porém imagine você em uma determinada situação em que precisa saber quantos bytes está sendo alocado por um determinado programa? e você por alguns instantes imagina sobre a possibilidade de poder fazer algum tipo de “overload” de uma determinada função na qual você conhece sua assinatura. (Digamos, você sabe a assinatura do método, quantidade e tipos dos parâmetros, …).

Pois bem, neste exemplo que irei demonstrar será para sobrecarregar todas as chamadas feitas pelo meu programa “main” às funções malloc() e free() e em seguida exibir uma mensagem no caso do malloc() imprimindo seu parâmetro que e o tamanho de bytes alocados, e na função free() exibindo os ponteiros que foram liberados.

Neste caso, iremos criar uma biblioteca chamada “libhijack_hehe.so” que será carregada através da variável mágica LD_PRELOAD em parceria com nosso querido linker dinâmico.

2) Abaixo código de exemplo de “hijack_hehe.c“, ou clique aqui para download.

/*
 *  Filename: hijack_hehe.c
 *  Created: Wed Jun  9 22:11:12 BRT 2010
 *  Author: Jorge Pereira <jpereiran@gmail.com>
 */
#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include <dlfcn.h>

#define HIJACK_DEBUG(fmt, ...) \
	fprintf(stderr, " ** DEBUG: %s:%d %s(): "fmt"\n", \
	__FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)

static void* (*hack_malloc)(size_t size) = NULL;
static void  (*hack_free)(void *p) = NULL;

static void* rest_malloc = NULL;

void*
malloc (size_t size)
{
  if (hack_malloc == NULL) 
  {
    hack_malloc = (void *(*)(size_t)) dlsym (RTLD_NEXT, "malloc");
    rest_malloc = NULL;
  }

  if (rest_malloc == NULL) 
  {
    rest_malloc = hack_malloc (size);
    HIJACK_DEBUG ("Alocando (%d) bytes, chunck(%p)", size, (void*)rest_malloc);
    return rest_malloc;
  }

  hack_malloc = NULL;
  return rest_malloc; 
}

void
free (void *p)
{
	HIJACK_DEBUG ("Desalocando (%p)", p);

  if (hack_free == NULL) 
  {
    hack_free = (void (*)(void *)) dlsym(RTLD_NEXT, "free");
  }

  hack_free (p);
}

Agora vamos compilar, executar e analisar o comportamento.

$ gcc -Wall -shared -ldl -o libhijack_hehe.so hijack_hehe.c
$ LD_PRELOAD=./libhijack_hehe.so ./main
 ** DEBUG: hijack_hehe.c:35 malloc(): Alocando (100) bytes, chunck(0x9273008)
NOME: Jorge Pereira
 ** DEBUG: hijack_hehe.c:46 free(): Desalocando (0x9273008)
$

Percebeu algo diferente na execução com a LD_PRELOAD passando como parâmetro a nossa libhijack_hehe.so? pois bem, todas as chamadas às funções malloc() e free() foram sobrecarregadas e passaram a se comportar conforme as versões que escrevi em hijack_hehe.c. Caso tenha ficado curioso, e so re-escrever tais exemplos com outras funções que você deseja sobrecarregar e ver o comportamento. Lembrando que basta utilizar a criatividade e perceberá na quantidade de coisas que podem ser feita com tal técnica.

Exemplo: Nas funções que fazem checagem com strcmp(), uso da crypt(), … entre outras.

Referências

  • man 8 ld.so

Autor: Jorge Pereira
Data: Wed Jun 9 23:42:26 BRT 2010