Instrumentación de funciones en C

20200526091908

Instrumentación de funciones en C

No podemos mejorar aquello que no podemos medir. Y con frecuencia, los programadores tenemos dificultades en medir el desempeño de nuestros programas en tiempo de ejecución. En este apunte, exploraremos una herramienta que puede ayudarnos a medir algunas características dinámicas de nuestro programa: caracterizar el stack, recurrencia, el árbol de llamadas entre funciones, etcétera.


Supongamos que tenemos una función llamada función objetivo. Y que además, para nuestro propio provecho como desarrolladores, queremos agregar dos funciones función prefijo y función sufijo que se ejecuten antes y después de la función objetivo. Hay una variedad de opciones para lograr este objetivo. En este documento nos enfocamos en las funciones de instrumentación. Otra forma de lograrlo podría ser con macros, o guardas de tipo ifdef.

Las funciones de instrumentación son dos funciones que desempeñan el papel de la función prefijo y función sufijo: __cyg_profile_func_enter y __cyg_profile_func_exit respectivamente. Ambas funciones reciben los mismos argumentos: un apuntador hacia la función objetivo, y un apuntador hacia la dirección desde donde la función objetivo fue llamada. Finalmente, para que el código funcione, tenemos que compilarlo con la bandera -finstrument-functions (para compiladores basados en gcc y clang).

Escribimos un ejemplo en el listado 1 que ilustra la declaración y el uso de la función prefijo y la función sufijo. El comando para compilar el programa (suponiendo que el código fuente se llama demo.c) está en el listado 2. Y la salida estándar del programa de demostración debería verse como el que está en el listado 3. Todos estos listados están al fondo de este texto.

Notemos que ambas declaraciones llevan consigo el modificador __attribute__((no_instrument_function)). Este modificador le indica al compilador que no debe instrumentar estas funciones. Si no colocáramos en las funciones prefijo y sufijo resultaría en un ciclo de recursión infinita (y muy probablemente haría que el programa fallara). En general, tenemos que agregar este sufijo a las funciones que no queremos instrumentar dentro de la misma unidad de compilación (conjunto de código fuente procesado en una corrida del compilador).

En un código con más de una función objetivo, las funciones prefijo y sufijo pueden hacer la distinción entre ellas con una sentencia switch en la que cada caso es la dirección de las funciones (contenida en su símbolo; en nuestro ejemplo, el caso se vería como case funcion_objetivo:).

#include <stdio.h>

// Declaración de la función prefijo
void __cyg_profile_func_enter(
    void * esta_funcion, 
    void * fue_llamada_desde_aqui
) __attribute__((no_instrument_function));

// Declaración de la función sufijo
void __cyg_profile_func_exit(
    void * esta_funcion, 
    void * fue_llamada_desde_aqui
)__attribute__((no_instrument_function));

int main()__attribute__((no_instrument_function));

void __cyg_profile_func_enter(
    void * esta_funcion, 
    void * fue_llamada_desde_aqui
)
{
    printf("--> Entrando a la funcion en %p desde %p\n", 
           esta_funcion, 
           fue_llamada_desde_aqui);
}

void __cyg_profile_func_exit(
    void * esta_funcion, 
    void * fue_llamada_desde_aqui
)
{
    printf("<-- Saliendo de la funcion en %p hacia %p\n", 
           esta_funcion, 
           fue_llamada_desde_aqui);
}

void funcion_objetivo()
{
    printf("funcion_objetivo (%p) se esta ejecutando.\n", funcion_objetivo);
}

int main()
{
    printf("La direccion de la funcion main es: %p.\n", main);

    funcion_objetivo();

    return 0;
}

Listado 1. Una aplicación con tres funciones instrumentadas: funcion_1, funcion_2, y funcion_3.


gcc ./demo.c -finstrument-functions -o demo

Listado 2. Comando para compilar el código del listado 1.


La direccion de la funcion main es: 0x55b9878401f8.
--> Entrando a la funcion en 0x55b9878401af desde 0x55b987840222
funcion_objetivo (0x55b9878401af) se esta ejecutando.
<-- Saliendo de la funcion en 0x55b9878401af hacia 0x55b987840222

Listado 3. Contenido de la salida estándar de nuestro programa.


Referencias

  1. "How to profile your own function calls in C." por Jacob Sorber.

Comentarios

Entradas más populares de este blog

10 palabras valen más que una imagen - 20240526223027

20220208192042 - Los modelos abstractos: sólo una cara del diamante

20220214085408 - El reto de hacer amistades nuevas en la vida adulta