¿Cuál fue su búsqueda de errores más difícil y cómo la encontró y la mató?


31

Esta es una pregunta de "Compartir el conocimiento". Estoy interesado en aprender de sus éxitos y / o fracasos.

Información que puede ser útil ...

Fondo:

  • Contexto: idioma, aplicación, entorno, etc.
  • ¿Cómo se identificó el error?
  • ¿Quién o qué identificó el error?
  • ¿Qué tan complejo fue reproducir el error?

La caza.

  • ¿Cuál fue tu plan?
  • ¿Qué dificultades encontraste?
  • ¿Cómo se encontró finalmente el código ofensivo?

El asesinato.

  • ¿Qué tan compleja fue la solución?
  • ¿Cómo determinó el alcance de la solución?
  • ¿Cuánto código estuvo involucrado en la solución?

Post mortem.

  • ¿Cuál fue la causa raíz técnicamente? desbordamiento del búfer, etc.
  • ¿Cuál fue la causa raíz de 30,000 pies?
  • ¿Cuánto tiempo tardó el proceso?
  • ¿Hubo alguna característica afectada negativamente por la solución?
  • ¿Qué métodos, herramientas, motivaciones encontró particularmente útiles? ... horriblemente inútil?
  • ¿Si pudieras hacerlo todo de nuevo? ............

Estos ejemplos son generales, no aplicables en todas las situaciones y posiblemente inútiles. Por favor sazone según sea necesario.

Respuestas:


71

En realidad, estaba en un subcomponente de visor de imágenes de terceros de nuestra aplicación.

Descubrimos que había 2-3 de los usuarios de nuestra aplicación que con frecuencia hacían que el componente del visor de imágenes lanzara una excepción y muriera horriblemente. Sin embargo, tuvimos docenas de otros usuarios que nunca vieron el problema a pesar de usar la aplicación para la misma tarea durante la mayor parte del día de trabajo. También hubo un usuario en particular que lo obtuvo con mucha más frecuencia que el resto de ellos.

Intentamos los pasos habituales:

(1) Los hizo cambiar de computadora con otro usuario que nunca tuvo el problema de descartar la computadora / configuración. - El problema los siguió.

(2) Hizo que iniciaran sesión en la aplicación y trabajaran como un usuario que nunca vio el problema. - El problema TODAVÍA los siguió.

(3) Hizo que el usuario informara qué imagen estaban viendo y configuró un arnés de prueba para repetir la visualización de esa imagen miles de veces en rápida sucesión. El problema no se presentó en el arnés.

(4) Un desarrollador se sentó con los usuarios y los observó todo el día. Vieron los errores, pero no se dieron cuenta de que estaban haciendo algo fuera de lo común para causarlos.

Luchamos con esto durante semanas tratando de descubrir qué tenían en común los "Usuarios de error" que los otros usuarios no tenían. No tengo idea de cómo, pero el desarrollador en el paso (4) tuvo un momento eureka en el disco para trabajar un día digno de Encyclopedia Brown.

Se dio cuenta de que todos los "Usuarios de error" eran zurdos, y confirmó este hecho. Solo los usuarios zurdos obtuvieron los errores, nunca los Righties. Pero, ¿cómo podría ser zurdo causar un error?

Le pedimos que se sentara y observara a los zurdos nuevamente, prestando atención específicamente a cualquier cosa que pudieran estar haciendo de manera diferente, y así es como lo encontramos.

Resultó que el error solo ocurrió si movió el mouse a la columna de píxeles más a la derecha en el visor de imágenes mientras cargaba una nueva imagen (error de desbordamiento porque el proveedor tenía un cálculo de 1 vez para el evento de mouseover).

Aparentemente, mientras esperaban que se cargara la siguiente imagen, todos los usuarios naturalmente movieron su mano (y por lo tanto el mouse) hacia el teclado.

El único usuario que recibió el error con mayor frecuencia fue uno de esos tipos de ADD que movieron compulsivamente su mouse con mucha impaciencia mientras esperaban que se cargara la siguiente página, por lo que movía el mouse hacia la derecha mucho más rápido y golpeaba el mouse. momento justo para que lo hiciera cuando ocurrió el evento de carga. Hasta que obtuvimos una solución del vendedor, le dijimos que soltara el mouse después de hacer clic (siguiente documento) y que no lo tocara hasta que se cargara.

