2011-10-19 19 views
7

Necesito depurar una fea y enorme biblioteca de matemática C, probablemente una vez producida por f2c. El código está abusando de local variables estáticas, y desafortunadamente en algún lugar parece explotar el hecho de que estas se inicializan automáticamente a 0. Si su función de entrada se llama con la misma entrada dos veces, está dando resultados diferentes. Si descargo la biblioteca y la vuelvo a cargar, funciona correctamente. Debe ser rápido, por lo que me gustaría deshacerme de la carga/descarga.rastrear variables estáticas no inicializadas

Mi pregunta es cómo descubrir estos errores con valgrind o con cualquier otra herramienta sin recorrer manualmente el código completo.

Estoy buscando lugares donde se declara una variable estática local, se lee primero y se escribe solo más tarde. El problema se complica aún más por el hecho de que las variables estáticas a veces se pasan a través de punteros (sí, es tan feo).

Entiendo que se puede argumentar que errores como este no deberían ser detectados por una herramienta automática, ya que en algunos casos, este es exactamente el comportamiento previsto. Aún así, ¿hay alguna forma de "sucias" las variables estáticas locales autoinicializadas?

+0

Pregunta interesante, pero valgrind no tiene conocimiento de "local" o "declarado". Creo que esto debe hacerse por análisis de código, no por análisis ejecutables. – aschepler

+0

usamos PC Lint, pero genera infinidad de advertencias, por lo que encontrar los puntos de acceso real a veces es como pescar en la oscuridad. –

Respuesta

5

el diablo está en los detalles, pero esto puede funcionar para usted:

En primer lugar, obtener Frama-C. Si está usando Unix, su distribución puede tener un paquete. El paquete no será la última versión, pero puede ser lo suficientemente bueno y le ahorrará algo de tiempo si lo instala de esta manera.

Digamos que su ejemplo es como abajo, solamente mucho más grande que no es obvio lo que está mal:

int add(int x, int y) 
{ 
    static int state; 
    int result = x + y + state; // I tested it once and it worked. 
    state++; 
    return result; 
} 

ingresa un comando como:

frama-c -lib-entry -main add -deps ugly.c 

Opciones -lib-entry -main add significa "mirar a la función add ". La opción -deps calcula dependencias funcionales. Encontrará estos "dependencias funcionales" en el registro:

[from] Function add: 
    state FROM state; (and default:false) 
    \result FROM x; y; state; (and default:false) 

Esta lista las entradas reales los resultados de add dependen, y los salidas reales calculados a partir de estas entradas, incluyendo las variables estáticas leídos desde y modificado Una variable estática que se inicializó correctamente antes de usarse normalmente no aparecería como entrada, a menos que el analizador no haya podido determinar que siempre se inicializó antes de que se leyera.

El registro muestra state como dependencia de \result. Si esperaba que el resultado devuelto dependiera únicamente de los argumentos (lo que significa que dos llamadas con los mismos argumentos producen el mismo resultado), es una pista de que puede haber algo mal aquí, con la variable state.

Otra sugerencia que se muestra en las líneas anteriores es que la función modifica state.

Esto puede ayudar o no. La opción significa que el analizador no supone que ninguna variable estática no constante haya conservado su valor en el momento en que se llama a la función bajo análisis, por lo que puede ser demasiado impreciso para su código. Hay formas de evitarlo, pero depende de usted si quiere apostar el tiempo necesario para aprender de esta manera.

EDIT: aquí es un ejemplo más complejo:

void initialize_1(int *p) 
{ 
    *p = 0; 
} 

void initialize_2(int *p) 
{ 
    *p; // I made a mistake here. 
} 

int add(int x, int y) 
{ 
    static int state1; 
    static int state2; 

    initialize_1(&state1); 
    initialize_2(&state2); 

    // This is safe because I have initialized state1 and state2: 
    int result = x + y + state1 + state2; 

    state1++; 
    state2++; 
    return result; 
} 

En este ejemplo, el mismo comando produce los resultados:

[from] Function initialize_1: 
     state1 FROM p 
[from] Function initialize_2: 
[from] Function add: 
     state1 FROM \nothing 
     state2 FROM state2 
     \result FROM x; y; state2 

Lo que se ve por initialize_2 es una lista vacía de dependencias, lo que significa que la función no asigna nada. Haré que este caso sea más claro al mostrar un mensaje explícito en lugar de solo una lista vacía. Si sabe lo que debe hacer cualquiera de las funciones initialize_1, initialize_2 o add, puede comparar este conocimiento a priori con los resultados del análisis y ver si algo está mal para initialize_2 y add.

SEGUNDA EDICION: y ahora mi ejemplo muestra algo extraño para initialize_1, así que quizás debería explicar eso. La variable state1 depende de p en el sentido de que p se usa para escribir en state1, y si p hubiera sido diferente, entonces el valor final de state1 hubiera sido diferente. Aquí hay un último ejemplo:

