Est-il possible de déterminer si un symbole est une variable ou une fonction dans C?

Je suis en train d’implémenter une fonctionnalité de débogage à distance limitée pour une application écrite en C s’exécutant sur une machine Linux. L’objective est de communiquer avec l’application et de rechercher la valeur d’une variable arbitraire ou d’exécuter une fonction arbitraire.

Je peux rechercher des symboles via des dlsym() , mais je ne peux pas déterminer si l’adresse renvoyée fait référence à une fonction ou à une variable. Existe-t-il un moyen de déterminer les informations de frappe via cette table de symboles?

Vous pouvez lire le fichier /proc/self/maps et parsingr les trois premiers champs de chaque ligne:

 - rwxp ... 

Ensuite, vous recherchez la ligne contenant l’adresse que vous recherchez et vérifiez les permissions:

  • rx : c’est du code;
  • rw- : ce sont des données accessibles en écriture;
  • r-- : ce sont des données en lecture seule;
  • toute autre combinaison: quelque chose de bizarre ( rwxp : code généré, …).

Par exemple le programme suivant:

 #include  void foo() {} int x; int main() { int y; printf("%p\n%p\n%p\n", foo, &x, &y); scanf("%*s"); return 0; } 

… dans mon système donne cette sortie:

 0x400570 0x6009e4 0x7fff4c9b4e2c 

… et ce sont les lignes pertinentes de /proc//maps :

 00400000-00401000 r-xp 00000000 00:1d 641656 /tmp/a.out 00600000-00601000 rw-p 00000000 00:1d 641656 /tmp/a.out .... 7fff4c996000-7fff4c9b7000 rw-p 00000000 00:00 0 [stack] .... 

Les adresses sont donc: code , données et données .

Sur les plates-formes x86, vous pouvez vérifier les instructions permettant de configurer la stack pour une fonction si vous pouvez examiner son espace d’adressage. C’est typiquement:

 push ebp mov ebp, esp 

Je ne suis pas positif sur les plates-formes x64, mais je pense que c’est similaire:

 push rbp mov rbp, rsp 

Ceci décrit la convention d’appel C

Gardez à l’esprit que les optimisations du compilateur peuvent optimiser ces instructions. Si vous voulez que cela fonctionne, vous devrez peut-être append un indicateur pour désactiver cette optimisation. Je crois que pour GCC, -fno-omit-frame-pointer fera l’affaire.

Une solution possible consiste à extraire une table de symboles pour l’application en analysant la sortie de l’ utilitaire nm . nm comprend des informations sur le type de symbole. Les symboles avec le type T (texte global) sont des fonctions.

Le problème avec cette solution est que vous devez vous assurer que votre table de symboles correspond à la cible (surtout si vous l’utilisez pour extraire les adresses, bien que l’utiliser en combinaison avec dlsym () soit plus sûr). La méthode que j’ai utilisée pour garantir que la partie de génération de table de symboles du processus de génération est une étape de post-traitement.

Je suppose que ce n’est pas une méthode très fiable, mais cela pourrait fonctionner:

Prenez l’adresse d’une fonction bien connue, telle que main() et l’adresse d’une variable globale bien connue.

Maintenant, prenez l’adresse du symbole inconnu et calculez la valeur absolue de la différence entre cette adresse et les deux autres. La plus petite différence indiquera que l’adresse inconnue est plus proche d’une fonction ou d’une variable globale, ce qui signifie que c’est probablement une autre fonction ou une autre variable globale.

Cette méthode fonctionne sous l’hypothèse que le compilateur / éditeur de liens compresse toutes les variables globales vers un bloc de mémoire spécifique et toutes les fonctions vers un autre bloc de mémoire. Le compilateur Microsoft, par exemple, place toutes les variables globales avant les fonctions (adresses inférieures dans la mémoire virtuelle).

Je suppose que vous ne voudrez pas vérifier les variables locales, car leur adresse ne peut pas être renvoyée par une fonction (une fois la fonction terminée, la variable locale est perdue)

Cela peut être fait en combinant dlsym() et dladdr1() .

 #define _GNU_SOURCE #include  #include  #include  int symbolType(void *sym) { ElfW(Sym) *pElfSym; Dl_info i; if (dladdr1(sym, &i, (void **)&pElfSym, RTLD_DL_SYMENT)) return ELF32_ST_TYPE(pElfSym->st_info); return 0; } int main(int argc, char *argv[]) { for (int i=1; i < argc; ++i) { printf("Symbol [%s]: ", argv[i]); void *mySym = dlsym(RTLD_DEFAULT, argv[i]); // This will not work with symbols that have a 0 value, but that's not going to be very common if (!mySym) puts("not found!"); else { int type = symbolType(mySym); switch (type) { case STT_FUNC: puts("Function"); break; case STT_OBJECT: puts("Data"); break; case STT_COMMON: puts("Common data"); break; /* get all the other types from the elf.h header file */ default: printf("Dunno! [%d]\n", type); } } } return 0; }