De ahora en adelante se conocía en la leyenda del equipo de desarrollo como "The Left Handed Bug"


14
Esa es la cosa más desagradable que he oído hablar.
Nathan Taylor

99
Sin embargo, hizo un héroe del tipo que lo resolvió.
JohnFx

2
Wow, ahora que es un gran error!
Mitchel Sellers

3
Gran descubrimiento! Bonita historia.
Toon Krijthe

11
Como si los zurdos no fueran tratados suficientemente como ciudadanos de segunda clase. Ahora también tenemos que cargar con más que nuestra parte justa de errores de software ... ¡caramba, gracias! : p
Dan Molding

11

Esto es de hace mucho tiempo (finales de la década de 1980).

La compañía para la que trabajé escribió un paquete CAD (en FORTRAN) que se ejecutaba en varias estaciones de trabajo Unix (HP, Sun, Silcon Graphics, etc.). Utilizamos nuestro propio formato de archivo para almacenar los datos y, cuando se inició el paquete, el espacio en disco era escaso, por lo que se utilizaron muchos cambios de bits para almacenar varios indicadores en los encabezados de las entidades.

El tipo de entidad (línea, arco, texto, etc.) se multiplicó por 4096 (creo) cuando se almacenó. Además, este valor se negó para indicar un elemento eliminado. Entonces, para obtener el tipo, teníamos un código que sí:

type = record[1] MOD 4096

En cada máquina, excepto en una, esto dio ± 1 (para una línea), ± 2 (para un arco), etc., y luego pudimos verificar el signo para ver si se eliminó.

En una máquina (HP, creo), tuvimos un problema extraño en el que se arruinó el manejo de los elementos eliminados.

Esto fue en los días previos a los depuradores visuales y de IDE, así que tuve que insertar declaraciones de seguimiento y registros para intentar localizar el problema.

Eventualmente descubrí que era porque, mientras que todos los demás fabricantes implementaron MODlo que -4096 MOD 4096resultó en -1HP, lo implementó matemáticamente correctamente, lo que -4096 MOD 4096resultó en -4097.

Terminé teniendo que pasar por todo el código base guardando el signo del valor y haciéndolo positivo antes de realizar el MODy luego multiplicando el resultado por el valor del signo.

Esto tomó varios días.


3
Probablemente ha habido cazas de insectos más difíciles a lo largo de los años, ¡pero esta se me ha quedado grabada durante más de 20 años!
ChrisF

7

Wow, buena lectura aquí!

Lo más difícil fue hace años cuando Turbo Pascal era grande, aunque podría haber sido uno de los primeros IDE de C ++ de esa época. Como desarrollador exclusivo (y tercero en esta startup), había escrito algo así como un programa CAD simplificado para vendedores. Fue genial en ese momento, pero desarrolló un desagradable accidente aleatorio. Era imposible reproducirlo, pero sucedía con la frecuencia suficiente como para iniciar una búsqueda de errores.

Mi mejor estrategia fue dar un solo paso en el depurador. El error ocurrió solo cuando el usuario había ingresado suficiente dibujo y tal vez tenía que estar en un cierto modo o estado de zoom, por lo que hubo una gran cantidad de ajustes tediosos y puntos de interrupción, que se ejecutó normalmente durante un minuto para ingresar un dibujo, y luego paso a través de una gran porción de código. Fueron especialmente útiles los puntos de interrupción que se saltearían un número ajustable de veces y luego se romperían. Todo este ejercicio tuvo que repetirse varias veces.

Finalmente lo reduje a un lugar donde se llamaba a una subrutina, se le dio un 2 pero desde dentro vi un número de galimatías. Podría haber captado esto antes, pero no había entrado en esta subrutina, suponiendo que obtuviera lo que se le había dado. ¡Cegado al suponer que las cosas más simples estaban bien!

Resultó estar llenando un int 16 bits en la pila, pero la subrutina esperaba 32 bits. O algo así. El compilador no rellenó automáticamente todo el valor a 32 bits, ni realizó una verificación de tipo suficiente. Era trivial de arreglar, solo parte de una línea, casi no se requería ningún pensamiento. Pero llegar allí llevó tres días de caza y cuestionamiento de lo obvio.

Así que tengo experiencia personal con esa anécdota acerca de que entra el consultor caro, después de un tiempo hace un toque en alguna parte y cobra $ 2000. Los ejecutivos exigen un desglose, y es $ 1 por el grifo, $ 1999 por saber dónde tocar. Excepto en mi caso, era tiempo, no dinero.

