Maison > développement back-end > C++ > le corps du texte

C Reflection Magic : journalisation simple avec un wrapper pour imprimer des arguments et des résultats de fonctions arbitraires

WBOY
Libérer: 2024-07-18 20:20:31
original
482 Les gens l'ont consulté

C Reflection Magic: Simple Logging with A Wrapper for Printing Arbitrary Functions Arguments and Results

Cet article est un rapport de recherche qui couvre certains aspects potentiels de l'implémentation de l'écriture d'un wrapper d'assistance qui enregistrera automatiquement les arguments et les résultats de la fonction C arbitraire. C'est l'un des exemples pour lesquels la réflexion peut être utile même en C. L'implémentation est basée sur le projet Metac. L’introduction en a été donnée dans cet article. La recherche donne de bons résultats, mais elle est toujours en cours. Les commentaires sur la façon dont cela pourrait être amélioré sont appréciés.

La journalisation est l'un des moyens importants de débogage. Effectuer une journalisation appropriée est la clé pour comprendre ce qui a potentiellement mal tourné sans utiliser de débogueur. Mais c’est ennuyeux d’imprimer tous les arguments de chaque fonction et son résultat. La réflexion C avec Metac pourrait potentiellement avoir la capacité de le faire, car les informations de débogage fournies par DWARF contiennent toutes les données sur le type de chaque argument. Vérifiez-le. Voici l'application de test :

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "metac/reflect.h"

int test_function1_with_args(int a, short b) {
    return a + b + 6;
}
METAC_GSYM_LINK(test_function1_with_args);

int main() {
    printf("fn returned: %i\n", test_function1_with_args(1, 2));

    return 0;
}
Copier après la connexion

Nous voulons créer une sorte de wrapper pour imprimer les arguments de test_function1_with_args. Metac générera ses informations de réflexion depuis METAC_GSYM_LINK(test_function1_with_args); est dans le code. Pour plus de simplicité, les types d’arguments int et courts sont sélectionnés. La première idée pour créer un wrapper est de créer une macro :

void print_args(metac_entry_t *p_entry, ...) {
// use va_args and debug information about types to print value of each argument
}

#define METAC_WRAP_FN(_fn_, _args_...) ({ \
        print_args(METAC_GSYM_LINK_ENTRY(_fn_), _args_); \
        _fn_(_args_); \
    })

int main() {
    // use wrapper instead of printf("fn returned: %i\n", test_function1_with_args(1, 2));
    printf("fn returned: %i\n",
        METAC_WRAP_FN(test_function1_with_args, 1, 2));

    return 0;
}
Copier après la connexion

Ce wrapper ne gère jusqu'à présent que les arguments, mais c'est ok pour la première étape. Essayons d'implémenter print_args. Voici la première tentative naïve :

