¿Cómo encontrar la causa del fallo a partir del código de destino y el rastreo de DDB?
Primero, necesitas código objeto con símbolos de depuración compilados desde el código fuente.
En segundo lugar, el contenido del rastreo debe ser lo más completo posible.
En términos generales, el rastreo DDB contendrá el nombre de la función, pero no puede proporcionar más información, como nombres de variables, líneas de código fuente correspondientes, etc. El diseño de DDB es lo más simple posible para evitar fallas al depurar el kernel, lo que resulta en la situación embarazosa de tener que abandonar el kernel y comenzar a depurar el depurador.
Otro impacto que no se puede ignorar es la confusión de código que puede causar la función de optimización del compilador C/C++. Muchas veces, el código proporcionado por el compilador puede no ser tan sencillo y existen varias optimizaciones comunes:
-Desenrollado del bucle. A veces, los bucles se expanden automáticamente para reducir el costo de la predicción de bifurcaciones por parte 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 incorporará funciones de extensión para evitar efectos innecesarios de limpieza de caché.
-Uso de Registro. El compilador utilizará registros tanto como sea posible para evitar operaciones de memoria (costosas).
Estos harán que el estilo del código ensamblador sea muy diferente del estilo de los metaprogramas en C.
Entonces, cuando no puedes depurar dinámicamente, especialmente cuando solo puedes obtener rastreo DDB, necesitas algunos métodos para localizar rápidamente el problema. Permítanme presentarles algo de mi experiencia práctica, con la esperanza de atraer más atención:
1. Obtenga el código ensamblador a través de objdump.
El método es objdump -D, que se explica por sí mismo.
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 se utiliza objdump para procesar. En el archivo, podemos encontrar la dirección de entrada de la función y calcular la dirección correspondiente después de agregar el desplazamiento.
3. Encuentre los valores característicos antes y después del código ensamblador.
Si está lo suficientemente familiarizado con los compiladores y el código para compilar manualmente programas C en código ensamblador, ciertamente no necesita hacerlo. Sin esta técnica, podemos utilizar algunas técnicas, como encontrar valores propios.
En términos generales, la mayoría de los asesinatos sin cabeza en el kernel terminan con violaciones de acceso a la memoria (trampa del kernel 12). La condición desencadenante más común para este tipo de problema es la activación de un puntero no válido. Por ejemplo, el siguiente código: % % if(VP-->;vp_state & ampF_DONE) {
/*Haz algo*/} Si vp contiene un valor no válido, provocará un bloqueo.
Ahora tenemos dos características: primero, generalmente se puede encontrar cerca del borde colapsado -> segundo, este tipo de problema en sí mismo a menudo aparece en el juicio, o hay juicio a su alrededor. En el ejemplo anterior, & eventualmente se traducirá a la directiva test[bl].
Se descubre que el bloqueo es de hecho ese punto, por lo que el código de búsqueda contiene -> instrucción And & If (de objdump, se descubre que hay un je debajo, por lo que debe ser un declaración de juicio.
Después de encontrar el fragmento de oración anterior, continúe observando las oraciones antes y después.
Después de determinar la ubicación, es más fácil crear o reproducir artificialmente el problema.
El siguiente problema es KASSERT y printf. Por supuesto, si quieres encontrar problemas más rápido y con mayor precisión, necesitas mucha práctica.