Lecciones aprendidas: 1) use los mejores compiladores, donde "mejor" se define como la verificación de todos los problemas que la informática sabe cómo verificar, y 2) cuestiona las cosas simples y obvias, o al menos verifica su correcto funcionamiento.

Desde entonces, todos los errores difíciles han sido realmente difíciles, ya que sé comprobar las cosas simples más a fondo de lo que parece necesario.

La Lección 2 también se aplica al error electrónico más difícil que he solucionado, también con una solución trivial, pero varias EE inteligentes habían estado perplejas durante meses. Pero este no es un foro de electrónica, así que no diré más de eso.


¡Publique el error electrónico en otro lugar y un enlace aquí!
tgkprog

6

La condición de carrera de datos de red desde el infierno

Estaba escribiendo un cliente / servidor de red (Windows XP / C #) para trabajar con una aplicación similar en una estación de trabajo realmente antigua (Encore 32/77) escrita por otro desarrollador.

Lo que la aplicación hizo esencialmente fue compartir / manipular ciertos datos en el host para controlar el proceso del host que ejecuta el sistema con nuestra elegante interfaz de usuario con pantalla táctil de monitor múltiple basada en PC.

Lo hizo con una estructura de 3 capas. El proceso de comunicaciones leyó / escribió datos a / desde el host, realizó todas las conversiones de formato necesarias (endianness, formato de punto flotante, etc.) y escribió / leyó los valores a / desde una base de datos. La base de datos actuó como un intermediario de datos entre las comunicaciones y las IU de la pantalla táctil. La aplicación de la interfaz de usuario de la pantalla táctil generó interfaces de pantalla táctil en función de la cantidad de monitores conectados a la PC (esto lo detectó automáticamente).

En el marco de tiempo dado, un paquete de valores entre el host y nuestra PC solo podía enviar 128 valores máximos a través del cable a la vez con una latencia máxima de ~ 110 ms por viaje de ida y vuelta (UDP se usó con una conexión directa x-over ethernet entre las computadoras). Por lo tanto, el número de variables permitidas en función del número variable de pantallas táctiles adjuntas estaba bajo estricto control. Además, el host (aunque tenía una arquitectura multiprocesador bastante compleja con un bus de memoria compartida utilizado para la computación en tiempo real) tenía aproximadamente 1/100 de la potencia de procesamiento de mi teléfono celular, por lo que tenía la tarea de hacer el menor procesamiento posible y su servidor / client tuvo que escribirse en ensamblado para asegurar esto (el host estaba ejecutando una simulación en tiempo real que no podía verse afectada por nuestro programa).

El problema fue. Algunos valores, cuando se cambian en la pantalla táctil, no tomarían solo el valor recién ingresado, sino que se alternarían aleatoriamente entre ese valor y el valor anterior. Eso y solo en unos pocos valores específicos en unas pocas páginas específicas con una cierta combinación de páginas alguna vez exhibió el síntoma. Casi perdimos el problema por completo hasta que comenzamos a ejecutarlo a través del proceso inicial de aceptación del cliente


Para precisar el problema, elegí uno de los valores oscilantes:

  • Revisé la aplicación de pantalla táctil, estaba oscilando
  • Revisé la base de datos, oscilando
  • Revisé la aplicación de comunicaciones, oscilando

Luego rompí los cables y comencé a decodificar manualmente las capturas de paquetes. Resultado:

  • No oscila, pero los paquetes no se veían bien, había demasiados datos.

Revisé cada detalle del código de comunicaciones cientos de veces sin encontrar ningún defecto / error.

Finalmente comencé a enviar correos electrónicos al otro desarrollador preguntando en detalle cómo funcionaba su parte para ver si había algo que me faltaba. Entonces lo encontré.

Aparentemente, cuando envió datos, no eliminó el conjunto de datos antes de la transmisión, por lo que, esencialmente, estaba sobrescribiendo el último búfer utilizado con los nuevos valores sobrescribiendo los antiguos, pero los valores antiguos no sobrescritos aún se transmitían.

Entonces, si un valor estaba en la posición 80 de la matriz de datos y la lista de valores solicitados cambiaba a menos de 80 pero ese mismo valor estaba contenido en la nueva lista, entonces ambos valores existirían en el búfer de datos para ese búfer específico en cualquier tiempo dado.

El valor que se leía de la base de datos dependía del intervalo de tiempo en que la UI solicitaba el valor.


La solución fue dolorosamente simple. Lea el número de elementos entrantes en el búfer de datos (en realidad estaba contenido como parte del protocolo del paquete) y no lea el búfer más allá de ese número de elementos.


Lecciones aprendidas:

  • No dé por sentado la potencia informática moderna. Hubo un tiempo en que las computadoras no eran compatibles con Ethernet y cuando el vaciado de una matriz podía considerarse costoso. Si realmente desea ver hasta dónde hemos llegado, imagine un sistema que prácticamente no tiene forma de asignación de memoria dinámica. Es decir, el proceso ejecutivo tuvo que preasignar toda la memoria para todos los programas en orden y ningún programa podría crecer más allá de ese límite. Es decir, asignar más memoria a un programa sin volver a compilar todo el sistema podría causar un bloqueo masivo. Me pregunto si la gente hablará sobre los días previos a la recolección de basura bajo la misma luz algún día.

  • Al hacer redes con protocolos personalizados (o manejar la representación de datos binarios en general), asegúrese de leer las especificaciones hasta que comprenda cada función de cada valor que se envía a través de la tubería. Quiero decir, léelo hasta que te duelan los ojos. Las personas manejan datos manipulando bits o bytes individuales, tienen formas muy inteligentes y eficientes de hacer las cosas. Perder el más mínimo detalle podría romper el sistema.

El tiempo total para arreglarlo fue de 2 a 3 días y la mayoría de ese tiempo lo pasé trabajando en otras cosas cuando me sentí frustrado con esto.

Nota al margen: la computadora host en cuestión no era compatible con Ethernet de forma predeterminada. La tarjeta para manejarla fue hecha a medida y adaptada y la pila de protocolos prácticamente no existía. El desarrollador con el que estaba trabajando era un gran programador, no solo implementó una versión simplificada de UDP y una pila de Ethernet falsa mínima (el procesador no era lo suficientemente potente como para manejar una pila de Ethernet completa) en el sistema para este proyecto. pero lo hizo en menos de una semana. También había sido uno de los líderes del equipo original del proyecto que había diseñado y programado el sistema operativo en primer lugar. Digamos que cualquier cosa que haya tenido que compartir sobre computadoras / programación / arquitectura, sin importar cuánto tiempo me haya quedado boquiabierto o cuánto conozca, escucharía cada palabra.


5

El fondo

  • En una aplicación WCF de misión crítica que maneja un sitio web y proporciona procesamiento de backend.
  • Aplicación de gran volumen (cientos de llamadas por segundo)
  • Servidor múltiple instancias múltiples
  • cientos de pruebas unitarias aprobadas e innumerables ataques de control de calidad

El bicho

  • Cuando se trasladó a producción, el servidor funcionaría bien durante un período de tiempo aleatorio y luego comenzaría a degradarse rápidamente y llevaría la CPU de la caja al 100%.

Como lo encontre

Al principio estaba seguro de que este era un problema de rendimiento normal, así que creo un registro elaborado. El rendimiento verificado en cada llamada habló con las personas de la base de datos acerca de la utilización que observaron los servidores en busca de problemas. 1 semana

Entonces estaba seguro de que tenía un problema de contención de hilos. Verifiqué que mis puntos muertos intentaban crear la situación, crear herramientas para intentar crear la situación en depuración. Con la creciente frustración de la gerencia, recurrí a mis compañeros sobre cómo sugirieron cosas desde reiniciar el proyecto desde cero hasta limitar el servidor a un hilo. 1.5 semanas

Luego miré el blog de Tess Ferrandez, creé un archivo de volcado de usuario y lo analicé con windebug la próxima vez que el servidor hizo un volcado. Descubrí que todos mis hilos estaban atascados en la función dictionary.add.

El largo, el pequeño y pequeño diccionario que solo rastreaba en qué registro escribir los errores de hilos x no estaba sincronizado.


3

Teníamos una aplicación que estaba hablando con un dispositivo de hardware que, en algunos casos, no funcionaría correctamente si el dispositivo se desconectaba físicamente hasta que se volviera a enchufar y se reiniciara dos veces.

El problema resultó ser que una aplicación que se ejecutaba en el inicio ocasionalmente estaba fallando cuando intentaba leer desde un sistema de archivos que aún no se había montado (por ejemplo, si un usuario lo configuró para leer desde un volumen NFS). Al inicio, la aplicación enviaría algunos ioctls al controlador para inicializar el dispositivo, luego leería los ajustes de configuración y enviaría más ioctls para poner el dispositivo en el estado correcto.

Un error en el controlador causaba que se escribiera un valor no válido en el dispositivo cuando se realizó la llamada de inicialización, pero el valor se sobrescribió con datos válidos una vez que se realizaron las llamadas para poner el dispositivo en un estado específico.

El dispositivo en sí tenía una batería y detectaría si perdía energía de la placa base, y escribiría una bandera en la memoria volátil que indica que había perdido energía, luego entraría en un estado específico la próxima vez que se encendiera, y un Se necesita enviar instrucciones para borrar la bandera.

El problema era que si se cortaba la energía una vez que se enviaron los ioctls para inicializar el dispositivo (y se escribió el valor no válido en el dispositivo) pero antes de que se pudieran enviar datos válidos. Cuando el dispositivo se volviera a encender, vería que la bandera se había configurado e intentaría leer los datos no válidos que se habían enviado desde el controlador debido a la inicialización incompleta. Esto pondría el dispositivo en un estado no válido donde el indicador de apagado se había borrado, pero el dispositivo no recibiría más instrucciones hasta que el controlador lo reiniciara. El segundo reinicio significaría que el dispositivo no estaba tratando de leer los datos no válidos que se habían almacenado en él y recibiría las instrucciones de configuración correctas, lo que le permitiría ponerlo en el estado correcto (suponiendo que la aplicación que envía los ioctls no haya fallado) )

