Jump to content

Search the Community

Showing results for tags 'c'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Supporter area
    • Tools of the Trade
    • Finance transparency
  • MBConf
    • MBConf v1
    • MBConf v2
    • MBConf v3
  • Mente Binária
    • General
    • Computer Architecture
    • Certifications
    • Quantum computing
    • Cryptography
    • Challenges and CTF
    • Hardware Hacking
    • Electronics
    • Conferences
    • Forensics
    • Games
    • Data privacy and laws
    • Code breaking
    • Networking
    • Pentest
    • Speak to us!
    • Software releases
  • Career
    • Study and profession
    • Jobs
  • Reverse Engineering
    • General
    • Malware Analysis
    • Firmware
    • Linux and UNIX-like
    • Windows
  • Programming
    • Assembly
    • C/C++
    • Python
    • Other languages
  • Operating Systems
    • GNU/Linux and UNIX-like
    • Windows
  • Segurança na Internet's Discussão

Categories

  • Portal Mente Binária
  • Specials

Categories

  • Tech basics
    • Text comprehension
    • English
    • Mathematics
  • Computing Basics
    • Lógica de Programação
    • Computers Architecture
    • Cryptography
    • Data Structures
    • Network
    • Operating Systems
  • Specifics
    • SO Internals
    • Web
    • Python
    • Javascript
    • Infrastructure
    • Go
    • Reverse Engineering
    • DevOps
    • C/C++
    • Log Analysis

Categories

  • Crackmes
  • Documentation
  • Debuggers
  • PE tools
  • Books
  • Util
  • Packers
  • Unpackers
  • Virtual Machines

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


GitHub


Twitter


LinkedIn


Website