int t[10]; 

void initialize_index(int i) 
{ 
    t[i] = 1; 
} 

int main(int argc, char **argv) 
{ 
    initialize_index(argv[1][0]-'0'); 
} 

Con el comando frama-c -deps t.c, las dependencias computados para initialize_index son:

[from] Function initialize_index: 
     t[0..9] FROM i (and SELF) 

Esto significa que cada una de las células depende de i (puede ser modificado si i es el índice de esa celda en particular). Cada celda también puede mantener su valor (si i indica otra celda): esto se indica con la mención (and SELF) en la última versión, y se indicó con un (and default:true) más oscuro en versiones anteriores.

+0

¡Gracias! Esto suena útil; No voy a intentarlo, ya que ya he encontrado mi error con el método más primitivo a continuación. Por cierto, ¿pueden las variables de seguimiento de Frama-C pasar a través de punteros? En mi caso, las estáticas se envían y se pueden inicializar en una función diferente. – takbal

+0

@takbal Sí, puede hacerlo, pero en presencia de punteros, es posible que deba describir los estados de la memoria en los que se llaman las funciones con más precisión. Editaré mi respuesta con un ejemplo más completo. En http://frama-c.com/download/frama-c-value-analysis.pdf sección 2.5.2, este análisis se usa para verificar las dependencias de una función hash criptográfica completa, con punteros y todo (pero no el El ejemplo más llamativo es que el código está limpio y no hay mucho que decir. –

1

no sé de cualquier biblioteca que lo hace por usted, pero me gustaría ver en el uso de expresiones regulares para encontrarlos. Algo así como

rgrep "static \ s * int" path/to/src/root | grep -v = | grep -v "("

Eso debería devolver todas las variables estáticas int declaradas sin un signo igual, y la última canalización debe eliminar todo lo que esté entre paréntesis (deshacerse de las funciones). Hay un buen cambio que esto no trabaje exactamente para usted, pero jugar con grep puede ser la manera más rápida de rastrearlo.

Por supuesto, una vez que encuentre uno que funcione puede reemplazar int con todos los otros tipos de variables para buscar los que son demasiado. HTH

+0

Una pena, pero prácticamente todas las variables se declaran estáticas sin init ... probablemente sea la acción incorrecta de f2c. – takbal

0

Mi pregunta es que cómo descubrir estos errores ...

Pero estos no son errores: la expectativa de que una variable estática se inicie en 0 es perfectamente válida, ya que le está asignando algún otro valor.

Por lo tanto, para una herramienta que busque automáticamente no -errores es poco probable que produzca un resultado satisfactorio.

Desde su descripción, parece que vuelve somefunc() resultado correcto primera vez que se llama, y ​​el resultado incorrecto en llamadas posteriores.

La manera más simple de solucionar estos problemas es tener dos sesiones de GDB una al lado de la otra: una recién cargada (calculará la respuesta correcta) y una con "segunda iteración" (calculará la respuesta incorrecta). Luego, realice un paso a través de ambas sesiones "en paralelo", y vea dónde comienza a divergir su flujo de cálculo o control.

Dado que por lo general puede dividir eficazmente el problema a la mitad, a menudo no se necesita mucho tiempo para encontrar el fallo. Los errores que siempre reproducen son los más fáciles de encontrar. Solo hazlo.

+0

Como dije en la publicación original, entiendo que desde el punto de vista del compilador/enlazador no es un error. La pregunta es más bien cómo convertir este tipo de uso en un error que se puede detectar automáticamente. – takbal

1

herramientas de análisis de código estático son bastante buenos para encontrar errores de programación típicos como el uso de las variables sin inicializar.Here es una lista de herramientas gratuitas que hacen esto para C.

Lamentablemente, no puedo recomendar ninguna de las herramientas en la lista. Solo estoy familiarizado con dos productos comerciales, Coverity y Klocwork. Coverity es muy bueno (y costoso). Klocwork es tan (pero menos costoso).

+0

He intentado tablillar antes pero no dije nada útil. – takbal

+0

¿Por qué no acaba de inicializar todas las variables locales a cero cuando se declaran? ¿Es esto una gran base de código? – Miguel

+0

Sí, es enorme. Tienes razón, seguramente podría hacer un truco de expresiones regulares para convertir todas las definiciones en init basado en 0, o incluso NaN para dobles. Este es un camino que no he probado al final. – takbal

1

Lo que hice al final es eliminar todos los calificadores estáticos del código por '#define static'. Esto convierte el uso estático no inicializado en un uso no válido, y las herramientas pueden descubrir el tipo de abuso que estoy buscando.

En mi caso real esto fue suficiente para determinar el lugar del error, pero en una situación más general debería refinarse si las estáticas realmente están haciendo algo importante, volviendo a agregar gradualmente "estática" cuando el código falla continuar.

Cuestiones relacionadas