Al final, tardó unas dos semanas en descubrir el conjunto exacto de circunstancias que causaban el problema.


2

Para un proyecto universitario, estábamos escribiendo un sistema de nodos P2P distribuidos que compartía archivos, esto soportaba multidifusión para detectarse entre sí, múltiples anillos de nodos y un servidor de nombres para que un nodo sea asignado a un cliente.

Escrito en C ++, utilizamos POCO para esto, ya que permite una buena programación de IO, Socket y Thread.


Surgieron dos errores que nos molestaron y nos hicieron perder mucho tiempo, uno realmente lógico:

Aleatoriamente, una computadora estaba compartiendo su IP localhost en lugar de su IP remota.

Esto provocó que los clientes se conectaran al nodo en la misma PC o que los nodos se conectaran consigo mismos.

¿Cómo identificamos esto? Cuando mejoramos la salida en el servidor de nombres, descubrimos en un momento posterior cuando reiniciamos las computadoras que nuestro script para determinar que la IP para dar era incorrecta. Aleatoriamente, el dispositivo lo apareció primero en lugar del dispositivo eth0 ... Realmente estúpido. Así que ahora estamos codificados para solicitarlo de eth0, ya que esto se comparte entre todas las computadoras de la universidad ...


Y ahora uno más molesto:

