bic : interprète AC et explorateur APIC'est un projet qui permet aux développeurs d'explorer et de tester C-API à l'aide d'une boucle d'impression Read EVAL, également connue sous le nom de REP.

Les dépendances d'exécution de BIC sont les suivantes:
Pour construire BIC, vous aurez besoin:
Veuillez vous assurer que vous les installez avant de construire BIC. La commande suivante doit les installer sur un système Debian / Ubuntu:
apt-get install build-essential Libreadline-dev autoconf-archive libgmp-dv attendre flex bison automake m4 libtool pkg-config
Vous pouvez également utiliser la commande suivante pour installer les dépendances requises via Homebrew sur un système macOS.
Brew Installer Bison Flex GMP Readline AutoConf-Archive
Vous pouvez compiler et installer BIC avec les commandes suivantes:
AutoreConf -i ./configure --enable-debug faire faire l'installation
Pour construire sur un système macOS, vous devez modifier la ligne de configuration en:
Yacc = "$ (Brew - Prefix Bison) / bin / bison -y" ./configure --enable-debug
Vous pouvez utiliser Docker pour construire et exécuter BIC avec la commande suivante:
docker build -t bic https://github.com/hexagonal-sun/bic.git#master
Une fois la construction de l'image, vous pouvez ensuite exécuter BIC avec:
docker run -i bic
Si vous utilisez Arch Linux, vous pouvez installer BIC à partir d'AUR:
yay -s bic
Lors de l'invoquer BIC sans arguments, l'utilisateur est présenté avec une invite REP:
BIC>
Ici, vous pouvez taper les instructions C et #include divers en-têtes système pour donner accès à différentes API sur le système. Les instructions peuvent être entrées directement dans le REP; Il n'est pas nécessaire de définir une fonction pour qu'ils soient évalués. Disons que nous souhaitons exécuter le programme C suivant:
#include <stdio.h>
int main ()
{
FILE * f = fopen ( "out.txt" , "w" );
fputs ( "Hello, world!n" , f );
return 0 ;
}Nous pouvons le faire sur le REP avec BIC en utilisant les commandes suivantes:
Bic> #include <stdio.h>
Bic> fichier * f;
f
Bic> f = fopen ("test.txt", "w");
Bic> fputs ("Bonjour, monde! N", f);
1
BIC>
Cela amènera Bic à appeler les fonctions C-Library fopen() et fputs() pour créer un fichier et écrire la chaîne Hello World. Si vous quittez maintenant BIC, vous devriez voir un fichier test.txt dans le répertoire de travail actuel avec la chaîne Hello, Worldn contenu dans l'informatique.
Notez qu'après évaluer une expression, BIC imprimera le résultat de l'évaluation. Cela peut être utile pour tester des expressions simples:
Bic> 2 * 8 + fileno (f); 19
Vous pouvez utiliser BIC pour obtenir des informations sur n'importe quelle variable ou type qui a été déclaré en préfixant son nom avec A ? . Cette syntaxe spéciale ne fonctionne que dans le REP, mais vous permettra d'obtenir diverses caractéristiques sur les types et les variables. Par exemple:
Bic> #include <stdio.h> Bic>? Stdout STDOUT est un pointeur vers une structure _io_file. La valeur de Stdout est 0x7ff1325bc5c0. Sizeof (stdout) = 8 octets. STDOUT a été déclaré sur: /usr/include/stdio.h:138.
Lorsque le REP commence, BIC verra si ~/.bic existe. S'il le fait, il est automatiquement évalué et l'environnement résultant est utilisé par le REP. Cela peut être utile pour définir des fonctions ou des variables couramment utilisées. Par exemple, disons que notre fichier ~/.bic contient:
#include <stdio.h>
int increment ( int a )
{
return a + 1 ;
}
puts ( "Good morning, Dave." );Lorsque nous lançons le REP, nous obtenons:
$ bic Bonjour, Dave. BIC> incrément (2); 3
Si vous passez BIC un fichier source, ainsi que -s , en tant qu'argument de ligne de commande, il l'évaluera, en appelant une fonction main() . Par exemple, supposons que nous ayons le fichier test.c qui contient ce qui suit:
#include <stdio.h>
int factorial ( int n )
{
if (! n )
{
return 1 ;
}
return n * factorial ( n - 1 );
}
int main ()
{
printf ( "Factorial of 4 is: %dn" , factorial ( 4 ));
return 0 ;
} Nous pouvons ensuite invoquer BIC avec -s test.c pour l'évaluer:
$ bic -s test.c Factoriel de 4 est: 24
Si vous souhaitez transmettre des arguments à un fichier C, ajoutez-les à la ligne de commande de BIC. Une fois que BIC a traité l'argument -s , tous les autres arguments sont traités comme des paramètres à transmettre au programme. Ces paramètres sont créés sous forme de variables argc et argv et transmis à main() . La valeur de argv[0] est le nom du fichier C que BIC exécute. Considérez le programme C suivant:
#include <stdio.h>
int main ( int argc , char * argv [])
{
for ( int i = 0 ; i < argc ; i ++ )
printf ( "argv[%d] = %sn" , i , argv [ i ]);
return 0 ;
}Si nous ne transmettons aucun argument:
$ bic -s test.c argv [0] = test.c
Alors que si nous invoquons BIC avec plus d'arguments, ils sont transmis au programme:
$ bic -s test.c -a foo -s bar abc argv [0] = test.c argv [1] = -a argv [2] = foo argv [3] = -s argv [4] = bar argv [5] = a argv [6] = b argv [7] = c
Vous pouvez également utiliser une expression spéciale: <REPL>; Dans votre code source pour faire en sorte que BIC vous déposent dans le REP à un point particulier de l'évaluation du fichier:

