2013-11-20 16 views
6

Sto implementando alcune funzionalità di debug remoto remoto per un'applicazione scritta in C in esecuzione su una macchina Linux. L'obiettivo è comunicare con l'applicazione e cercare il valore di una variabile arbitraria o eseguire una funzione arbitraria.È possibile determinare se un simbolo è una variabile o una funzione in C?

Sono in grado di cercare i simboli tramite le chiamate dlsym(), ma non sono in grado di determinare se l'indirizzo restituito si riferisce a una funzione o una variabile. C'è un modo per determinare le informazioni di battitura tramite questa tabella dei simboli?

+4

dipendente dalla piattaforma, ma si può ottenere via con 1. esaminando l'indirizzo (spazio), oppure 2. con la ricerca di una qualche funzione speciale a partire codice (trampolini, ecc) –

+0

o 3. Estrarre informazioni fuori delle informazioni di debug del DWARF, se disponibili (che non è banale) – nos

+0

Le informazioni di debug non sono disponibili per questa applicazione; l'applicazione è talmente grande che il tentativo di compilare con le informazioni di debug arresta qualsiasi cosa tenti di leggerlo (gdb) – dykeag

risposta

2

È possibile leggere il file /proc/self/maps e analizzare i primi tre campi di ogni riga:

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

Poi si cerca la riga che contiene l'indirizzo che si sta cercando e controllare le autorizzazioni:

  • r-x: è un codice;
  • rw-: si tratta di dati scrivibili;
  • r--: si tratta di dati di sola lettura;
  • qualsiasi altra combinazione: qualcosa di strano (rwxp: codice generato, ...).

Per esempio il seguente programma:

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

... nel mio sistema dà questo output:

0x400570 
0x6009e4 
0x7fff4c9b4e2c 

... e queste sono le linee interessate da /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] 
.... 

Quindi gli indirizzi sono: codice, dati e dati.

+1

Ottima risposta! Per chiarire per altri lettori, la prima colonna di numeri in '/ proc//maps' è un indirizzo _range_. Quindi, per determinare se un simbolo è una funzione, verifica se il puntatore si trova all'interno di un intervallo di indirizzi contrassegnati da 'x'. L'indirizzo di una variabile sarà in un intervallo non contrassegnato da 'x'. – dykeag

+0

@rodrigo puoi dirmi cosa fa il '% * s'? – phyrrus9

+0

@ phyrrus9: legge una stringa dallo standard input ('% s') ma la elimina senza salvarla da nessuna parte (' * '). Nota che la chiamata a 'scanf()' non ha parametri aggiuntivi. L'ho scritto per fermare il programma finché non viene premuto ENTER in modo che il file '/ proc//maps' possa essere letto. Alcune persone preferiscono usare 'getchar()' invece ... – rodrigo

3

Su piattaforme x86, è possibile verificare le istruzioni utilizzate per configurare lo stack per una funzione se è possibile esaminare il suo spazio indirizzo. E 'in genere:

push ebp 
mov ebp, esp 

io non sono positivo circa le piattaforme x64, ma penso che sia simile:

push rbp 
mov rbp, rsp 

This descrive la convenzione di chiamata C

Tenete a mente, tuttavia, compilatore le ottimizzazioni possono ottimizzare queste istruzioni. Se vuoi che funzioni, potresti dover aggiungere un flag per disabilitare questa ottimizzazione. Credo che per GCC, -fno-omit-frame-pointer farà il trucco.

+1

A meno che il codice non venga compilato senza ottimizzazioni, il puntatore del frame è probabilmente omesso laddove possibile. Quindi non sarebbe affidabile. –

+0

Oh, è vero. Sono sicuro che potrebbe disabilitare quella ottimizzazione però. Modificherò la mia risposta, grazie – chbaker0

2

Una possibile soluzione è estrarre una tabella di simboli per l'applicazione analizzando l'output di nm utility. nm include informazioni sul tipo di simbolo. I simboli con il tipo T (testo globale) sono funzioni.

Il problema di questa soluzione è che si deve garantire che il vostro tabella dei simboli corrisponde al bersaglio (soprattutto se avete intenzione di usarlo per estrarre gli indirizzi, anche se utilizzarlo in combinazione con dlsym() sarebbe più sicuro). Il metodo che ho usato per garantire che è quello di rendere la generazione della tabella dei simboli parte del processo di compilazione come una fase di post-elaborazione.

1

Credo che questo non è un metodo molto affidabile, ma potrebbe funzionare:

prendere l'indirizzo di una funzione ben noto, come ad esempio main() e l'indirizzo di una variabile globale ben noto.

Ora prendi l'indirizzo del simbolo sconosciuto e calcola il valore assoluto della differenza tra questo indirizzo e gli altri due. La differenza più piccola indicherà che l'indirizzo sconosciuto è più vicino a una funzione o a una variabile globale, il che significa che probabilmente è un'altra funzione o un'altra variabile globale.

Questo metodo funziona con il presupposto che il compilatore/linker impacchetterà tutte le variabili globali in un blocco di memoria specifico e tutte le funzioni in un altro blocco di memoria. Il compilatore Microsoft, ad esempio, inserisce tutte le variabili globali prima (gli indirizzi più bassi nella memoria virtuale).

ti sto assumendo non sarò disposto a verificare la presenza di variabili locali, come il cui indirizzo non può essere restituito da una funzione (una volta che la funzione termina, la variabile locale è perso)

1

È possibile combinare dlsym() e dladdr1().

#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; 
} 
Problemi correlati