Aleatoriamente, el flujo de paquetes se pausaría aleatoriamente.
Cuando el próximo cliente se conecte, continuará ...

Esto sucedió de manera aleatoria y, dado que más de una computadora está involucrada, se volvió más molesto depurar este problema, las computadoras de la universidad no nos permiten ejecutar Wireshark en ellas, por lo que no podemos adivinar si el problema estaba en el lado emisor o receptor lado.

Con una gran cantidad de resultados en el código, simplemente asumimos que enviar los comandos funciona bien,
esto nos dejó preguntándonos dónde estaba el verdadero problema ... Parecía que la forma en que POCO sondea es incorrecta y que en su lugar deberíamos verificar los caracteres disponibles en el zócalo entrante.

Asumimos que esto funcionó ya que las pruebas más simples en un prototipo que involucraba menos paquetes no causaron este problema, por lo que esto hizo que asumiéramos que la declaración de la encuesta funcionaba pero ... No lo fue. :-(


Lecciones aprendidas:

  • No haga suposiciones estúpidas como el orden de los dispositivos de red.

  • Los marcos no siempre hacen bien su trabajo (ya sea implementación o documentación).

  • Proporcione suficiente salida en el código; si no está permitido, asegúrese de registrar detalles extendidos en un archivo.

  • Cuando el código no ha sido probado por la unidad (porque es demasiado difícil), no asuma que las cosas funcionan.


1
Abordar problemas de red sin cables (o herramienta similar) es heroico en / de sí mismo.
Evan Plaice

2

Todavía estoy en mi búsqueda de errores más difícil. Es uno de esos que a veces está allí y otras no es un error. Por eso estoy aquí, a las 6:10 am del día siguiente.

Fondo:

  • Contexto: idioma, aplicación, entorno, etc.
    • PHP OS Commerce
  • ¿Cómo se identificó el error?
    • Los órdenes aleatorios que funcionan en parte, fallan aleatoriamente y redirigen problemas
  • ¿Quién o qué identificó el error?
    • Cliente, y el problema de redireccionamiento era obvio
  • ¿Qué tan complejo fue reproducir el error?
    • No he podido reproducir, pero el cliente ha podido.

La caza.

  • ¿Cuál fue tu plan?
    • Agregue código de depuración, complete el orden, analice datos, repita
  • ¿Qué dificultades encontraste?
    • Falta de problemas repetibles y código horrible
  • ¿Cómo se encontró finalmente el código ofensivo?
    • se encontró mucho código ofensivo ... simplemente no exactamente lo que necesitaba.

El asesinato.

  • ¿Qué tan compleja fue la solución?
    • muy
  • ¿Cómo determinó el alcance de la solución?
    • no había alcance ... estaba en todas partes
  • ¿Cuánto código estuvo involucrado en la solución?
    • ¿Todo ello? No creo que haya un archivo sin tocar

Post mortem.

  • ¿Cuál fue la causa raíz técnicamente? desbordamiento del búfer, etc.
    • mala práctica de codificación
  • ¿Cuál fue la causa raíz de 30,000 pies?
    • Yo preferiría no decir...
  • ¿Cuánto tiempo tardó el proceso?
    • por siempre y un día
  • ¿Hubo alguna característica afectada negativamente por la solución?
    • ¿característica? ¿O se trata de un error?
  • ¿Qué métodos, herramientas, motivaciones encontró particularmente útiles? ... horriblemente inútil?
  • ¿Si pudieras hacerlo todo de nuevo? ............
    • ctrl + a Del

Si la razón fue "mala práctica de codificación", es posible que desee discutir con su jefe si este es un buen momento para revisar las prácticas de codificación de su equipo, y tal vez presentar una revisión por pares.

2

Tuve que arreglar algunas cosas confusas de simultaneidad el último semestre, pero el error que aún más se destaca para mí fue en un juego basado en texto que estaba escribiendo en el ensamblaje PDP-11 para una tarea. Se basó en Conway's Game of Life y, por alguna extraña razón, una gran parte de la información al lado de la cuadrícula se sobrescribía constantemente con información que no debería haber estado allí. La lógica también era bastante sencilla, por lo que era muy confusa. Después de repasarlo varias veces para redescubrir que toda la lógica es correcta, de repente noté cuál era el problema. Esta cosa:.

En PDP-11, este pequeño punto al lado de un número lo convierte en base 10 en lugar de 8. Estaba al lado de un número que limitaba un bucle que se suponía que estaba limitado a la cuadrícula, cuyo tamaño se definía con los mismos números pero en base 8)

