2009-05-14 16 views
11

Immaginate Ho il seguente programma C semplice:Printf l'indirizzo corrente nel programma C

int main() { 

int a=5, b= 6, c; 
c = a +b; 
return 0; 
} 

Ora, vorrei conoscere l'indirizzo dell'espressione c = a + b, che è l'indirizzo del programma dove viene eseguita questa aggiunta. C'è qualche possibilità che io possa usare printf? Qualcosa lungo la linea:

int main() { 

int a=5, b= 6, c; 
printf("Address of printf instruction in memory: %x", current_address_pointer_or_something) 
c = a +b; 
return 0; 
} 

so come sono riuscito a trovare l'indirizzo con l'ausilio di gdb e poi informazioni linea file.c: riga. Tuttavia, dovrei sapere se potrei farlo anche direttamente con il printf.

+1

Sarebbe utile sapere quale architettura del processore e compilatore si sta utilizzando. Il consenso sembra essere che non esiste un modo veramente portatile per farlo. – RichieHindle

+0

non è possibile che l''istruzione' che stai guardando si traduca in realtà in una serie di istruzioni di assemblaggio, che coprono un intervallo di indirizzi? – user44511

risposta

6

Visual C++ ha l'intrinseco _ReturnAddress, che può essere utilizzato per ottenere alcune informazioni qui.

Per esempio:

__declspec(noinline) void PrintCurrentAddress() 
{ 
    printf("%p", __ReturnAddress); 
} 

che vi darà un indirizzo vicino al espressione si sta guardando. Nel caso di alcune ottimizzazioni, come il tail folding, questo non sarà affidabile.

23

In gcc, è possibile prendere l'indirizzo di un'etichetta utilizzando l'operatore & &. Così si potrebbe fare questo:

int main() 
{ 
    int a=5, b= 6, c; 

    sum: 
     c = a+b; 

    printf("Address of sum label in memory: %p", &&sum); 
    return 0; 
} 

Il risultato di & & somma è l'obiettivo della istruzione di salto che verrebbe emesso se avete fatto un goto sum. Quindi, anche se è vero che non esiste una mappatura da indirizzo a punto one-to-one in C/C++, puoi comunque dire "procurami un puntatore a questo codice".

+4

Questa sembra essere un'estensione non standard –

+0

Whoops, hai ragione. È solo gcc. – Charlie

+5

La domanda in sé ha tutti i tipi di aspetti non standard. È giusto fornire una risposta con estensioni non standard. – sigjuice

2

testato in Visual Studio 2008:

int addr; 
__asm 
{ 
    call _here 
    _here: pop eax 
    ; eax now holds the PC. 
    mov [addr], eax 
} 

printf("%x\n", addr); 

credito a this question.

+4

Questo interferisce con il predittore dell'indirizzo di ritorno della CPU poiché è stato modificato l'indirizzo di ritorno non tramite un ret. http://blogs.msdn.com/oldnewthing/archive/2004/12/16/317157.aspx – Michael

+1

Non sarebbe meglio solo inserire un'etichetta locale, quindi caricare l'indirizzo di quell'etichetta con un MOV immediato? es: _here: mov eax, _here (newline) mov [addr], eax – bdonlan

+0

@bdonlan Penso che usando un'etichetta del genere sia possibile ottenere solo un offset, non l'indirizzo –

0

Non conosco i dettagli, ma dovrebbe esserci un modo per effettuare una chiamata a una funzione che può quindi eseguire la scansione del pacco di reso per l'indirizzo del chiamante, quindi copiarlo e stamparlo.

1

Ecco uno schizzo di un approccio alternativo:

Si supponga che non si è spogliato simboli di debug, e, in particolare, si ha il numero di riga per affrontare tabella che un debugger simbolico a livello sorgente ha bisogno al fine di attuare le cose come un singolo passaggio per linea di origine, impostare un punto di interruzione su una linea di origine e così via.

