De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.
Colaboração: Evgueni Dodonov
Data de Publicação: 27 de Abril de 2006
Esta dica tem como objetivo descrever alguns dos métodos para análise de desempenho e funcionamento de aplicativos para Linux/BSD/Unix em geral.
Existem inúmeras técnicas para fazer avaliação automática de desempenho de aplicativos, vou descrever as três mais desconhecidas porém as mais poderosas de todas.
A primeira técnica consiste em interceptação de chamadas de funções através de modificação automática do código fonte. É possível fazer esta modificação de várias maneiras, vou descrever a mais símples -- utilizar o SED.
O seguinte script vai interceptar todas as funções de leitura de dados, escrevendo uma mensagem:
#!/bin/sh # # DEBUG.SH script # cat > debug.h << EOF #ifndef _MPIDEBUG_H #define _MPIDEBUG_H #include <stdio.h> #define _read(fd, buf, size) {\ printf("Estou lendo %d bytes do arquivo %d na posição %p\n", size, fd, buf);\ read(fd, buf, size);\ } #endif EOF cat << EOF /* Generated with EugeniDebug script */ #include "debug.h" EOF cat $z | sed e 's/\(read\)/_&/g'
Salvando isso como um arquivo e executando com
# sh debug.sh arquivo.c > novo_arquivo.c
no arquivo novo_arquivo.c teremos o código instrumentado automaticamente. Este código, após compilado, executará o macro "_read" para toda função "read" do código original. Com isto, dá para fazer inúmeras coisas, só ilustrei a mais símples de todas.
A segunda técnica não precisa nem de modificação do código fonte. Ela funciona com aplicativos compilados dinamicamente e intercepta as chamadas de funções usando as funções "dlopen()" e "dlsym()".
Vamos fazer o seguinte arquivo:
/* libdebug.c */ #include <dlfcn.h> #include <stdio.h> static int (*_read)(int fd, void *buf, size_t size); void _init(void) { void *libc_handler = RTLD_NEXT; _read = dlsym(ibc_handler, "read"); } int read(int fd, void *buf, size_t size) { printf("Estou lendo %d bytes do arquivo %d na posição %p\n", size, fd, buf); return _read(fd, buf, size); }
Compilando este código com seguinte comando:
# gcc -D_GNU_SOURCE -DPIC -fPIC -D_REENTRANT libdebug.c # ld -shared -o libdebug.o -ldl -lc
teremos uma biblioteca libdebug.so.
Agora, colocando esta biblioteca na variável de ambiente LD_PRELOAD, todas as funções "read" dos aplicativos serão interceptadas:
# LD_PRELOAD=`pwd`/libdebug.so ./a.out
A 1a técnica necessita de modificação do código fonte. A 2a necessita de aplicativos compilados dinamicamente. Uma 3a técnica possibilita interceptar qualquer função do sistema, mesmo para aplicativos compilados estaticamente. Para isto, é utilizada a função ptrace().
A função permite utilizar breakpoints no contexto do kernel, interceptando qualquer chamada do sistema de um aplicativo. Esta técnica é mais complicada e necessita de alguns conhecimentos de arquitetura do Linux e dos registros do processador (EAX, EBX, etc).
O seguinte código interceptará a função "open" utilizando a função ptrace():
/* debug.c */ #include <stdio.h> #include <sys/ptrace.h> /* Função para recuperar texto dos registros */ /* OBS: Eu sei que é complicado de entender mesmo :) */ void getdata(pid_t child, long addr, char *str, int len) { char *laddr; int i, j; union u { long val; char chars[sizeof(long)]; }data; i = 0; j = len / sizeof(long); laddr = str; while(i < j) { data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); memcpy(laddr, data.chars, sizeof(long)); ++i; laddr += sizeof(long); } j = len % sizeof(long); if(j != 0) { data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); memcpy(laddr, data.chars, j); } str[len] = '\0'; } /* Processa um syscall interceptado */ void process_syscall (long syscall, int pid) { struct user_regs_struct regs; switch(syscall) { case SYS_open: { /* Interceptamos o open() */ char str[256]; ptrace(PTRACE_GETREGS, pid, NULL, ®s); getdata(pid, regs.ebx, str, 256); printf("Tentativa de abrir %s!\n", str); break; } default: break; } } int main(int argc, char **argv) { int pid; if (argc < 2) { fprintf(stderr, "Uso: %s <comando>\n", argv[0]); exit(1); } /* Vamos criar um filho para executar o aplicativo proriamente dito */ pid = fork(); if (pid == 0) { /* Program */ char *command; char **params; int i; command = (char *)malloc(strlen(argv[1]) * sizeof(char)); params = (char **)malloc((argc) * sizeof(char*) + 1); strncpy(command, argv[1], strlen(argv[1])); for (i=1; i < argc; i++) { params[i-1] = (char *)malloc(strlen(argv[i])); strncpy(params[i-1], argv[i], strlen(argv[i])); } /* Vamos dizer ao kernel que o aplicativo será monitorado */ ptrace(PTRACE_TRACEME, 0, NULL, NULL); execve(command, params, env); perror("execve"); exit(1); } if (pid > 0) { /* Enquanto o aplicativo está em execução */ while(wait(NULL) >= 0) { long orig_eax; /* Interceptar o contexto do programa */ orig_eax = ptrace(PTRACE_PEEKUSER, pid, 4 * ORIG_EAX, NULL); /* Processar a syscall */ process_syscall(orig_eax, pid); /* Voltar o fluxo de execução ao programa rodando */ ptrace(PTRACE_SYSCALL, pid, NULL, NULL); } } return 0; }
Depois disto, é só compilar:
# gcc -o debug debug.c
e rodar alguma coisa
# ./debug /bin/cat /etc/passwd
Obviamente, somente as chamadas do sistema conhecidos pelo kernel podem ser interceptadas.
Bem, é isso :).
This policy contains information about your privacy. By posting, you are declaring that you understand this policy:
This policy is subject to change at any time and without notice.
These terms and conditions contain rules about posting comments. By submitting a comment, you are declaring that you agree with these rules:
Failure to comply with these rules may result in being banned from submitting further comments.
These terms and conditions are subject to change at any time and without notice.
Comentários