Todavía se destaca por la cantidad de daño que causó una adición tan pequeña de 4 píxeles. Entonces, ¿cuál es la conclusión? No codifique en el ensamblaje PDP-11.


2

El programa del marco principal dejó de funcionar sin motivo

Acabo de publicar esto en otra pregunta. Ver publicación aquí

Sucedió porque instalaron una versión más nueva del compilador en Main-Frame.

Actualización 06/11/13: (La respuesta original fue eliminada por OP)

Heredé esta aplicación de marco principal. Un día, de la nada, dejó de funcionar. Eso es todo ... poof se detuvo.

Mi trabajo consistía en hacerlo funcionar lo más rápido posible. El código fuente no había sido modificado durante dos años, pero de repente simplemente se detuvo. Traté de compilar el código y se rompió en la línea XX. Miré la línea XX y no podía decir qué haría que la línea XX se rompiera. Pedí las especificaciones detalladas para esta aplicación y no había ninguna. La línea XX no fue la culpable.

Imprimí el código y comencé a revisarlo de arriba hacia abajo. Comencé a crear un diagrama de flujo de lo que estaba sucediendo. El código era tan complicado que apenas podía entenderlo. Dejé de intentar hacer un diagrama de flujo. Tenía miedo de hacer cambios sin saber cómo ese cambio afectaría el resto del proceso, especialmente porque no tenía detalles de lo que hizo la aplicación.

