2009-05-14 11 views
11

Imagínese Tengo el siguiente programa en C simple:Printf la dirección actual en el programa C

int main() { 

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

Ahora, me gustaría saber la dirección de la expresión de c = a + b, que es la dirección de programa donde esta adición se lleva a cabo. ¿Hay alguna posibilidad de que pueda usar printf? Algo a lo largo de la línea:

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

sé cómo he podido encontrar la dirección a cabo mediante el uso de GDB y luego file.c línea de información: la línea. Sin embargo, debería saber si también podría hacer eso directamente con el printf.

+1

Sería útil saber qué arquitectura de procesador y compilador está utilizando. El consenso parece ser que no existe una forma realmente portátil de hacerlo. – RichieHindle

+0

¿no es posible que la 'instrucción' que está viendo en realidad se traduzca en una serie de instrucciones de ensamblaje, abarcando un rango de direcciones? – user44511

Respuesta

6

Visual C++ tiene la _ReturnAddress intrínseca, que se puede utilizar para obtener información aquí.

Por ejemplo:

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

que le dará una dirección cerca de la expresión que está viendo. En el caso de algunas optimizaciones, como el plegado de la cola, esto no será confiable.

23

En gcc, puede tomar la dirección de una etiqueta utilizando el operador & &. Por lo que podría hacer esto:

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

    sum: 
     c = a+b; 

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

El resultado de & & suma es el objetivo de la instrucción de salto que se emitiría si se hizo un goto sum. Por lo tanto, si bien es cierto que no existe una correspondencia uno a uno entre direcciones en C/C++, aún puede decir "obtener un puntero a este código".

+4

Esto parece ser una extensión no estándar –

+0

Vaya, tienes razón. Es solo gcc. – Charlie

+5

La pregunta en sí tiene todo tipo de aspectos no estándar. Es justo proporcionar una respuesta con extensiones no estándar. – sigjuice

2

Probado en Visual Studio 2008:

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

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

crédito a this question.

+4

Hacer esto interfiere con el predictor de dirección de retorno de la CPU ya que modificó la dirección de retorno no a través de un ret. http://blogs.msdn.com/oldnewthing/archive/2004/12/16/317157.aspx – Michael

+1

¿No sería mejor simplemente poner una etiqueta local, luego cargar la dirección de esa etiqueta con un MOV inmediato? eg: _aquí: mov eax, _aquí (newline) mov [addr], eax – bdonlan

+0

@bdonlan Creo que al usar una etiqueta como esa solo puedes obtener una compensación, no la dirección –

0

No conozco los detalles, pero debe haber una manera de hacer una llamada a una función que luego puede rastrear la pila de devolución para la dirección de la persona que llama, y ​​luego copiar e imprimir eso.

1

Aquí hay un bosquejo de un enfoque alternativo:

Supongamos que usted no ha despojado símbolos de depuración, y en particular que tiene el número de línea para hacer frente a la mesa que un depurador simbólico a nivel de fuente necesita con el fin de poner en práctica las cosas como un solo paso por línea de origen, establecer un punto de interrupción en una línea de origen, y así sucesivamente.

La mayoría de las cadenas de herramientas utilizan formatos de datos de depuración razonablemente bien documentados, y a menudo hay bibliotecas de ayuda que implementan la mayoría de los detalles.

Dado que hay algo de ayuda del macro del preprocesador __LINE__ que evalúa el número de línea actual, debería ser posible escribir una función que busque la dirección de cualquier línea fuente.

Las ventajas son que no se requiere ensamblaje, la portabilidad se puede lograr llamando a las bibliotecas de información de depuración específicas de la plataforma, y ​​no es necesario manipular directamente la pila o utilizar trucos que interrumpen la interconexión de la CPU.

Una gran desventaja es que será más lento que cualquier enfoque basado en la lectura directa del contador del programa.

1

Para 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

Usando gcc en 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; 
} 

en cuenta que debido a la presencia de las optimizaciones del compilador, la posición aparente de la expresión no podría corresponder a su posición en la fuente original.

La ventaja de utilizar este método sobre el & & foo etiqueta método es que no cambia el gráfico de control de flujo de la función. Tampoco rompe la unidad de predicción de retorno como los enfoques que utilizan call :) Por otra parte, depende mucho de la arquitectura ... y como no perturba al CFG, no hay garantía de que salte a la dirección en la pregunta tendría algún sentido.

+0

¿Estás seguro de que no cambia la CFG? Tengo entendido que un bloque __asm__ es una barrera del planificador, al igual que una etiqueta. – Charlie

+0

No importa, estaba pensando en 'asm volátil'. – Charlie

0

Si el compilador es bueno esta adición ocurre en los registros y nunca se almacena en la memoria, al menos no en la forma en que está pensando. En realidad, un buen compilador verá que su programa no hace nada, manipulando valores dentro de una función, pero nunca enviar esos valores a ninguna parte fuera de la función puede dar como resultado ningún código.

Si se va a:

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

Entonces, un buen compilador tampoco almacenará ese valor C en la memoria, permanecerá en los registros, aunque también depende del procesador. Si, por ejemplo, los compiladores de ese procesador utilizan la pila para pasar variables a funciones, entonces el valor de c se calculará usando registros (un buen compilador verá que C siempre es 11 y simplemente lo asignará) y el valor se colocará en la pila mientras se envía a la función printf. Naturalmente, la función de impresión puede necesitar almacenamiento temporal en la memoria debido a su complejidad (no cabe todo lo que necesita hacer en los registros).

A donde me dirijo es que no hay respuesta para su pregunta. Depende mucho del procesador, compilador, etc. No hay una respuesta genérica. Tengo que preguntarme cuál es la raíz de la pregunta, si esperabas sondear con un depurador, entonces esta no es la pregunta que debes hacer.

En pocas palabras, desmonte su programa y mírelo, para que compile ese día con esos ajustes, podrá ver dónde el compilador ha colocado valores intermedios. Incluso si el compilador asigna una ubicación de memoria para la variable, eso no significa que el programa alguna vez almacenará la variable en esa ubicación. Depende de las optimizaciones.