Vous pouvez utiliser BIC pour explorer les API d'autres bibliothèques autres que LIBC. Supposons que nous souhaitons explorer la bibliothèque Capstone, nous passons dans une option -l pour faire en sorte que BIC charge cette bibliothèque lorsque cela commence. Par exemple:

Notez que lorsque BIC imprime un type de données composé (une struct ou une union ), elle montre tous les noms de membres et leurs valeurs correspondantes.
Au cœur de la mise en œuvre de Bic se trouve l'objet tree . Ce sont des objets génériques qui peuvent être utilisés pour représenter un programme entier ainsi que l'état de l'évaluateur actuel. Il est implémenté dans tree.h et tree.c Chaque type d'arbre est défini en c.lang . Le fichier c.lang est une spécification de type LISP de:
T_ADD .Addition .tADD .LHS et RHS .Le code pour créer un objet avec l'ensemble d'attributs ci-dessus serait:
( deftype T_ADD " Addition " " tADD "
( " LHS " " RHS " ))Une fois défini, nous pouvons utiliser cet objet dans notre code C de la manière suivante:
tree make_increment ( tree number )
{
tree add = tree_make ( T_ADD );
tADD_LHS ( add ) = number ;
tADD_RHS ( add ) = tree_make_const_int ( 1 );
return add ;
} Notez qu'un ensemble de macros d'accessoires, tADD_LHS() et tADD_RHS() , nous avons été générés pour que nous puissions accéder aux différentes emplacements de propriété. Lorsque --enable-debug est défini pendant la compilation, chacun de ces macros se développe à un chèque pour garantir que lors de la définition de la propriété tADD_LHS d'un objet, l'objet est en effet une instance d'un T_ADD .
Le fichier c.lang est lu par de nombreux compilateurs source à source qui génèrent des extraits de code. Ces services publics comprennent:
gentype : génère une liste des types d'objets d'arbre.gentree : génère une structure qui contient toutes les données de propriété pour les objets d'arbre.genctypes : génère une liste d'objets d'arbre de type C - ceux-ci représentent les types de données fondamentaux en C.genaccess : Générez des macros d'accessoires pour les propriétés des objets arborescentes.gengc : Générez une fonction de marque pour chaque objet d'arbre, cela permet au collecteur des ordures de traverser les arbres d'objets.gendump : générer du code pour vider les objets d'arbre récursivement.gendot : générer un fichier DOT pour une hiérarchie tree donnée, ce qui lui permet d'être visualisé. La sortie de Lexer & Parser est une hiérarchie d'objets tree qui est ensuite transmise dans l'évaluateur ( evaluator.c ). L'évaluateur évaluera ensuite de manière récursive chaque élément d'arborescence, mettant à jour l'état de l'évaluateur interne, exécutant ainsi un programme.
Les appels aux fonctions externes à l'évaluateur sont traités de manière dépendante de la plate-forme. Actuellement, X86_64 et Aarch64 sont les seules plates-formes prises en charge et le code à gérer est respectivement dans les dossiers x86_64 et aarch64 . Cela fonctionne en prenant un objet tree d'appel de fonction (représenté par un T_FN_CALL ) de l'évaluateur avec tous les arguments évalués et en les rassemblant dans une liste liée simple. Ceci est ensuite traversé dans l'assemblage pour déplacer la valeur dans le registre correct en fonction de l'entraînement x86_64 ou AARCH64, puis de la branche vers l'adresse de la fonction.
L'analyseur et le lexer sont mis en œuvre dans parser.m4 et lex.m4 respectivement. Après avoir traversé M4, la sortie est deux analyseurs de bisons et deux lexers flexibles.
La raison de deux analyseurs est que la grammaire d'un REP C est très différente de celle d'un fichier C. Par exemple, nous voulons que l'utilisateur puisse taper des instructions à évaluer sur le REP sans avoir besoin de les emballer dans une fonction. Malheureusement, la rédaction d'une déclaration en dehors d'un corps de fonction n'est pas valide C. En tant que tel, nous ne voulons pas que l'utilisateur puisse écrire des instructions nues dans un fichier C. Pour y parvenir, nous avons deux ensembles de règles de grammaire différents qui produisent deux analyseurs. La plupart des règles de grammaire se chevauchent et nous utilisons donc un seul fichier M4 pour prendre soin des différences.