Hoje tive uma idéia ao ler um artigo intitulado How Compiling Works no qual o autor descreve em linhas gerais, mas de uma maneira interessante, o processo de compilação de um código escrito em C (abordando o processo de linkagem com as bibliotecas) tomando como exemplo a função printf. Resolvi fazer uma brincadeira, mostrando como isso pode ser realmente implementado na prática, usando chamadas de sistema (syscalls) do Linux, de modo que não seja necessário usar a implementação disponível na glibc.
Começando com um exemplo simples, bem parecido com que todo mundo que um dia pensou em programar já viu. A diferença que a função printf será modificada para receber apenas caracteres (não variáveis) e a quantidade.
#include "stdio.h" int main() { printf("Hello, world!\n",15); return 0; }
Reparem que o cabeçalho está entre aspas, indicando ao compilador que vamos usar uma implementação particular e não a implementação da glibc, no meu caso os arquivos
- /usr/include/stdio.h (protótipo)
- /usr/lib/libc.so (implementação)
Serão substituidos pelos arquivos stdio.h e stdio.c abaixo:
#ifndef STDIO_H_ #define STDIO_H_ int printf(char* texto, int tam); #endif /* STDIO_H_ */
Agora que temos o protótipo da função printf() que é diferente da função printf() que conhecemos mas servirá para esse primeiro exemplo:
#include "stdio.h" int printf(char* texto, int tam) { __asm__ ("movl $0x4,%%eax; \ movl $0x1,%%ebx; \ movl %0,%%ecx; \ movl %1,%%edx; \ int $0x80": : "g" (texto) , "g" (tam) ); return 0; }
Podemos incrementar um pouco mais do código em assembly, para não precisar estipular o número de caracteres.
__asm__ (" xorl %%edx,%%edx; \ xorl %%eax,%%eax; \ movl %0,%%ecx; \ loop: movb (%%ecx,%%edx),%%al; \ incl %%edx; \ test %%al,%%al; \ jnz loop; \ decl %%edx; \ movl $0x4,%%eax; \ movl $0x1,%%ebx; \ int $0x80": : "g" (texto));
Para fazer uma função equivalente ao printf() da glibc precisaríamos implementar o suporte a múltiplos argumentos e mais um monte de coisas
Fiz um esboço de uma idéia do printf, mas ainda usando funções da glibc apenas implementando a syscall write.
#include <stdlib.h> #include <stdarg.h> #include <string.h> #define _write(texto, d) __asm__ ("movl $0x4,%%eax; \ movl $0x1,%%ebx; \ movl %0,%%ecx; \ movl %1,%%edx; \ int $0x80": : "g" (texto) , "g" (d) ); int write(char *fmt, ...); int main(){ char *index=malloc(2); char *nome=malloc(20); strcpy(index,"19"); strcpy(nome,"Escovando Bits !!!"); write("blog: %s \nindex: %s\n",nome,index); return 0; } int write(char *fmt, ...) { char *s; va_list ap; register int d=0,tam; int size; va_start(ap, fmt ); tam = strlen(fmt); for (d=0; d<tam; d++, *++fmt) { if (*fmt != '%' ) { _write(fmt,1); } else { switch (*++fmt) { case 's': { s = va_arg(ap, char *); size = strlen(s); _write(s,size); break; } // fim case } // fim switch } // fim else } // fim for va_end(ap); return(1); }
Se analisarmos o código-fonte da glibc veremos que sua implementação é completamente diferente da proposta nesse post, por questões de portabilidade de hardware, segurança, elegância, desempenho, etc; Um exemplo é a possiblidade de criar extensões para a função printf.
Bom, essa foi a primeira entrada do ano aqui no EscovandoBits, mais novidades para os próximos dias, com a cobertura do Mauro sobre a Campus Party que acontece do dia 19 a 25 de Janeiro.
Arquivado em: programação Etiquetado: | c, glibc, kernel, Linux, syscalls
