2013-11-20 8 views
6

Ich implementiere einige eingeschränkte Remote-Debugging-Funktionalität für eine Anwendung geschrieben in C auf einer Linux-Box. Das Ziel besteht darin, mit der Anwendung zu kommunizieren und den Wert einer beliebigen Variablen zu suchen oder eine beliebige Funktion auszuführen.Ist es möglich zu bestimmen, ob ein Symbol eine Variable oder Funktion in C ist?

Ich bin in der Lage, Symbole durch dlsym() Aufrufe zu suchen, aber ich kann nicht feststellen, ob die zurückgegebene Adresse bezieht sich auf eine Funktion oder eine Variable. Gibt es eine Möglichkeit, Tippinformationen über diese Symboltabelle zu ermitteln?

+4

plattformabhängig, aber Sie können mit 1.er Prüfung der Adresse (Raum), oder 2. durch die Suche nach einer besonderen Funktion Startcode (Trampoline, etc.) –

+0

oder 3. ziehen Sie die Informationen aus wegkommen der DWARF-Debuginformationen, falls verfügbar (was nicht trivial ist) – nos

+0

Debuginformationen sind für diese Anwendung nicht verfügbar; Die Anwendung ist so groß, dass der Versuch, mit Debugging-Informationen zu kompilieren, alle abstürzt, die versuchen, sie zu lesen (gdb) – dykeag

Antwort

2

Sie die Datei lesen können /proc/self/maps und analysieren die ersten drei Felder jeder Zeile:

<begin-addr>-<end-addr> rwxp ... 

Sie dann die Linie suchen, die die Adresse enthält die Sie suchen, und überprüfen Sie die Berechtigungen:

  • r-x: es ist Code;
  • rw-: es sind beschreibbare Daten;
  • r--: es sind schreibgeschützte Daten;
  • jede andere Kombination: etwas seltsam (rwxp: generierten Code, ...).

Zum Beispiel das folgende Programm:

#include <stdio.h> 

void foo() {} 
int x; 

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

... in meinem System gibt diese Ausgabe:

0x400570 
0x6009e4 
0x7fff4c9b4e2c 

... und das sind die entsprechenden Zeilen aus /proc/<pid>/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] 
.... 

So sind die Adressen: Code, Daten und Daten.

+1

Große Antwort! Zur Verdeutlichung für andere Leser ist die erste Spalte mit Zahlen in '/ proc//maps' eine Adresse _range_. Um zu bestimmen, ob ein Symbol eine Funktion ist, sehen Sie, ob der Zeiger in einen Bereich von Adressen fällt, die mit "x" markiert sind. Die Adresse einer Variablen wird in einem Bereich liegen, der nicht mit "x" markiert ist. – dykeag

+0

@rodrigo können Sie mir sagen, was das '% * s' tut? – phyrrus9

+0

@ phyrrus9: Es liest eine Zeichenkette aus der Standardeingabe ('% s'), aber verwirft sie dann, ohne sie irgendwo zu speichern (' * '). Beachten Sie, dass der Aufruf von 'scanf()' keine zusätzlichen Parameter enthält. Ich schrieb das, um das Programm zu stoppen, bis ENTER gedrückt wurde, damit die Datei '/ proc//maps' gelesen werden kann. Manche Leute bevorzugen es stattdessen 'getchar()' zu benutzen ... – rodrigo

3

Auf x86-Plattformen können Sie nach den Anweisungen zum Einrichten des Stacks für eine Funktion suchen, wenn Sie in den Adressraum des Stacks schauen können. Es ist in der Regel:

push ebp 
mov ebp, esp 

ich x64-Plattformen nicht positiv bin, aber ich denke, es ähnelt:

push rbp 
mov rbp, rsp 

This die Konvention C-Aufruf beschreibt

jedoch Denken Sie daran, Compiler Optimierungen können diese Anweisungen optimieren. Wenn dies funktioniert, müssen Sie möglicherweise ein Flag hinzufügen, um diese Optimierung zu deaktivieren. Ich glaube an GCC, -fno-omit-frame-pointer wird den Trick machen.

+1

Wenn der Code ohne Optimierungen nicht kompiliert wird, wird der Frame-Zeiger wahrscheinlich wo möglich weggelassen. Das wäre also nicht zuverlässig. –

+0

Oh, das stimmt. Ich bin sicher, dass er diese eine Optimierung deaktivieren konnte. Ich werde meine Antwort bearbeiten, danke – chbaker0

2

Eine mögliche Lösung besteht darin, eine Symboltabelle für die Anwendung zu extrahieren, indem die Ausgabe des nm utility analysiert wird. nm enthält Informationen zum Symboltyp. Symbole mit dem Typ T (globaler Text) sind Funktionen.

Das Problem mit dieser Lösung ist, dass Sie sicherstellen müssen, dass Ihre Symboltabelle mit dem Ziel übereinstimmt (besonders wenn Sie es verwenden, um die Adressen zu extrahieren, obwohl es in Kombination mit dlsym() sicherer wäre). Die Methode, die ich verwendet habe, um sicherzustellen, dass die Generierung der Symboltabelle Teil des Build-Prozesses ist, ist ein Post-Processing-Schritt.

1

Ich denke, dies ist nicht eine sehr zuverlässige Methode, aber es könnte funktionieren:

die Adresse einer bekannten Funktion übernehmen, wie main() und die Adresse eines bekannten globalen Variablen.

Nehmen Sie jetzt die Adresse des unbekannten Symbols und berechnen Sie den absoluten Wert der Differenz zwischen dieser Adresse und den anderen beiden. Der kleinste Unterschied zeigt an, dass die unbekannte Adresse näher an einer Funktion oder an einer globalen Variablen ist, was bedeutet, dass es sich wahrscheinlich um eine andere Funktion oder eine andere globale Variable handelt.

Diese Methode funktioniert unter der Annahme, dass der Compiler/Linker alle globalen Variablen in einen bestimmten Speicherblock und alle Funktionen in einen anderen Speicherblock packt. Microsoft Compiler, zum Beispiel, legte alle globalen Variablen vor (niedrigere Adressen im virtuellen Speicher) Funktionen.

Ich nehme an, Sie werden nicht bereit sein, für lokale Variablen zu überprüfen, wie Adresse, die nicht durch eine Funktion zurückgegeben werden (wenn die Funktion beendet ist, wird der lokale Variable ist verloren)

1

Dies kann durch Kombination von dlsym() und dladdr1() erfolgen.

#define _GNU_SOURCE 

#include <dlfcn.h> 
#include <link.h> 
#include <stdio.h> 

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; 
}