void print_args(metac_entry_t *p_entry, ...) {
    if (p_entry == NULL || metac_entry_has_paremeter(p_entry) == 0) {
        return;
    }

    va_list args;
    va_start(args, p_entry);

    printf("%s(", metac_entry_name(p_entry));

    // output each argument
    for (int i = 0; i < metac_entry_paremeter_count(p_entry); ++i) {
        if (i > 0) {
            printf(", ");
        }

        // get i-th arg
        metac_entry_t * p_param_entry = metac_entry_by_paremeter_id(p_entry, i);
        if (metac_entry_is_parameter(p_param_entry) == 0) {
            // something is wrong
            break;
        }
        // if it’s … argument just print … - there is no way so far to handle that
        if (metac_entry_is_unspecified_parameter(p_param_entry) != 0) {
            // we don't support printing va_args... there is no generic way
            printf("...");
            break;
        }

        // get arg name and info about arg type
        metac_name_t param_name = metac_entry_name(p_param_entry);
        metac_entry_t * p_param_type_entry = metac_entry_parameter_entry(p_param_entry);
        if (param_name == NULL || param_name == NULL) {
            // something is wrong
            break;
        }

        // lets handle only base_types for now
        if (metac_entry_is_base_type(p_param_type_entry) != 0) {
            // take what type of base type it is. It can be char, unsigned char.. etc
            metac_name_t param_base_type_name = metac_entry_base_type_name(p_param_type_entry);

// if _type_ is matching with param_base_type_name, get data using va_arg and print it.
#define _base_type_arg_(_type_, _pseudoname_) \
    do { \
        if (strcmp(param_base_type_name, #_pseudoname_) == 0) { \
            _type_ val = va_arg(args, _type_); \
            metac_value_t * p_val = metac_new_value(p_param_type_entry, &val); \
            if (p_val == NULL) { \
                break; \
            } \
            char * s = metac_value_string(p_val); \
            if (s == NULL) { \
                metac_value_delete(p_val); \
                break; \
            } \
            printf("%s: %s", param_name, s); \
            free(s); \
            metac_value_delete(p_val); \
        } \
    } while(0)
    // handle all known base types
    _base_type_arg_(char, char);
    _base_type_arg_(unsigned char, unsigned char);
    _base_type_arg_(short, short int);
    _base_type_arg_(unsigned short, unsigned short int);
    _base_type_arg_(int, int);
    _base_type_arg_(unsigned int, unsigned int);
    _base_type_arg_(long, long int);
    _base_type_arg_(unsigned long, unsigned long int);
    _base_type_arg_(long long, long long int);
    _base_type_arg_(unsigned long long, unsigned long long int);
    _base_type_arg_(bool, _Bool);
    _base_type_arg_(float, float);
    _base_type_arg_(double, double);
    _base_type_arg_(long double, long double);
    _base_type_arg_(float complex, complex);
    _base_type_arg_(double complex, complex);
    _base_type_arg_(long double complex, complex);
#undef _base_type_arg_
        }
    }
    printf(")\n");
    va_end(args);
    return;
}
Copier après la connexion

Si nous l'exécutons, nous verrons :

% ./c_print_args
test_function1_with_args(a: 1, b: 2)
fn returned: 9
Copier après la connexion

Ça marche ! Mais il ne gère que les types de base. Et nous voulons que ce soit universel.

Le principal défi ici est avec cette ligne :

 _type_ val = va_arg(args, _type_); 
Copier après la connexion

La macro va_arg de C nécessite que le type de l'argument soit connu au moment de la compilation. Toutefois, les informations de réflexion fournissent uniquement les noms de types au moment de l'exécution. Pouvons-nous le tromper ? va_arg est une macro qui couvre une fonction intégrée. Le deuxième paramètre est un type (chose très atypique). Mais pourquoi cette chose a-t-elle besoin du type ? La réponse est : comprendre la taille et être capable de la sortir de la pile. Nous devons couvrir toutes les tailles possibles et obtenir un pointeur vers l’argument suivant. Du côté de Metac, nous connaissons la taille de l'argument - nous pouvons utiliser cet extrait pour l'obtenir :

        metac_size_t param_byte_sz = 0;
        if (metac_entry_byte_size(p_param_type_entry, &param_byte_sz) != 0) {
            // something is wrong
            break;
        }
Copier après la connexion

Comme idée suivante, créons la macro qui couvrira 1 taille et assurons-nous de la gérer correctement :

        char buf[32];
        int handled = 0;
#define _handle_sz_(_sz_) \
        do { \
            if (param_byte_sz == _sz_) { \
                char *x = va_arg(args, char[_sz_]); \
                memcpy(buf, x, _sz_); \
                handled = 1; \
            } \
        } while(0)
        _handle_sz_(1);
        _handle_sz_(2);
        _handle_sz_(3);
        _handle_sz_(4);
// and so on ...
        _handle_sz_(32);
#undef _handle_sz_

Copier après la connexion

Avec cette approche, nous avons couvert différentes tailles de 1 à 32. Nous pourrions générer un code et couvrir des arguments dimensionnés jusqu'à n'importe quel nombre arbitraire, mais dans la plupart des cas, les gens utilisent des pointeurs plutôt que de transmettre directement des tableaux/structures. Pour notre exemple, nous en garderons 32.
Refactorisons notre fonction pour la rendre plus réutilisable, divisons-la en 2 vprint_args et print_args de la même manière que « vprtintf » et printf :

void vprint_args(metac_tag_map_t * p_tag_map, metac_entry_t *p_entry, va_list args) {
    if (p_entry == NULL || metac_entry_has_paremeter(p_entry) == 0) {
        return;
    }

    printf("%s(", metac_entry_name(p_entry));

    for (int i = 0; i < metac_entry_paremeter_count(p_entry); ++i) {
        if (i > 0) {
            printf(", ");
        }

        metac_entry_t * p_param_entry = metac_entry_by_paremeter_id(p_entry, i);
        if (metac_entry_is_parameter(p_param_entry) == 0) {
            // something is wrong
            break;
        }
        if (metac_entry_is_unspecified_parameter(p_param_entry) != 0) {
            // we don't support printing va_args... there is no generic way
            printf("...");
            break;
        }

        metac_name_t param_name = metac_entry_name(p_param_entry);
        metac_entry_t * p_param_type_entry = metac_entry_parameter_entry(p_param_entry);
        if (param_name == NULL || p_param_type_entry == NULL) {
            // something is wrong
            break;
        }

        metac_size_t param_byte_sz = 0;
        if (metac_entry_byte_size(p_param_type_entry, &param_byte_sz) != 0) {
            // something is wrong
            break;
        }

        char buf[32];
        int handled = 0;
#define _handle_sz_(_sz_) \
        do { \
            if (param_byte_sz == _sz_) { \
                char *x = va_arg(args, char[_sz_]); \
                memcpy(buf, x, _sz_); \
                handled = 1; \
            } \
        } while(0)
        _handle_sz_(1);
        _handle_sz_(2);
//...
        _handle_sz_(32);
#undef _handle_sz_

        if (handled == 0) {
            break;
        }

        metac_value_t * p_val = metac_new_value(p_param_type_entry, &buf);
        if (p_val == NULL) {
            break;
        }
        char * v = metac_value_string_ex(p_val, METAC_WMODE_deep, p_tag_map);
        if (v == NULL) {
            metac_value_delete(p_val);
            break;
        }
        char * arg_decl = metac_entry_cdecl(p_param_type_entry);
        if (arg_decl == NULL) {
            free(v);
            metac_value_delete(p_val);
            break;
        }

        printf(arg_decl, param_name);
        printf(" = %s", v);

        free(arg_decl);
        free(v);
        metac_value_delete(p_val);

    }
    printf(")");
}

void print_args(metac_tag_map_t * p_tag_map, metac_entry_t *p_entry, ...) {
    va_list args;
    va_start(args, p_entry);
    vprint_args(p_tag_map, p_entry, args);
    va_end(args);
    return;
}
Copier après la connexion

Le lecteur remarquera peut-être que nous avons ajouté p_tag_map comme premier argument. Ceci est pour des recherches plus approfondies - ce n'est pas utilisé dans cet article.

Essayons maintenant de créer une partie qui gère le résultat. Malheureusement, typeof n'est pas pris en charge avant C23 (extension gcc en option, mais cela ne fonctionnera pas avec clang) et nous avons un dilemme : voulons-nous conserver notre notation METAC_WRAP_FN telle quelle, ou pouvons-nous lui transmettre un argument supplémentaire - type du résultat de la fonction à utiliser comme tampon. Nous pourrions probablement utiliser libffi pour gérer cela de manière universelle - Metac connaît le type, mais il n'est pas clair comment placer les données renvoyées dans un tampon de taille appropriée. Pour plus de simplicité changeons notre macro :

#define METAC_WRAP_FN_RES(_type_, _fn_, _args_...) ({ \
        printf("calling "); \
        print_args(NULL, METAC_GSYM_LINK_ENTRY(_fn_), _args_); \
        printf("\n"); \
        WITH_METAC_DECLLOC(loc, _type_ res = _fn_(_args_)); \
        print_args_and_res(NULL, METAC_GSYM_LINK_ENTRY(_fn_), METAC_VALUE_FROM_DECLLOC(loc, res), _args_); \
        res; \
    })