Found 12 results

  1. Pessoal, disponibilizei no meu GitHub o meu primeiro projeto OpenSource, como forma de retornar para a comunidade o que tenho aprendido nestes últimos anos. O projeto se chama LIBCPF (C Plugin Framework - https://github.com/fabianofurtado/libcpf/) e se trata de uma biblioteca/framework pra gerenciamento de Plugins (".so") no Linux, feita em C. Para maiores detalhes, veja a documentação no arquivo README.md. Ela foi lançada utilizando-se a licença GPL v2, uma vez que não entendo muito sobre esse assunto e precisava de uma licença para o projeto. Espero que, de alguma forma, este projeto possa ser útil para você. Mesmo que não exista a necessidade de utilização desta lib em sua aplicação, a mesma pode ser usada para listar os nomes das funções e os offsets que existem dentro de uma shared library, algo que considero bem interessante. Veja no exemplo como isso funciona. Como qualquer programa em C, o desenvolvimento deu muito trabalho pois tentei desenvolvê-la com foco na segurança, tentando evitar possíveis "buffer overflows" ou qualquer outro tipo de vulnerabilidades. Sugestões e críticas serão sempre bem-vindas! Desde já, agradeço.
  2. Introdução Há poucas semanas, um amigo comentou que estava buscando automatizar um processo que envolvia buscas por expressões regulares em arquivos binários. Essa tarefa normalmente é realizada com ferramentas de linha de comando específicas (grep, git-grep, ack-grep, ag, ripgrep, ugrep, entre outras), e um script pode invocá-las e fazer o parsing do output. A ripgrep em particular oferece resultados em JSON, o que facilita bastante o processo. Apesar disso, chamar um processo externo e intepretar a saída não é a maneira ideal de incorporar uma funcionalidade em um programa, sendo a mais correta o uso de uma biblioteca, mas surpreendi-me ao perceber que não havia nenhuma que cumprisse tal propósito. E assim nasceu a libag, um fork da ferramenta ag com o objetivo de transformá-la em uma biblioteca para facilitar a incorporação, em outros programas, do recurso de busca avançada no conteúdo de arquivos. Uso e recursos A libag requer as mesmas dependências do ag: liblzma, libpcre e zlib. No Ubuntu (e possivelmente em outras distribuições Debian-like): $ sudo apt install libpcre3-dev zlib1g-dev liblzma-dev Uma vez resolvidas as dependências o processo de build é bastante simples. Basta clonar o repositório: $ git clone https://github.com/Theldus/libag $ cd libag/ e compilar: Makefile $ make -j4 $ make install # Opcional CMake $ mkdir build && cd build/ $ cmake .. -DCMAKE_BUILD_TYPE=Release $ make -j4 O uso da biblioteca é bastante simples e consiste no uso de três funções básicas: ag_init(), ag_search() e ag_finish() (embora funções mais avançadas também estejam disponíveis). Um exemplo mínimo e completo segue abaixo: #include <libag.h> int main(void) { struct ag_result **results; size_t nresults; char *query = "foo"; char *paths[1] = {"."}; /* Initiate Ag library with default options. */ ag_init(); /* Searches for foo in the current path. */ results = ag_search(query, 1, paths, &nresults); if (!results) { printf("No result found\n"); return (1); } printf("%zu results found\\n", nresults); /* Show them on the screen, if any. */ for (size_t i = 0; i < nresults; i++) { for (size_t j = 0; j < results[i]->nmatches; j++) { printf("file: %s, match: %s\n", results[i]->file, results[i]->matches[j]->match); } } /* Free all results. */ ag_free_all_results(results, nresults); /* Release Ag resources. */ ag_finish(); return 0; } Uma vez que a libag possui bindings para Python e Node.js, seguem os mesmos exemplos abaixo: Python: from libag import * # Initiate Ag library with default options. ag_init() # Search. nresults, results = ag_search("foo", ["."]) if nresults == 0: print("no result found") else: print("{} results found".format(nresults)) # Show them on the screen, if any. for file in results: for match in file.matches: print("file: {}, match: {}, start: {} / end: {}". format(file.file, match.match, match.byte_start, match.byte_end)) # Free all resources. if nresults: ag_free_all_results(results) # Release Ag resources. ag_finish() Node.js (sujeito a alterações): const libag = require('./build/Release/libag_wrapper'); libag.ag_init(); r = libag.ag_search("foo", ["."]); r.results.forEach((file, i) => { file.matches.forEach((match, j) => { console.log( "File: " + file.file + ", match: " + match.match + ", start: " + match.byte_start + " / end: " + match.byte_end ); }); }); libag.ag_finish(); Uso "avançado" e pesquisa em arquivos binários O uso básico é como ilustrado acima e representa também as opções padrões do ag. Entretanto, a libag permite um controle fino sobre as opções de busca e também sobre as worker threads. As opções podem ser tunadas a partir da estrutura struct ag_config, que contém uma série de inteiros que representam diretamente o recurso desejado. Opções como num_workers e search_binary_files definem a quantidade de worker threads e habilitam a busca em arquivos binários, respectivamente. Um exemplo mais interessante segue abaixo, vamos localizar arquivos ELF e PE32 que também contenham meu nome de usuário neles: from libag import * pattern = "david"; elf_signature = "^\x7f\x45\x4c\x46"; pe_signature = "^\x4d\x5a"; signatures = elf_signature + "|" + pe_signature; # Enable binary files search config = ag_config() config.search_binary_files = 1 # Initiate Ag library with custom options. ag_init_config(config) # Search. nresults, results = ag_search(signatures, ["dir/"]) if nresults == 0: print("no result found") sys.exit(1) print("{} pe+elf found".format(nresults)) for file in results: print("file: {}".format(file.file)) pe_elf = [] # Add to our list for file in results: pe_elf.append(file.file); ag_free_all_results(results) # Search again looking for pattern nresults, results = ag_search(pattern, pe_elf) if nresults == 0: print("no result found") sys.exit(1) # Show them print("{} binaries found that matches {}".format(nresults, pattern)) for file in results: for match in file.matches: print("file: {}, match: {}, start: {} / end: {}". format(file.file, match.match, match.byte_start, match.byte_end)) # Free all resources. ag_free_all_results(results) # Release Ag resources. ag_finish() A pasta dir/ contém um arquivo ELF, um PE32+, um plaintext e uma imagem .png. Note que o range informado pode ser verificado com dd+hexdump, como segue abaixo: $ file dir/* dir/hello: PE32+ executable (console) x86-64, for MS Windows dir/img.png: PNG image data, 1247 x 711, 8-bit/color RGB, non-interlaced dir/libag.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped dir/random.txt: ASCII text $ ./elf_pe.py 2 pe+elf found file: dir/libag.so file: dir/hello 1 binaries found that matches david file: dir/libag.so, match: david, start: 90094 / end: 90098 $ dd if=dir/libag.so bs=1 skip=90094 count=5 | hexdump -C 5+0 records in 5+0 records out 5 bytes copied, 2.1437e-05 s, 233 kB/s 00000000 64 61 76 69 64 |david| 00000005 É claro que o exemplo acima é bastante simplório e serve apenas para ilustrar possíveis casos de uso da biblioteca. O processo de "libificação" A escolha do fork do ag foi bem natural: eu já tinha alguma familiaridade com o código fonte do ag e eu já havia brincado com ele alguns meses atrás. Além disso, o código do ag não é tão grande e eu sou um programador C em 99.9% do meu tempo, o que elimina alternativas como ripgrep (Rust) e ugrep (C++). O processo de "libificação" não foi tão complicado: primeiro eu extraí apenas o código fonte do projeto e escrevi meus próprios/novos scripts de build por cima (Makefile e CMake). Depois disso, foi investigar o processo de search original do ag e trazer isso para a minha biblioteca. De forma resumida, o ag original pode ser divido em três grandes etapas: Inicialização Configura o log_level (para propósitos de debug); Inicializa as estruturas de .gitignore (o ag é capaz de ignorar esse tipo de arquivo); Inicializa as opções default da linha de comando; Realiza o parsing das opções de linha de comando; Compila a regex via PCRE; Inicializa os mutexes e as worker threads. Busca O processo de busca e path traversal é realizado por uma única thread, que a partir da lista de paths recuperados via linha de comando invoca a rotina search_dir(), que para cada novo path encontrado o adiciona em uma "work_queue_t" e acorda todas as worker threads (se estiverem dormindo) para recuperar mais trabalhos. O processo de sincronização é feito via mutexes, mas em defesa do maintainer original do ag, um profiling no Intel VTune mostra que o tempo de hold de cada thread é mínímo, o que realmente torna o ag (e libag) escalável com o aumento da quantidade de cores. Resultados Uma vez que uma worker thread (WT) obtém um novo "job", ela então começa a leitura do arquivo: seja via mmap (padrão) ou todo o arquivo de uma só vez. Para buscas de texto literal, a "WT" utiliza os algoritmos de BoyerMoore e de hash para identificação da string. Em caso de expressão regular, utiliza-se a biblioteca PCRE. A decisão dos algoritmos é feita automaticamente, para maior desempenho. À medida que novos resultados são encontrados (para um único arquivo), eles são adicionados em uma lista de matches, definida pelo tipo match_t e ao finalizar a leitura do arquivo os resultados encontrados são impressos na tela, protegidos por um mutex. Salvando os resultados Uma vez obtidos os resultados, entra então a maior "incisão" da libag: interferir no processo de dump dos resultados e salvá-los de forma estruturada e facilmente recuperável posteriormente. Para isso, foi introduzido um vetor de resultados (dinâmico) por WT, que armazena os dados obtidos até o momento. Além disso, a rotina principal das worker threads (search_file_worker()) foi levemente modificada para notificar do término das threads e também permitir facilmente manipular o "start/stop" delas sob demanda. Uma vez que toda a work queue foi preenchida e processada, a rotina de search (da biblioteca) pode resumir sua execução, fazendo o join dos resultados parciais das threads e retornando um "struct ag_search**", que contém uma lista de resultados por arquivo, a string do match, flags e byte de início e fim da ocorrência, algo como: /** * Structure that holds a single result, i.e: a file * that may contains multiples matches. */ struct ag_result { char *file; size_t nmatches; struct ag_match { size_t byte_start; size_t byte_end; char *match; } **matches; int flags; }; Bindings Para facilitar a utilização da libag, o projeto também possui bindings experimentais para Python 2/3 e também Node.js (em desenvolvimento). Python Os bindings para Python foram escritos utilizando o SWIG: um programa que, dado um arquivo de interface, é capaz de gerar um "glue-code" que, quando compilado em conjunto com o código original, funciona como um wrapper/binding para a linguagem em questão. O SWIG gera código compilável para CPython e os tipos não-primitivos (como as duas estruturas introduzidas pela biblioteca) são mapeados via "type-maps". Node.js Embora o SWIG suporte Node.js, ele parece utilizar diretamente a API da v8 engine, que está sempre em constante mudanças, e portanto, o código gerado não compila na última versão estável (v14.17.1 LTS) do Node. Para contornar isso, os bindings para Node.js foram escritos em cima da Node-API, uma API em C (também disponível em C++, com nome de node-addon-api) que oferece um conjunto mínimo de recursos em cima da v8 e que têm como objetivo manter a estabilidade de addons escritos mesmo entre versões distintas do Node e v8. Limitações Ao contrário de programas construídos em cima de uma biblioteca, como o cURL+ libcurl, ag (aparentemente) nunca foi idealizado como tal e portanto o código fonte impõe certas restrições ao adaptá-lo para uma biblioteca, das quais vale destacar: Apenas uma única configuração "global": não é possível (no momento) utilizar opções distintas de search para múltiplos searchs; Uma alternativa a isso é manter múltiplos "ag_config" e setá-los com ag_set_config() antes de cada busca. Esta limitação se deve ao ag utilizar uma única estrutura global para armazenar os parâmetros de linha de comando, estrutura essa utilizada em diversos pontos em todo o código fonte. Uma busca de cada vez: assim como no item anterior, o ag também usa diversas estruturas globais para manter o estado da busca durante um ag_search(), o que impossibilita de utilizar múltiplos searchs simultaneamente. Uma solução temporária foi adicionada com o uso da função ag_search_ts(), que internamente utiliza locks para serializar todas as chamadas a ag_search(). Dentre outros. Qualquer contribuição em pontos como esses e outros é muito bem vinda =). Conclusão Por fim, convido todos a conhecerem a página da libag no GitHub. Trata-se de um projeto de código aberto e, portanto, está aberto a contribuições. Espero que ele possa ser tão útil para mais pessoas quanto está sendo para os que já o utilizam! ~ Theldus signing off!
  3. A heap é uma estrutura especial de memória usada pelo processo. O que tem de especial nela é o fato de seu tamanho ser variável, já que sua memória pode ser alocada ou desalocada dinamicamente pelo processo. Isso pode ser feito usando syscalls do sistema operacional e o mesmo é responsável por alocar mais páginas de memória para a seção caso seja necessário. No Linux o segmento de dados pode ser aumentado ou diminuído usando a syscall brk, e é esse espaço de memória que os programas normalmente usam para a heap. Em C nós normalmente não usamos a heap diretamente e, ao invés disso, usamos a função malloc() e semelhantes para lidar com a alocação dinâmica de memória. O que alguns não sabem é que na verdade chamadas para a função malloc() ou free() não necessariamente irão resultar na alocação ou desalocação de memória para o processo. Isso se dá porque é a libc quem de fato gerencia a heap e nós não estamos diretamente solicitando ou liberando memória para o sistema operacional. *libc é a biblioteca padrão da linguagem C que contém funções essenciais para tarefas básicas como: Manipulação de strings e arquivos, entrada e saída de dados, funções básicas de matemática etc. A função malloc(), de acordo com a implementação da glibc, usa o segmento de dados, o dividindo em uma ou mais regiões de memória que eles chamam de “arenas”. A arena principal corresponde à heap original do processo, porém outras arenas também podem ser alocadas para o processo. Inicialmente cada thread criada no processo tem uma arena individual até atingir um certo limite pré-definido de arenas que podem ser alocadas no processo. Atingindo esse limite, as threads posteriores passam a compartilhar a mesma arena. Em cada arena de memória existem divisões da memória que são chamadas de maneira homônima de heap, e são nessas heaps que a função malloc() de fato aloca memória para o usuário da libc. Cada bloco de memória que malloc() aloca na heap é chamado de chunk, e cada chunk contém metadados usados pelo sistema interno de malloc para organizar a heap como uma lista duplamente encadeada. Para fins de ilustração, abaixo está a estrutura de um chunk, usada na glibc: struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; INTERNAL_SIZE_T mchunk_size; struct malloc_chunk* fd; struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; O valor mchunk_prev_size seria o tamanho do chunk anterior e mchunk_size o tamanho do chunk atual. Os ponteiros *fd e *bk são usados somente quando o chunk está livre, e seriam ponteiros usados para a lista circular duplamente encadeada de chunks que apontam para o chunk posterior e anterior, respectivamente. No entanto, essa estrutura não representa muito claramente como o chunk é de fato usado pelo sistema de malloc, na figura abaixo isso é ilustrado com mais precisão. O ponteiro que malloc() retorna não aponta para o início do chunk mas sim para o início do espaço de memória que pode ser usado pelo usuário. O tamanho do espaço de memória de um chunk é alinhado pelo tamanho de um double word na arquitetura. Caso malloc() seja chamado passando um tamanho desalinhado como argumento, um espaço extra é alocado para manter o alinhamento. Por exemplo, se o alinhamento está sendo feito para 8 bytes e malloc é chamada com 9 como argumento, malloc irá te devolver um chunk com 16 bytes de espaço de memória usável. Além do alinhamento no tamanho do chunk, também existe um alinhamento no endereço de memória retornado por malloc() que é sempre alinhado para o tamanho de uma word. Isso é feito porque em algumas arquiteturas esse alinhamento de memória é necessário para se evitar uma exceção. Em outras arquiteturas (x86, por exemplo) o alinhamento melhora a performance do processador no acesso à memória. Como existe esse alinhamento no tamanho de um chunk isso garante que os três bits menos significativos de mchunk_size não sejam necessários para definir o tamanho do chunk. Se aproveitando disso, os três últimos bits são usados como flags para determinar alguns metadados usados pelo sistema de chunks. O bit M indica que o chunk não pertence a nenhuma arena e, ao invés disso, foi alocado dinamicamente em uma memória mapeada. Caso este bit esteja ligado, os outros dois são ignorados. No contexto de um chunk livre este bit está sempre desligado, tendo em vista que a lista encadeada de chunks livres somente se aplica a chunks que estão em alguma arena. Os chunks diretamente mapeados na memória (com bit M ligado) são criados para chunks muito grandes. Esses chunks quando liberados com a função free() são imediatamente liberados da memória. Por outro lado, usar free() em um chunk comum não necessariamente irá liberar memória para o sistema operacional. O que free() faz nesse caso é marcar o chunk como livre o adicionando de volta à lista de chunks livres. Assim como é indicado nesse trecho da glibc: /* Mark the chunk as belonging to the library again. */ (void)tag_region (chunk2mem (p), memsize (p)); Repare como o comentário descreve a ação como “marcar o chunk como pertencente à biblioteca novamente”, e é efetivamente isso que a função free() faz, não sendo necessariamente uma liberação de memória para o sistema operacional. Inclusive um recurso de otimização que a glibc usa é o que eles chamam de tcache (Thread Local Cache), que se trata de uma lista de chunks existente em cada thread individualmente. Quando você aloca um novo chunk na thread e posteriormente o libera, ele é adicionado ao tcache daquela thread e pode ser reutilizado em uma nova alocação posterior. Um adendo que a função free() pode efetivamente liberar memória para o sistema operacional se houver vários chunks livres no topo do segmento de dados (o que raramente acontece). Ela faz isso chamando a função interna systrim(), que por sua vez (no Linux) usa a syscall brk para diminuir novamente o segmento de dados. Um detalhe interessante que vale citar aqui é que na glibc (no Linux) existem as funções brk e sbrk que servem como wrappers para aumentar/diminuir o segmento de dados. O sistema de liberação de memória do systrim() espera que essas funções não sejam utilizadas diretamente para poder fazer a liberação de memória apropriadamente. Se você usá-las em seu código por algum motivo, irá “quebrar” o sistema de liberação de memória automático do free(), o fazendo não mais liberar memória quando é usado em chunks de arenas. Logo, não é recomendável que você use essas funções diretamente a menos que você esteja implementando seu próprio sistema de gerenciamento de memória dinâmica. O código abaixo é um experimento a fim de vermos na prática os metadados do chunk no Linux: // gcc test.c -o test #include <stdio.h> #include <stdlib.h> int main(void) { size_t *content = malloc(8); size_t chunk_size = content[-1] & ~0b111; size_t chunk_flags = content[-1] & 0b111; printf("size: %zu\nflags: %zu\n", chunk_size, chunk_flags); return 0; } No meu Linux é retornado 32 como tamanho do chunk e 1 como flag, indicando que somente o bit P está ligado. Sugiro ao leitor variar o tamanho passado para malloc a fim de comprovar que o alinhamento do tamanho do chunk de fato ocorre. Também sugiro passar um número grande para malloc() a fim de ver a partir de qual tamanho malloc() irá deixar de usar uma arena e irá alocar o chunk com mmap(). Caso isso ocorra o bit M será ligado e o número 2 (decimal) será indicado como flags. Nota: Esse código propositalmente não utiliza free() antes de finalizar o programa. É redundante e desnecessário usá-la quando o programa é finalizado, tendo em vista que todas as páginas de memória usadas pelo processo serão liberadas pelo sistema operacional. Referências https://man7.org/linux/man-pages/man2/brk.2.html https://sourceware.org/glibc/wiki/MallocInternals https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=e2d7b1b58396906375ba0e953a20ac57f0904378;hb=refs/heads/master http://c-faq.com/malloc/freeb4exit.html
  4. Os compiladores são ferramentas muito úteis e importantes no mundo da programação e desenvolvimento. A função básica dos compiladores é pegar uma linguagem de "alto nível" (linguagem com maior nível de abstração do hardware) e produzir um código semanticamente equivalente em "baixo nível". A velocidade de execução do código compilado é uma vantagem que se destaca, tendo em vista que o compilador faz otimizações no processo de compilação. Verificações de erros sintáticos e semânticos são outras funcionalidades também executadas pelo compilador. Por que criar um compilador? Além dos motivos mencionados anteriormente, a forma mais simples e rápida de entender como os compiladores funcionam é criando um. Neste tutorial iremos criar um compilador simples, porém abordando os principais conceitos da compilação de forma teórica e prática. Para seguir esse tutorial será necessário o conhecimento de algoritmo e no mínimo uma linguagem de programação. Neste artigo estarei utilizando a linguagem C. Antes de começarmos a criação do projeto, vamos organizar o nosso projeto: Criaremos uma linguagem que trabalha com números inteiros e reais; Utilizaremos condições (if, else, etc); Utilizaremos expressões aritméticas e relacionais; Etapas da compilação As etapas que um compilador executa são: Análise léxica, Análise sintática, análise semântica, otimizador de código e gerador de código objeto. Alguns compiladores tem uma estrutura bem mais complexa, dependendo da linguagem a ser compilada: Nosso projeto terá as seguintes etapas: análise léxica, análise sintática, análise semântica e gerador de código. O gerador de código vai gerar um bytecode para uma máquina virtual que também vamos implementar. Bytecodes são instruções para uma máquina virtual, como mover um valor para a memória ou para um registrador, por exemplo. Abaixo podemos ver um trecho de código em Python e seus respectivos bytecodes: def soma(): print(10 + 10) 0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 (20) 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE No final desta série estaremos executando o seguinte código: INIT VAR max := 10 VAR num INPUT num IF (num < max) INIT PRINT 0 END ELSE INIT PRINT 1 END END Análise Léxica A análise léxica consiste em pegar cada caractere de uma linguagem e identificar os padrões da linguagem. Exemplo: int a = 10 Aqui podemos identificar os seguintes padrões: int é uma palavra reservada do compilador; a é um identificador/variável; = é um sinal de atribuição; 10 é um número inteiro; Ao realizar esse processo estamos identificando os lexemas, que são pedaços de uma string (texto), reconhecidos pelo analisador léxico. Os tokens são um par constituído de um nome e um valor de atributo, sendo este último opcional: <tipo, valor> Onde: tipo como o nome já diz seria o tipo do token. valor é o valor de um token. Alguns tokens não utilizam este campo. Representação da análise léxica: Para uma entrada como VAR num := 100 + 10 obtemos os seguintes tokens: <PC_VAR> <ID, num> <OP_ATR> <T_INT, 100> <OP_MAIS> <T_INT, 10> Onde: <PC_VAR> representa a palavra chave VAR; <ID, num> representa um identificador (variável ou função) tendo o valor num; <OP_ART> representa o operador de atribuição =; <OP_MAIS> representa o operador aritmético mais (+); <T_INT, 100>, <T_INT, 10> representa um inteiro com o valor 100 e 10 respectivamente; Não se esqueça que os tipos de token são definidos por você! Usarei o gcc como compilador C e o vscode como editor. Iremos começar de uma forma simples, melhorando tudo aos poucos, vamos nessa! Essa é a estrutura de pastas do nosso projeto. Temos uma pasta para os headers, uma pasta src para o código fonte e a pasta exe, que terá o executável: Escreva o texto seguinte no arquivo teste.txt: INIT PRINT 1 + 2 * 3 END include/lex.h - Aqui simplesmente criamos um módulo para tratar da análise léxica e definimos a função que retorna um token: #ifndef art_lex_h #define art_lex_h void proximo_token(); #endif src/lex.c: Esta é nossa função inicial que lê cada caractere e mostra na console. Se o caractere for EOF, significa que não há mais caracteres no arquivo (fim de arquivo) e então paramos o loop: #include <string.h> #include <ctype.h> #include "glob.h" #include "lex.h" // variável que passará por cada caractere do arquivo static int c; void proximo_token() { while (1) { c = fgetc(file); if (c == EOF) break; else printf("%c", c); } } includes/glob.h: Este outro arquivo serve para algumas definições globais (que vamos usar em mais de um arquivo). Definimos os tipos dos tokens, um enum para representar o token e uma struct com os campos tipo e val: #ifndef art_glob_h #define art_glob_h #include <stdio.h> #include <stdlib.h> FILE *file; // linha atual static int linha = 1; // tipos de tokens enum { // palavras chave PC_INIT, PC_END, PC_PRINT, PC_INPUT, PC_VAR, PC_IF, PC_ELSE, // numeros T_INT, // operadores OP_MAIS, OP_MENOS, OP_MULT, OP_DIVI, // ( ) := < > <= >= = T_LPARENT, T_RPARENT, T_ATRIB, T_MENOR, T_MAIOR, T_MENOR_I, T_MAIOR_I, T_IGUAL, // identificador ID }; typedef struct { int tipo; int val; } Token; Token tok; #endif src/main.c: Na função main iremos tentar abrir um arquivo. Caso haja algum erro o programa sairá mostrando a mensagem de erro. Caso contrário, leremos todos os caracteres do arquivo teste.txt. Vamos ver se funciona: #include <stdlib.h> #include "lex.h" #include "glob.h" int main(int argc, char *argv[]) { // abrir o arquivo file = fopen(argv[1], "r"); if (file == NULL) { printf("Erro ao abrir o arquivo"); exit(EXIT_FAILURE); } proximo_token(); fclose(file); return EXIT_SUCCESS; // ou return 0 } Para facilitar o processo de compilação usaremos o seguinte Makefile: all: gcc -c src/lex.c -I includes -o exe/lex.o gcc src/main.c exe/*.o -I includes -o exe/main rm -r exe/*.o *Se você estiver em um ambiente Windows saiba que o comando rm -r exe/*.o não funcionará. Ao executar o Makefile teremos na pasta exe o arquivo compilado. Ao executarmos teremos a seguinte saída: INIT PRINT 1 + 2 * 3 END Perfeito! Por agora vamos ignorar espaços em branco, tabulação e quebra de linha. Criaremos agora uma função que vai criar um token. Por enquanto ela irá apenas mostrar na saída algo como <’+’, 0> <’INIT’, 0>, mas depois vamos mudar isso. lex.c: Aqui estamos somando 1 na variável linha para uso posterior em caso de nosso compilador ache um caractere que não existe em nossa linguagem (como um “$”, por exemplo): void makeToken(char *nome, int val) // mostrar o token { printf("<%s, %d>", nome, val); } void voltaPonteiro() // volta um caracter se necessário { if (c != EOF) fseek(file, ftell(file)-1, SEEK_SET); } void proximo_token() { // após o if else if (c == ' ' || c == '\t') continue; else if (c == '\n') { linha++; continue; } } No código acima temos uma função voltaPonteiro, que é responsável por voltar um caractere no arquivo. Em alguns casos vamos ter que ver o caractere a frente e depois voltar o caractere quando estivermos analisando uma palavra chave. Enquanto o caractere for alfanumérico o ponteiro avança. Para facilitar o entendimento vamos utilizar a imagem abaixo como exemplo. Aqui reconhecemos a palavra num e paramos no caractere =, ou seja, reconhecemos o token <ID, num>. Quando vamos continuar o processo iniciamos do =, isto é, o próximo caractere é o espaço, seguido do número 1 e assim por diante. Tendo em vista que = é um caractere diferente do que estaríamos esperando iremos esquece-lo e então voltaremos um caractere parando assim no m. lex.c: vamos reconhecer operadores aritméticos como mais (+), menos (-), multiplicação (*) e divisão (/): void proximo_token() { // codigo anterior else if (c == '+') makeToken("+", 0); else if (c == '-') makeToken("-", 0); else if (c == '*') makeToken("*", 0); else if (c == '/') makeToken("/", 0); // codigo else Ao compilar o código e executar teremos algo como: $ ./exe/main.exe teste.txt INITPRINT1<+, 0>2<*, 0>3END lex.c: Agora vamos reconhecer os demais números, palavras, parênteses, etc: else if (c == '+') { makeToken("+", 0); } else if (c == '-') { makeToken("-", 0); } else if (c == '*'){ makeToken("*", 0); } else if (c == '/') { makeToken("/", 0); } else if (c == '(') { makeToken("(", 0); } else if (c == ')') { makeToken(")", 0); } else if (c == ':') { c = fgetc(file); // pega o próximo caractere if (c == '=') // se for '=' sabemos que é o token ':=' makeToken(":=", 0); } else if (c == '<') { c = fgetc(file); // pega o próximo caractere if (c == '=') // se for '=' sabemos que é o token '<=' makeToken("<=", 0); else makeToken("<", 0); } else if (c == '>') { c = fgetc(file); if (c == '=') makeToken(">=", 0); else makeToken(">", 0); } else if (c == '=') { makeToken("=", 0); } else if (isdigit(c)) { numero(); } else if (isalpha(c)) { palavra(); } else { printf("O caracter '%c' na linha %d nao reconhecido.\n", c, linha); exit(EXIT_FAILURE); } lex.c: Temos duas novas funções, são elas palavra e numero: void palavra() { char palavra[100] = ""; int pos = 0; while (isalnum(c)) { palavra[pos++] = c; c = fgetc(file); } voltaPonteiro(); if (strcmp(palavra, "INIT") == 0) makeToken("INIT", 0); else if (strcmp(palavra, "PRINT") == 0) makeToken("PRINT", 0); else if (strcmp(palavra, "INPUT") == 0) makeToken("INPUT", 0); else if (strcmp(palavra, "VAR") == 0) makeToken("VAR", 0); else if (strcmp(palavra, "IF") == 0) makeToken("IF", 0); else if (strcmp(palavra, "ELSE") == 0) makeToken("ELSE", 0); else if (strcmp(palavra, "END") == 0) makeToken("END", 0); else makeToken("ID", 0); } Não é a função mais otimizada que você já viu, mas funciona: void numero() { int k = 0; while (isdigit(c)) { k = k * 10 + c - '0'; c = fgetc(file); } voltaPonteiro(); makeToken("T_INT", k); } Testamos o código agora: $ ./exe/main teste.txt <INIT, 0><PRINT, 0><T_INT, 1><+, 0><T_INT, 2><*, 0><T_INT, 3><END, 0> Olha só, reconhecemos a maior parte dos tokens de nossa linguagem! Agora que tal mais um teste utilizando outro teste.txt? INIT VAR max := 10 VAR num INPUT num IF (num < max) INIT PRINT 0 END ELSE INIT PRINT 1 END END $ ./exe/main teste.txt <INIT, 0><VAR, 0><END, 0><:=, 0><=, 0><T_INT, 10><VAR, 0><END, 0><INPUT, 0><END, 0><IF, 0> <(, 0><END, 0><<, 0><END, 0><), 0><INIT, 0><PRINT, 0><T_INT, 0><END, 0><ELSE, 0><INIT, 0> <PRINT, 0><T_INT, 1><END, 0><END, 0> Na próxima parte vamos fazer algumas alterações no analisador léxico e depois daremos início ao analisador sintático. Até lá. 🙂
  5. Eu tenho serios problemas com meu GCC ao usar funcoes matematicas. Esse codigo eh um exercicios (13 do capitulo 3) do livro do Andre Backes - Linguagem - Completa e Descomplicada. Como no exemplo abaixo: #include <stdio.h> #include <stdlib.h> #include <tgmath.h> int main(void) { double h, a, b; printf("-- Calculo da Hipotenuza do triangulo retangulo --\n"); scanf("%lf%lf",&a,&b); h = sqrt(exp(2.00*log(a) + exp(2.00*log(b)))); printf("O valor da hipotenuza eh: %.4lf\n",h); return EXIT_SUCCESS; } O problema aparece quando vou compilar, seja usando make ou gcc -std=c??. Tambem usando -std=gnu?? make exec13_3 cc exec13_3.c -o exec13_3 /usr/bin/ld: /tmp/ccktALow.o: na função "main": exec13_3.c:(.text+0x36): referência não definida para "log" /usr/bin/ld: exec13_3.c:(.text+0x49): referência não definida para "log" /usr/bin/ld: exec13_3.c:(.text+0x52): referência não definida para "exp" /usr/bin/ld: exec13_3.c:(.text+0x5c): referência não definida para "exp" /usr/bin/ld: exec13_3.c:(.text+0x61): referência não definida para "sqrt" collect2: error: ld returned 1 exit status make: *** [<builtin>: exec13_3] Error 1 Eh nisso que fico perdido. Consultei as headers tgmath.h e math.h, alem de sua indicacao de livro (pdf) "ModernC". Mas nao consigo achar o problema da falta de referencia. PS: sim, a ortografia falha eh por conta do teclado US.
  6. Version 1a

    345 downloads

    Estruturas de dados lineares básica Abordagem prática, com implementações em C e Java. Valéria Maria Bezerra Cavalcanti Nadja da Nóbrega Rodrigues Sinopse Número de páginas: 296 ISBN: 9788563406613 Biografia do Autor Nadja da Nóbrega Rodrigues Nadja da Nobrega Rodrigues é mestre em Administração de Empresas (UFPB), especialista em Sistemas de Informação e Redes de Computadores (UFPB) e graduada em Ciência da Computação (UFPB). Trabalhou por 12 anos na indústria de TIC, fazendo parte de empresas públicas como SERPRO, DATAPREV e UFPB (NTI), além de ter atuado em várias empresas da iniciativa privada, dentre elas, a Confederação das Unimeds do Norte-Nordeste. Na área acadêmica, lecionou em faculdades nos estados da Paraíba e Pernambuco, e na UFPB. Atualmente é professora, pesquisadora e extensionista no IFPB, atuando nas áreas de Engenharia de Software, Sistemas de Informação e Inclusão Digital.
  7. Version v0.33.9

    1,698 downloads

    Livro gratuito, de mais de 200 páginas, sobre Assembly 64-bits e C, além de outros assuntos. É repleto de dicas, teoria e prática. ? Segue o conteúdo resumido: Introdução Capítulo 1: Introdução ao processador Capítulo 2: Interrompemos nossa programação Capítulo 3: Resolvendo dúvidas frequentes sobre a Linguagem C Capítulo 4: Resolvendo dúvidas sobre a linguagem Assembly Capítulo 5: Misturando C e Assembly Capítulo 6: Ferramentas Capítulo 7: Medindo performance Capítulo 8: Otimizações “automáticas” Capítulo 9: Caches Capítulo 10: Memória Virtual Capítulo 11: Threads Capítulo 12: Ponto flutuante Capítulo 13: Instruções Estendidas Capítulo 14: Dicas e macetes Capítulo 15: Misturando Java e C Capítulo 16: Usando Python como script engine Apêndice A: System calls Apêndice B: Desenvolvendo para Windows usando GCC Apêndice C: Built-ins do GCC Apêndice D: Módulos do Kernel
  8. Pessoal, recebi recentemente esta referência de técnicas anti-debug atualizadas por uma fonte confiável, que é a empresa Check Point. Não cheguei a olhar ainda, mas achei interessante de compartilhar logo com os colegas, por ser um tema de grande relevância na área de engenharia reversa. Abraços! https://research.checkpoint.com/2020/cpr-anti-debug-encyclopedia-the-check-point-anti-debug-techniques-repository/
  9. Alguém me indica um bom livro para aprender programação utilizando sockets em C? Pode ser em inglês. Desde já agradeço!
  10. Olá, estou tendo problemas com esse código. Eu tenho que inserir um dado em uma Fila, na qual eu tenho um ponteiro para um vetor de structs. Eu tenho um limite de elementos na qual cada fila pode ter(MAX_CLIENTES(definida como 50)). O problema que estou enfrentando é que de alguma maneira algumas vezes ele não insere pois a fila está cheia e outras vezes insere normalmente.Segue o código. Quem puder me dar alguma dica de onde posso estar errando, acredito que seja na alocação de memória. #include <stdio.h> #include <stdlib.h> #include<time.h> #define MAX_CLIENTES 50 #define MAX_CAIXAS 20 typedef struct node{ int numItems; struct node* next; }nodo; typedef struct fila{ int tamanho; nodo* inicio; nodo* fim; }Fila; Fila* criaFila(){ Fila* nova_fila = (Fila*) malloc(sizeof(Fila)); nova_fila->tamanho = 0; nova_fila->inicio = NULL; nova_fila->fim = NULL; return nova_fila; } int vazia(Fila* fila){ return (fila->fim == NULL); } nodo* ins_fim (nodo* fim, int v){ nodo* p = (nodo*) malloc(sizeof(nodo)); p->numItems = v; p->next = NULL; if (fim != NULL) /* verifica se lista não estava vazia */ fim->next = p; return p; } void insere (Fila** f, int v, int i){ if(f[i]->tamanho < MAX_CLIENTES){ f[i]->fim = ins_fim(f[i]->fim,v); if (f[i]->inicio==NULL) /* fila antes vazia? */ f[i]->inicio = f[i]->fim; f[i]->tamanho++; }else{ printf("filha cheia\n"); } } //=========== nodo* ret_ini(nodo* inicio){ nodo* p = inicio->next; free(inicio); return p; } int main(void){ Fila* fila[MAX_CAIXAS]; do{ printf("Quantos caixas deseja criar? \n"); scanf("%d",&quant); }while(quant > MAX_CAIXAS || quant <= 0); for(i = 0; i < quant; i++){ fila[i+1] = criaFila(); } insere(fila, 5,0); } Desde já agradeço.
  11. Pessoal, estou com um problema que talvez seja simples para alguns de vcs, mas como não tenho prática em C, estou com dificuldades. Comecei o keygenme da aula 17 CERO sugerido pelo Fernando que recebe um nome e converte para maiúsculo. E justo nesta parte ele estoura o buffer. O que pode estar errado? [ paulosgf /home/paulosgf/crack/keygenme ] $ cat keygenme.c #include <stdio.h> #include <ctype.h> int main(void) { char *str; printf("Nome: "); scanf("%s", str); while (*str != '\0') { *str = toupper((unsigned char) *str); str++; } printf("%s\n", str); return 0; } [ paulosgf /home/paulosgf/crack/keygenme ] $ gcc -o keygenme keygenme.c [ paulosgf /home/paulosgf/crack/keygenme ] $ ./keygenme Nome: paulo Falha de segmentação
  12. Boas, já procurei mas ainda não consegui fazer o que pretendo, que é o seguinte : Fazer um programa em C que procura por uma Key no registro do windows, se essa key existir "do something" else "do something". Alguém me pode dar umas luzes de como posso fazer isso, por favor ? obrigado
×
×
  • Create New...