Entonces, decidí comenzar en la parte superior del código fuente y agregar espacios en blanco y frenos de línea para que el código sea más legible. Me di cuenta, en algunos casos, de que había condiciones que combinaban AND y OR y no era claramente distinguible entre qué datos se estaban AND y qué datos se estaban OR. Entonces comencé a poner paréntesis alrededor de las condiciones AND y OR para hacerlas más legibles.

A medida que avanzaba lentamente para limpiarlo, periódicamente guardaba mi trabajo. En un momento intenté compilar el código y sucedió algo extraño. El error había pasado la línea de código original y ahora estaba más abajo. Así que continué, especializando las condiciones AND y OR con los padres. Cuando terminé de limpiarlo funcionó. Imagínate.

Entonces decidí visitar el taller de operaciones y preguntarles si habían instalado recientemente algún componente nuevo en el marco principal. Dijeron que sí, recientemente actualizamos el compilador. Hmmmm

Resulta que el antiguo compilador evaluó la expresión de izquierda a derecha independientemente. La nueva versión del compilador también evaluó expresiones de izquierda a derecha, pero el código ambiguo que significa una combinación poco clara de AND y OR no se pudo resolver.

Lección que aprendí de esto ... SIEMPRE, SIEMPRE, SIEMPRE use parens para separar las condiciones AND y las condiciones OR cuando se usan en conjunción entre sí.


se ha eliminado la publicación a la que apunta su enlace. ¿Le importaría actualizar la respuesta?
mosquito

1
@gnat - Lo encontré en archive.org :)
Michael Riley - AKA Gunny

1

Fondo:

  • Contexto: Servidor web (C ++) que permite a los clientes registrarse
  • Error: al solicitar la página, simplemente no respondería, toda la granja de servidores que es, y los procesos se matarían (y se volverían a lanzar) porque tomaron demasiado tiempo (solo se permiten unos segundos) para servir la página
  • Algunos usuarios se quejaron, pero fue extremadamente esporádico, por lo que pasó desapercibido (las personas tienden a presionar "Actualizar" cuando no se sirve una página). Sin embargo, notamos los volcados del núcleo;)
  • En realidad, nunca pudimos reproducirnos en nuestros entornos locales, el error apareció varias veces en los sistemas de prueba pero nunca apareció durante las pruebas de rendimiento.

La caza.

  • Plan: Bueno, como teníamos volcados y registros de memoria, queríamos analizarlos. Dado que estaba afectando a toda la granja y teníamos algunos problemas con las bases de datos en el pasado, sospechábamos de la base de datos (DB única para varios servidores)
  • Dificultades: un volcado de servidor completo es enorme, por lo que se borran con bastante frecuencia (para no quedarse sin espacio), por lo que tuvimos que ser rápidos para tomar uno cuando ocurrió ... Persistimos. Los volcados mostraron varias pilas (nunca nada de DB, tanto por eso), fallaron al preparar la página en sí (no en los cálculos anteriores) y confirmaron lo que mostraban los registros, preparar la página a veces tomaría mucho tiempo, incluso aunque es solo un motor de plantilla básico con datos precalculados (MVC tradicional)
  • Cómo llegar: después de algunas muestras más y de pensar un poco, nos dimos cuenta de que nos tomó tiempo leer los datos del HDD (la plantilla de página). Como se trataba de toda la granja, primero buscamos trabajos programados (crontab, lotes), pero los tiempos nunca coincidían de un evento a otro ... Finalmente se me ocurrió que esto siempre ocurría unos días antes de la activación de una nueva versión del software y tuve un AhAh! momento ... fue causado por la distribución del software! La entrega de varios cientos de megabytes (comprimidos) puede afectar el rendimiento del disco: / Por supuesto, la distribución es automática y el archivo se envía a todos los servidores a la vez (multidifusión).

El asesinato.

  • Fix Complexity: cambiar a plantillas compiladas
  • Código afectado: ninguno, un simple cambio en el proceso de compilación