Copier après la connexion

Maintenant, nous passons _type_ comme premier argument pour stocker le résultat. Si nous passons un type ou des arguments incorrects, le compilateur se plaindra de ce _type_ res = _fn_(_args_). C'est bien.
Imprimer le résultat est une tâche triviale, nous l'avons déjà fait dans le premier article. Mettons également à jour nos fonctions de test pour accepter différents types de paramètres.
Voici le dernier exemple de code.

Si nous l'exécutons, nous obtiendrons les commentaires :

% ./c_print_args

# show args of base type arg function
calling test_function1_with_args(int a = 10, short int b = 22)
fn returned: 38

# show args if the first arg is a pointer
calling test_function2_with_args(int * a = (int []){689,}, short int b = 22)
fn returned: 1710

# using METAC_WRAP_FN_RES which will print the result. using pointer to list
calling test_function3_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},})
fn returned: 87.820000

# another example of METAC_WRAP_FN_RES with int * as a first arg
calling test_function2_with_args(int * a = (int []){689,}, short int b = 22)
test_function2_with_args(int * a = (int []){689,}, short int b = 22) returned 1710

# the log where 1 func with wrapper calls another func with wrapper
calling test_function4_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},})
calling test_function3_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},})
test_function3_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},}) returned 87.820000
test_function4_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},}) returned -912.180000
Copier après la connexion

On voit que Metac nous imprime la représentation profonde des arguments ainsi que des résultats. En général, cela fonctionne, bien qu'il y ait quelques défauts comme la nécessité de gérer chaque taille d'argument séparément.

Voici quelques limitations supplémentaires :

  1. clang n'expose pas les informations de débogage sur les fonctions externes comme printf. Cela signifie que notre wrapper ne fonctionnera pas avec cela tel quel. Nous devrons peut-être introduire quelques astuces supplémentaires.
  2. les fonctions avec des arguments non spécifiés... n'afficheront pas de tels arguments. il n'existe pas de moyen générique, mais nous souhaiterons peut-être fournir un moyen de fournir un rappel pour extraire des informations dans de tels cas.
  3. il n'y a pas (encore ?) de support pour les cas d'arguments liés, par ex. lorsque nous passons le pointeur et la longueur comme 2 arguments séparés mais logiquement connectés.

Si vous avez des suggestions sur la façon dont cela pourrait être plus générique, veuillez commenter. Merci d'avoir lu !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal