Ir para conteúdo
  • Cadastre-se

Candeer

Apoiadores
  • Total de itens

    8
  • Registro em

  • Última visita

Reputação

3 Neutral

Sobre Candeer

Últimos Visitantes

O bloco dos últimos visitantes está desativado e não está sendo visualizado por outros usuários.

  1. Olá, neste artigo compartilharei um pouco da minha pesquisa no desenvolvimento de debuggers. No momento estou trabalhando em um protótipo de debugger para Linux, mas nada tão avançado quanto um gdb ou radare (muitas coisas são necessárias para chegar neste nível de maturidade de software). O desenvolvimento de debuggers é uma atividade muito interessante, já que, em sua forma mais básica, pode ser resumido em uma série de chamadas de sistema (syscalls) para que seja possível o controle do processo a ser depurado (muitas vezes chamado de debuggee) e de seus recursos, mas não vamos colocar a carroça na frente dos cavalos e vamos em partes. Antes de começarmos a discutir detalhes mais específicos acerca da depuração de processos, é necessário um entendimento básico de como os mesmos se comunicam na plataforma que vamos desenvolver o tal debugger, no nosso caso, UNIX-like. Inter-process communication (IPC) IPC é uma forma que processos podem utilizar para se comunicar dentro de um sistema operacional. Existem diversas maneiras de comunicação: via sinais (signals), sockets, etc, mas para a criação de um debugger é apenas necessário usar sinais para a execução. Sinais funcionam como uma notificação que pode ser enviada à um processo específico para avisar que algum evento ocorreu. É possível também programar um processo para reagir aos sinais de maneira não padrão. Se você já teve um uso razoável de Linux, você provavelmente já enviou sinais à um processo. Por exemplo, quando você aperta Ctrl+C para interromper a execução de um processo, é enviado um sinal do tipo SIGINT, que nada mais é que uma abreviação para Signal Interruption. Se o processo em questão não está preparado para reagir a este sinal, o mesmo é terminado. Por exemplo, considere o seguinte código: #include <stdio.h> int main(void) { while(1) printf("hi\n"); return 0; } Ao compilar e executar o código acima e apertar Ctrl+C, o mesmo encerra como esperado, porém podemos verificar que um SIGINT foi enviado usando a ferramenta ltrace, que além de listar chamadas a bibliotecas também mostra os sinais enviados ao processo: $ gcc -o hello hello.c $ ltrace ./hello Rode o comando acima e aperte Ctrl+C para verificar o sinal enviado! Programando reações a sinais A capacidade de enviar sinais a um processo nos dá a possibilidade de saber o que esta acontecendo com algum processo específico que estejamos depurando. Para programar reações a algum tipo de sinal, podemos incluir a biblioteca signal, para que possamos usar a função e estrutura (struct) sigaction: struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); A struct sigaction nos permite adicionar handlers (tratadores) para nossos sinais, enviando o endereço de nossa função que realiza algum tipo de ação baseada no sinal enviado para o campo sa_handler(sigaction handler). Um handler neste contexto nada mais é que uma função que sempre vai ser chamada quando um dado sinal for enviado, dessa maneira podemos executar alguma ação quando recebermos um sinal. Já a função sigaction recebe o número do sinal, porém uma série de macros já são pré-definidas e podemos passar como argumento apenas o nome do sinal, como SIGINT por exemplo. A função recebe também a referência da struct previamente definida (struct sigaction) e, caso precise trocar um handler por outro, também recebe no último argumento (oldact) o handler anterior, para que possa ser feita a troca pelo novo. Como não é o nosso caso, vamos passar NULL neste último argumento. O código abaixo simula um uso de handlers de sinais, que imprime uma mensagem quando um sinal é enviado: #include <stdio.h> #include <signal.h> #include <unistd.h> // sleep void simple_handler(int sig) { printf("Hello SIGINT\n"); } int main() { struct sigaction sig_handler = { simple_handler }; sigaction(SIGINT, &sig_handler, NULL); sleep(1000); return 0; } Ao executar o código acima, aperte Ctrl+C e veja que será imprimido a mensagem do nosso handler! O manual da signal contém uma tabela com todos os sinais usados por sistemas POSIX. Para enviarmos sinais facilmente em sistemas UNIX podemos usar o comando kill: $ kill -l O comando acima mostra todos os sinais e seus respectivos números, com isso podemos fazer algo interessante. Por exemplo, rode o código acima em um terminal separado e use o kill para se comunicar com o seu processo, assim: $ ps ax | grep simple_signal $ kill -2 <pid> Primeiro buscamos o PID do nosso processo então usamos o kill que espera como primeiro argumento numero do sinal (listado em kill -l) e o segundo o PID do processo alvo. Ao enviar o sinal, podemos ver que o nosso código reage aos sinais que foram associados a um handler especifico! Tente criar handlers para vários sinais e teste usando o comando kill. 😃 Abaixo um código para demonstrar um uso real de um software que escreve dados aleatórios nos arquivos temporários e antes de uma finalização abrupta, é deletado o que foi usado: #include <stdio.h> #include <signal.h> #include <unistd.h> // Log errors void fatal(const char* err_msg) { fprintf(stderr, "Error: %s\n", err_msg); } // Escreve algo random em um arquivo void random_work() { FILE* temp_files = fopen("/tmp/foo", "w"); if (!temp_files) { fatal("Cant open foo!"); } else { fprintf(temp_files, "%s", "Random random random!\n"); fclose(temp_files); } } // Handler para deleta arquivos criados void handler_termination(int sig) { // Verifica se existe usando a function access // Caso existe usa a syscall unlink para remover o arquivo if (access("/tmp/foo", R_OK) < 0) return; unlink("/tmp/foo"); printf("All clean! closing...\n"); } int main() { //struct sigaction que recebe a function handler_termination como valor do seu handler struct sigaction interruption_handler; interruption_handler.sa_handler = handler_termination; // Syscall sigaction que associa o nosso handler para um sinal especifico // O ultimo campo NULL, espera o handler anterior para que posso tornar o novo handler o default sigaction(SIGINT, &interruption_handler, NULL); random_work(); sleep(1000); handler_termination(0); return 0; } Dica: Dê uma olhada na tabela de sinais e crie handlers para o mesmo código acima! Para a construção do nosso debugger iremos focar mais no signal SIGTRAP, para que seja possível detectar se o nosso processo sofreu uma "trap" da CPU. Uma trap ocorre quando acontece alguma interrupção síncrona na execução, que faz o processo ficar parado até que o sistema operacional execute alguma ação. Isto será usado para implementar e interpretar breakpoints. Veremos tudo isso com mais detalhes em breve! Sinta-se livre para comentar e sugerir correções e melhorias. Até o próximo artigo! Links úteis: Syscall IPC CERO 11 – Linux Syscalls Syscalls, Kernel mode vs User mode Programação em C
  2. Candeer

    AnalyseMe - Nível 05

    Opa, analise abaixo. As analises estão muito boas, evoluindo naturalmente
  3. Candeer

    AnalyseMe - Nível 04

  4. Candeer

    AnalyseMe - Nível 03

    Muito maneiro, segue a analise e o script para decifrar. Algoritmo em python
  5. Candeer

    Ajuda com linguagem C!!!

    Na main() voce esta enviando o vetor em si para o scanf, o certo seria voce enviar somente a variavel que voce deseja atribuir, nesse caso voce acessa ela via indice scanf("%d", vetor[i]); Visto que as variaveis sao globais, nao precisa enviar elas via parametros ja que voce pode acessar-las de qualquer local do codigo e ainda na mediaVetores() voce esta atribuindo valores para os argumentos que nao foram passados, isso funciona na maioria das linguagens(python, java, c++...) mas em C voce nao pode fazer isso. Voce nao precisa de argumentos nesse caso, ja que voce ja sabe quais sao as variaveis nesse codigo. ainda na funcao mediaVetores() voce esta somando um vetor com um float, o correto seria fazer a soma com os valores que estao armazenados no vetor, no caso via indice, mas ja que voce quer somente a media dos vetores, voce pode somar os valores dos dois vetores na variavel total e depois dividir pelo total de elementos. float mediaVetores (){ for (int i = 0; i < TAM_VETOR; i++){ total += ((float)vetor1[i] + (float)vetor2[i]); } total /= TAM_VETOR * 2; return total; } O tamanho dos vetores sao fixos (5), entao voce pode definir uma macro (#define TAM_VETOR 5) e usar ela como tamanho, e voce pode fazer a mediaVetores() retornar um float em vez de ser void. No final, o codigo ficaria da seguinte maneira #include <stdio.h> #include <stdlib.h> #define TAM_VETOR 5 int i; int vetor1 [TAM_VETOR]; int vetor2 [TAM_VETOR]; int vetor3 [TAM_VETOR]; float total = 0; float mediaVetores (){ for (int i = 0; i < TAM_VETOR; i++){ total += ((float)vetor1[i] + (float)vetor2[i]); } total /= TAM_VETOR * 2; return total; } int main() { printf ("\n Digite os valores do primeiro vetor: \n\n"); for (int i = 0; i < 5; i++){ scanf ("%d", &vetor1[i]); } printf ("\n Digite os valores do segundo vetor: \n\n"); for (int i = 0; i < 5; i++){ scanf ("%d", &vetor2[i]); } printf("Media vetores: %f\n", mediaVetores()); } No canal do papo binario tem um curso execelente da linguagem C.
×