Post mortem.

  • Causa principal: problema operativo o falta de planificación a futuro :)
  • Escala de tiempo: tardó meses en rastrear, una cuestión de días para arreglar y probar, unas pocas semanas para el control de calidad y las pruebas de rendimiento y la implementación, sin prisa, ya que sabíamos que la implementación de la solución desencadenaría el error ... y nada de lo contrario ... un poco pervertido realmente!
  • Efectos secundarios adversos: imposibilidad de cambiar las plantillas en tiempo de ejecución ahora que están integradas en el código entregado, sin embargo, no utilizamos mucho la función, ya que generalmente cambiar las plantillas significa que tiene más datos para verter. Usar CSS es mayormente suficiente para cambios de diseño "pequeños".
  • Métodos, herramientas: gdb+ monitoreo! Solo nos tomó tiempo sospechar del disco y luego identificar la causa de los picos de actividad en el gráfico de monitoreo ...
  • La próxima vez: ¡trate todos los IO como adversos!

1

El más difícil nunca fue asesinado porque nunca podría reproducirse más que en el entorno de producción completo con la fábrica en funcionamiento.

El más loco que maté:

¡Los dibujos están imprimiendo galimatías!

Miro el código y no puedo ver nada. Saco un trabajo de la cola de la impresora y lo examino, se ve bien. (Esto fue en la era DOS, PCL5 con HPGl / 2 incorporado; en realidad, muy bueno para trazar dibujos y sin dolores de cabeza por construir una imagen rasterizada en memoria limitada). Lo dirijo a otra impresora que debería entenderlo, imprime bien .

Revierta el código, el problema sigue ahí.

Finalmente, hago manualmente un archivo simple y lo envío a la impresora, galimatías. Resulta que no fue mi error en absoluto, sino la impresora en sí. La compañía de mantenimiento lo había actualizado a la última versión cuando estaban arreglando algo más y esa última versión tenía un error. Hacer que entendieran que habían eliminado la funcionalidad crítica y tuvieron que actualizarla a una versión anterior fue más difícil que encontrar el error en sí.

Uno que era aún más irritante, pero como solo estaba en mi caja, no lo pondría en primer lugar:

Borland Pascal, código DPMI para lidiar con algunas API no compatibles. Ejecútelo, ocasionalmente funcionó, por lo general se disparó tratando de lidiar con un puntero no válido. Sin embargo, nunca produjo un resultado incorrecto, como cabría esperar de pisotear un puntero.

Depuración: si paso por el código de una sola vez, siempre funcionará correctamente; de ​​lo contrario, sería tan inestable como antes. La inspección siempre mostró los valores correctos.

El culpable: había dos.

1) El código de la biblioteca de Borland tenía un error importante: los punteros en modo real se almacenaban en variables de puntero en modo protegido. El problema es que la mayoría de los punteros en modo real tienen direcciones de segmento no válidas en modo protegido y cuando intenta copiar el puntero lo carga en un par de registros y luego lo guarda.

2) El depurador nunca diría nada sobre una carga tan inválida en el modo de un solo paso. No sé qué hizo internamente, pero lo que se presentó al usuario parecía completamente correcto. Sospecho que en realidad no estaba ejecutando la instrucción, sino que la estaba simulando.


1

Este es solo un error muy simple que de alguna manera me convertí en una pesadilla.

Antecedentes: estaba trabajando en hacer mi propio sistema operativo. La depuración es muy difícil (las declaraciones de rastreo son todo lo que puede tener, y a veces ni siquiera eso)

Error: en lugar de hacer dos conmutadores de hilo en el modo de usuario, en su lugar sería un error de protección general.

La búsqueda de errores: pasé probablemente una o dos semanas tratando de solucionar este problema. Insertar declaraciones de seguimiento en todas partes. Examinando el código de ensamblaje generado (de GCC). Imprimiendo todos y cada uno de los valores que pude.

El problema: en algún momento temprano en la búsqueda de errores, había colocado una hltinstrucción en el crt0. El crt0 es básicamente lo que inicia un programa de usuario para su uso en un sistema operativo. Esta hltinstrucción provoca un GPF cuando se ejecuta desde el modo de usuario. Lo puse allí y básicamente lo olvidé. (originalmente, el problema era un desbordamiento del búfer o un error de asignación de memoria)

La solución: eliminar la hltinstrucción :) Después de eliminarlo, todo funcionó sin problemas.

Lo que aprendí: cuando intente depurar un problema, no pierda de vista las soluciones que intenta. Haga diferencias regulares con la última versión estable de control de fuente y vea lo que ha cambiado recientemente cuando nada más funciona

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.