Red de conocimiento informático - Material del sitio web - Cómo encontrar fallos a partir del código de destino y los rastreos de DDB

Cómo encontrar fallos a partir del código de destino y los rastreos de DDB

Revisado hoy...

Primero, necesita compilar su código fuente con código objeto que incluya símbolos de depuración.

En segundo lugar, el contenido del trazo inverso debe ser lo más completo posible.

Normalmente, los rastreos de DDB contendrán nombres de funciones pero no proporcionarán más información como nombres de variables, líneas de código fuente correspondientes, etc. DDB está diseñado para ser lo más simple posible para evitar provocar un bloqueo mientras se depura el kernel, lo que lleva a la incómoda situación de tener que tirar el kernel por la ventana y comenzar a depurar el depurador.

Otro impacto que no se puede ignorar es que las funciones de optimización del compilador C/C++ pueden provocar confusión en el código. En muchos casos, el código proporcionado por el compilador puede no ser sencillo. Las optimizaciones comunes incluyen las siguientes:

- Desenrollado del bucle. A veces, los bucles se desenrollan automáticamente para reducir el coste de la predicción de ramas del procesador.

- Expansión en línea. Las funciones en línea se pueden expandir cuando se llaman. Aunque las llamadas a funciones son "baratas", a veces el compilador expandirá las funciones en línea para evitar vaciados de caché innecesarios.

- Registrar uso. El compilador utilizará tantos registros como sea posible para evitar operaciones de memoria (costosas).

Estos harán que el estilo del código ensamblador sea muy diferente al de los metaprogramas C.

Por lo tanto, cuando la depuración dinámica no es posible, especialmente cuando solo hay rastreo DDB, se deben utilizar algunos métodos para localizar rápidamente el problema. A continuación presentaré parte de mi experiencia práctica, con la esperanza de inspirar a otros:

1. Obtenga el código ensamblador a través de objdump.

El método es objdump -D, lo cual es evidente.

2. Encuentre la ubicación del código (el rastreo de DDB le indicará el nombre de la función + la dirección de compensación).

Cuando usamos objdump para procesar archivos .o, podemos encontrar la dirección de entrada de la función y calcular la dirección después de agregar el desplazamiento.

3. Encuentre los valores característicos anteriores y siguientes en el código ensamblador.

Si está lo suficientemente familiarizado tanto con el compilador como con el código para compilar un programa C en código ensamblador a mano, ciertamente no necesita hacerlo. Si no tienes esta habilidad, podemos usar algunos trucos como encontrar valores propios.

En general, la mayoría de los accesos sin cabeza que ocurren en el kernel terminan con un acceso a la memoria fuera de los límites (trampa del kernel 12). El desencadenante más común de este tipo de problema es una definición de puntero no válida. Por ejemplo, el siguiente código %%%if (vp->vp_state & F_DONE) {

/* Haz algo */} Si vp contiene un valor no válido, el sistema fallará.

Ahora tenemos dos características: primero, generalmente podemos encontrar -> cerca de un colapso; segundo, este tipo de problemas tienden a ocurrir en la sentencia misma, o en las sentencias que rodean la sentencia. En el ejemplo anterior, & eventualmente se traducirá a la directiva one-test[bl].

Descubrí que el bloqueo se produjo efectivamente en ese punto, así que busqué la declaración if que contenía -> y & en el código (objdump encontró que había un je debajo, por lo que debe ser un declaración de sentencia).

Después de encontrar el segmento de declaración anterior, debe continuar mirando las declaraciones anteriores y posteriores.

Una vez determinada la ubicación, es más fácil crear o reproducir artificialmente el problema.

La siguiente pregunta es KASSERT y printf. Por supuesto, encontrar problemas de forma más rápida y precisa requiere mucha práctica.