La maggior parte delle catene di strumenti utilizza formati di dati di debug ragionevolmente ben documentati e spesso ci sono librerie di supporto che implementano la maggior parte dei dettagli.

Dato questo e un po 'di aiuto dalla macro del preprocessore __LINE__ che valuta il numero di riga corrente, dovrebbe essere possibile scrivere una funzione che cerca l'indirizzo di qualsiasi linea sorgente.

I vantaggi sono che non è richiesto alcun assemblaggio, la portabilità può essere ottenuta chiamando su librerie di informazioni di debug specifiche della piattaforma e non è necessario manipolare direttamente lo stack o utilizzare trucchi che interrompono la pipeline della CPU.

Un grande svantaggio è che sarà più lento di qualsiasi approccio basato sulla lettura diretta del contatore del programma.

1

Per x86:

int test() 
{ 
    __asm { 
     mov eax, [esp] 
    } 
} 


__declspec(noinline) int main() // or whatever noinline feature your compiler has 
{ 
    int a = 5; 
    int aftertest; 

    aftertest = test()+3; // aftertest = disasms to 89 45 F8 mov dword ptr [a],eax. 

    printf("%i", a+9); 
    printf("%x", test()); 
    return 0; 
} 
0

Uso gcc su i386 o x86-64:

#include <stdio.h> 

#define ADDRESS_HERE() ({ void *p; __asm__("1: mov 1b, %0" : "=r" (p)); p; }) 

int main(void) { 
    printf("%p\n", ADDRESS_HERE()); 
    return 0; 
} 

nota che a causa della presenza di ottimizzazioni, la posizione apparente dell'espressione potrebbe non corrispondere la sua posizione nella fonte originale.

Il vantaggio di utilizzare questo metodo sul metodo & & foo label non modifica il grafico del flusso di controllo della funzione. D'altra parte, è molto dipendente dall'architettura ... e poiché non perturba il CFG, non c'è alcuna garanzia che il salto nell'indirizzo in la domanda avrebbe alcun senso.

+0

Sei sicuro che non cambi il CFG? La mia comprensione è che un blocco __asm__ è una barriera dello scheduler, proprio come sarebbe un'etichetta. – Charlie

+0

Non importa, stavo pensando a "asm volatile". – Charlie

0

Se il compilatore è valido, questa aggiunta avviene nei registri e non viene mai memorizzata nella memoria, almeno non nel modo in cui si sta pensando. In realtà un buon compilatore vedrà che il tuo programma non fa nulla, manipolando i valori all'interno di una funzione, ma non mandare mai quei valori da nessuna parte al di fuori della funzione può portare a nessun codice.

Se si dovesse:

c = a + b; printf ("% u \ n", c);

Quindi un buon compilatore non memorizzerà mai quel valore C in memoria che rimarrà nei registri, sebbene dipenda anche dal processore. Se per esempio compilatori per quel processore usano lo stack per passare le variabili alle funzioni allora il valore per c sarà calcolato usando i registri (un buon compilatore vedrà che C è sempre 11 e lo assegna semplicemente) e il valore sarà messo in pila mentre viene inviato alla funzione printf. Naturalmente la funzione printf potrebbe necessitare di una memoria temporanea in memoria a causa della sua complessità (non può adattarsi a tutto ciò che deve fare nei registri).

Dove sto andando è che non c'è risposta alla tua domanda. È fortemente dipendente dal processore, dal compilatore, ecc. Non esiste una risposta generica. Devo chiedermi quale sia la radice della domanda, se speri di sondare con un debugger, allora questa non è la domanda da porre.

Bottom line, disassemblare il programma e guardarlo, per quella compilazione in quel giorno con quelle impostazioni, sarete in grado di vedere dove il compilatore ha posto valori intermedi. Anche se il compilatore assegna una posizione di memoria per la variabile che non significa che il programma memorizzerà mai la variabile in quella posizione. Dipende dalle ottimizzazioni.