debugging - perdido - ¿Cuál es el error más difícil que hayas encontrado y solucionado?
me alegro que hayas llegado bien (30)
¡Un punto muerto en mi primer programa de subprocesos múltiples!
Fue muy difícil encontrarlo porque sucedió en un grupo de subprocesos. De vez en cuando, un hilo en el grupo se estancaba, pero los otros aún funcionaban. Dado que el tamaño de la agrupación era mucho mayor de lo necesario, tardó una semana o dos en notarse el primer síntoma: la aplicación se colgó por completo.
¿Qué lo hizo difícil de encontrar? ¿Cómo lo localizaste?
No lo suficientemente cerca para cerrar pero ver también
https://stackoverflow.com/questions/175854/what-is-the-funniest-bug-youve-ever-experienced
Básicamente, cualquier cosa que involucre hilos.
Ocupé un puesto en una empresa en el que tuve la dudosa distinción de ser una de las pocas personas lo suficientemente cómodas para enhebrar problemas desagradables. El horror. Debería tener que obtener algún tipo de certificación antes de poder escribir código enhebrado.
Bryan Cantrill de Sun Microsystems dio una excelente Google Tech Talk sobre un error que rastreó utilizando una herramienta que ayudó a desarrollar llamada dtrace.
The Tech Talk es divertido, geek, informativo y muy impresionante (y largo , unos 78 minutos).
No daré ningún spoiler aquí sobre el error, pero él comienza a revelar al culpable alrededor de las 53:00.
Con FORTRAN en una minicomputadora de datos generales en los años 80, tuvimos un caso en el que el compilador causó que 1 (uno) constante se tratara como 0 (cero). Sucedió porque algún código antiguo pasaba una constante de valor 1 a una función que declaraba la variable como un parámetro FORTRAN, lo que significaba que (se suponía que era) inmutable. Debido a un defecto en el código, hicimos una asignación a la variable de parámetro y el compilador alegremente cambió los datos en la ubicación de memoria que utilizó para una constante de 1 a 0.
Muchas funciones no relacionadas después tuvimos código que hizo una comparación contra el valor literal 1 y la prueba fallaría. Recuerdo mirar el código por más tiempo en el depurador. Imprimiría el valor de la variable, sería 1 pero la prueba ''if (foo .EQ. 1)'' fallaría. Me tomó mucho tiempo antes de pensar en pedirle al depurador que imprimiera lo que pensaba que era el valor de 1. A continuación, tomó un montón de pelo para rastrear a través del código para encontrar cuando la constante 1 se convirtió en 0.
Cuando el conejito de mascota del cliente mordió la mitad del cable de ethernet. Sí. Estuvo mal.
El mío era un problema de hardware ...
De vuelta en el día, utilicé una DEC VaxStation con un gran monitor CRT de 21 ". Nos mudamos a un laboratorio en nuestro nuevo edificio e instalamos dos VaxStations en rincones opuestos de la sala. Al encenderse, mi monitor parpadeaba como una discoteca (Sí, fueron los 80), pero el otro monitor no.
De acuerdo, intercambia los monitores. El otro monitor (ahora conectado a mi VaxStation) parpadeó, y mi monitor anterior (movido a través de la sala) no lo hizo.
Recordé que los monitores basados en CRT eran susceptibles a los campos magnéticos. De hecho, eran muy susceptibles a campos magnéticos alternos de 60 Hz. Inmediatamente sospeché que algo en mi área de trabajo estaba generando un campo magnético alterno de 60 Hz.
Al principio, sospeché algo en mi área de trabajo. Desafortunadamente, el monitor todavía parpadeaba, incluso cuando todos los demás equipos se apagaban y desenchufaban. En ese momento, comencé a sospechar algo en el edificio.
Para probar esta teoría, convertimos la VaxStation y su monitor de 85 lb en un sistema portátil. Colocamos todo el sistema en un carrito plegable y lo conectamos a un cable de extensión naranja de 100 pies. El plan era utilizar esta configuración como un medidor de intensidad de campo portátil, con el fin de localizar la pieza ofensiva del equipo.
Rodar el monitor nos confundió por completo. El monitor parpadeó en exactamente la mitad de la habitación, pero no del otro lado. La habitación tenía forma de cuadrado, con puertas en esquinas opuestas, y el monitor parpadeaba en un lado de una línea diagnóstica que conectaba las puertas, pero no del otro lado. La habitación estaba rodeada por los cuatro lados por pasillos. Empujamos el monitor hacia los pasillos, y el parpadeo se detuvo. De hecho, descubrimos que el parpadeo solo ocurría en una mitad triangular de la sala, y en ningún otro lado.
Después de un período de total confusión, recordé que la habitación tenía un sistema de iluminación de techo de dos vías, con interruptores de luz en cada puerta. En ese momento, me di cuenta de lo que estaba mal.
Moví el monitor a la mitad de la sala con el problema y apagué las luces del techo. El parpadeo se detuvo. Cuando encendí las luces, el parpadeo se reanudó. Encendiendo o apagando las luces de cualquiera de los interruptores de la luz, se activó o se apagó el parpadeo dentro de la mitad de la habitación.
El problema fue causado por alguien que cortaba esquinas cuando conectaban las luces del techo. Al conectar un interruptor bidireccional en un circuito de iluminación, coloca un par de cables entre los contactos del interruptor SPDT, y un cable individual desde el común en un interruptor, a través de las luces y hasta el común en el otro interruptor.
Normalmente, estos cables están agrupados. Se van como un grupo de una caja de distribución, corren hacia el accesorio de techo y sobre la otra caja. La idea clave es que todos los cables que llevan corriente estén agrupados.
Cuando el edificio estaba conectado, el cable individual entre los interruptores y la luz se enrutaba a través del techo, pero los cables que viajaban entre los interruptores se enrutaban a través de las paredes.
Si todos los cables corrieran cerca y paralelos entre sí, entonces el campo magnético generado por la corriente en un cable fue cancelado por el campo magnético generado por la corriente igual y opuesta en un cable cercano. Desafortunadamente, la forma en que las luces estaban realmente conectadas significaba que la mitad de la habitación estaba básicamente dentro de un transformador principal de una sola vuelta. Cuando las luces estaban encendidas, la corriente fluía en un bucle, y el monitor pobre básicamente estaba sentado dentro de un gran electroimán.
Moraleja de la historia: las líneas calientes y neutras en el cableado de alimentación de CA están una al lado de la otra por una buena razón.
Ahora, todo lo que tenía que hacer era explicarle a la gerencia por qué tenían que volver a cablear parte de su nuevo edificio ...
El mensaje de Adam Liss sobre el proyecto en el que ambos trabajamos, me recordó un error divertido con el que tuve que lidiar. En realidad, no fue un error, pero llegaremos a eso en un minuto.
Resumen ejecutivo de la aplicación en caso de que aún no haya visto el mensaje de Adam: software de automatización de la fuerza de ventas ... en computadoras portátiles ... al final del día marcaron ... para sincronizar con la base de datos de Mother.
Un usuario se quejó de que cada vez que intentaba marcar, la aplicación se bloqueaba. La gente de soporte al cliente pasó por todos sus trucos de diagnóstico generalmente por teléfono, y no encontraron nada. Por lo tanto, tuvieron que ceder a lo último: tener el usuario FedEx la computadora portátil en nuestras oficinas. (Esto era muy importante, ya que la base de datos local de cada computadora portátil se personalizaba para el usuario, por lo que tenía que prepararse una nueva computadora portátil, enviarla al usuario para que la usara mientras trabajábamos en su original, luego teníamos que cambiar y haga que finalmente sincronice los datos en la primera computadora portátil original).
Entonces, cuando llegó la computadora portátil, me la dieron para resolver el problema. Ahora, la sincronización implicaba conectar la línea telefónica al módem interno, dirigirse a la página de "Comunicación" de nuestra aplicación y seleccionar un número de teléfono de una lista desplegable (con el último número utilizado preseleccionado). Los números en el DDL formaban parte de la personalización, y básicamente eran el número de la oficina, el número de la oficina con el prefijo "+1", el número de la oficina con el prefijo "9 ,,," en caso de que fueran llamando desde un hotel, etc.
Entonces, hago clic en el ícono "COMM" y presiono regresar. Marcó, se conectó a un módem y luego se colgó de inmediato. Me cansé un par de veces más. 100% de repetibilidad.
Entonces, se enganchó un alcance de datos entre la computadora portátil y la línea telefónica, y se miró la información que cruzaba la línea. Parecía bastante extraño ... ¡Lo más extraño era que podía leerlo!
Al parecer, el usuario quería usar su computadora portátil para marcar en un sistema BBS local, y por lo tanto, cambiar la configuración de la aplicación para usar el número de teléfono de BBS en lugar de la de la compañía. Nuestra aplicación esperaba nuestro protocolo binario patentado, no largos flujos de texto ASCII. Los búferes se desbordaron: ¡KaBoom!
El hecho de que un problema al marcar comenzó inmediatamente después de que cambió el número de teléfono, podría darle al usuario promedio una pista de que fue la causa del problema, pero este tipo nunca lo mencionó.
Arreglé el número de teléfono y se lo devolví al equipo de soporte, con una nota que lo eligió como el "usuario de Bonehead de la semana". (*)
(*) OkOkOk ... Probablemente hay una posibilidad muy buena de que el chico del chico, al ver a su padre marcar cada noche, pensara que así es como se conecta con BBS, y cambió el número de teléfono cuando estaba solo en casa con la computadora portátil. Cuando se estrelló, no quería admitir que había tocado la computadora portátil, y mucho menos romperla; así que simplemente lo guardó y no se lo contó a nadie.
El primero fue que nuestro producto lanzado exhibió un error, pero cuando intenté depurar el problema, no ocurrió. Al principio pensé que era una cuestión de "liberar contra depuración", pero incluso cuando compilé el código en modo de lanzamiento, no pude reproducir el problema. Fui a ver si algún otro desarrollador podía reproducir el problema. Nop. Después de mucha investigación (produciendo un código de ensamblaje mixto / listado de código C) de la salida del programa y pasando por el código de ensamblaje del producto lanzado (¡asco!), Encontré la línea ofensiva. ¡Pero la línea se veía bien para mí! Luego tuve que buscar lo que hicieron las instrucciones de ensamblaje y, por supuesto, las instrucciones de ensamblaje incorrectas estaban en el ejecutable liberado. Luego revisé el ejecutable que produjo mi entorno de compilación: tenía las instrucciones de ensamblaje correctas. Resultó que la máquina de compilación de alguna manera se corrompió y produjo un código de ensamblaje incorrecto para una sola instrucción para esta aplicación. Todo lo demás (incluidas las versiones anteriores de nuestro producto) produjo código idéntico a otras máquinas de desarrollo. Después de mostrar mi investigación al gerente de software, rápidamente reconstruimos nuestra máquina de construcción.
En algún lugar profundo de las entrañas de una aplicación en red estaba la línea (simplificada):
if (socket = accept() == 0)
return false;
//code using the socket()
¿Qué pasó cuando la llamada tuvo éxito? socket
se estableció en 1. ¿Qué hace send()
cuando se le da un 1? (como en:
send(socket, "mystring", 7);
Se imprime en stdout
... esto lo encontré después de 4 horas de preguntarme por qué, con todos mis printf()
sacados, mi aplicación estaba imprimiendo en la ventana de la terminal en lugar de enviar los datos a través de la red.
Escuché acerca de un error clásico en la escuela secundaria; una terminal en la que solo podía iniciar sesión si se sentaba en la silla frente a ella. (Rechazaría su contraseña si estuviera parado).
Se reprodujo bastante confiablemente para la mayoría de la gente; puede sentarse en la silla, iniciar sesión, cerrar sesión ... pero si se pone de pie, se le niega, todo el tiempo.
Eventualmente resultó que un imbécil había intercambiado un par de teclas adyacentes en el teclado, E / R y C / V IIRC, y cuando te sentabas, tecleabas y entrabas, pero cuando te parabas, tenías que cazar. n peck, entonces miraste las etiquetas incorrent y fallaste.
Esto está de vuelta cuando pensé que los relojes C ++ y digitales eran bastante limpios ...
Tengo la reputación de ser capaz de resolver fugas de memoria difíciles. Otro equipo tenía una filtración que no podían rastrear. Me pidieron que investigue.
En este caso, eran objetos COM. En el núcleo del sistema había un componente que daba muchos pequeños objetos COM que todos parecían más o menos iguales. Cada uno fue entregado a muchos clientes diferentes, cada uno de los cuales fue responsable de hacer AddRef()
y Release()
la misma cantidad de veces.
No había forma de calcular automáticamente quién había llamado a cada AddRef
, y si tenían Release
d.
Pasé unos días en el depurador, escribiendo direcciones hexadecimales en pequeños pedazos de papel. Mi oficina estaba cubierta con ellos. Finalmente encontré al culpable. El equipo que me pidió ayuda estaba muy agradecido.
Al día siguiente cambié a un lenguaje GC. *
(* No es cierto en realidad, pero sería un buen final para la historia).
Esto fue en Linux, pero podría haber sucedido en prácticamente cualquier sistema operativo. Ahora la mayoría de ustedes probablemente estén familiarizados con la API de socket BSD. Felizmente lo usamos año tras año, y funciona.
Estábamos trabajando en una aplicación masivamente paralela que tendría muchos enchufes abiertos. Para probar su funcionamiento, contamos con un equipo de prueba que abriría cientos y, a veces, más de mil conexiones para la transferencia de datos. Con los números de canal más altos, nuestra aplicación comenzaría a mostrar un comportamiento extraño. A veces simplemente se colgaba. La otra vez recibimos errores que simplemente no podían ser ciertos (por ej., Accept () devolviendo el mismo descriptor de archivo en llamadas subsecuentes que por supuesto resultaron en caos).
Pudimos ver en los archivos de registro que algo salió mal, pero fue increíblemente difícil de precisar. Las pruebas con Rational Purify dijeron que nada estaba mal. Pero algo estaba mal. Trabajamos en esto durante días y nos frustramos cada vez más. Fue un showblocker porque la prueba ya negociada causaría estragos en la aplicación.
Como el error solo se produjo en situaciones de carga elevada, verifiqué todo lo que hacíamos con los sockets. Nunca habíamos probado casos de carga elevada en Purify porque no era factible en una situación de memoria intensiva.
Finalmente (y afortunadamente) recordé que la gran cantidad de sockets podría ser un problema con select () que espera cambios de estado en los sockets (puede leer / puede escribir / error). Efectivamente, nuestra aplicación comenzó a causar estragos exactamente en el momento en que llegó al socket con el descriptor 1025. El problema es que select () funciona con parámetros de campo de bits. Los campos de bits se completan con macros FD_SET () y amigos que NO VERIFICAN SUS VALORES.
Así que cada vez que tenemos más de 1024 descriptores (cada OS tiene su propio límite, Linux vanilla kernels tiene 1024, el valor real se define como FD_SETSIZE), la macro FD_SET felizmente sobrescribirá su campo de bits y escribirá basura en la siguiente estructura en la memoria.
Reemplacé todas las llamadas select () con poll (), que es una alternativa bien diseñada para la llamada arcana select (), y las situaciones de carga alta nunca han sido un problema para siempre. Tuvimos suerte porque todo el manejo del socket estaba en una clase framework donde 15 minutos de trabajo podían resolver el problema. Hubiera sido mucho peor si las llamadas a select () hubieran sido rociadas sobre el código.
Lecciones aprendidas:
incluso si una función API tiene 25 años y todo el mundo la usa, puede tener rincones oscuros que aún no conoce
las anotaciones de memoria no verificadas en las macros de la API son MAL
una herramienta de depuración como Purify no puede ayudar en todas las situaciones, especialmente cuando se usa mucha memoria
Siempre tenga un marco para su aplicación si es posible. Usarlo no solo aumenta la portabilidad sino que también ayuda en caso de errores de API
muchas aplicaciones usan select () sin pensar en el límite del socket. Así que estoy bastante seguro de que puedes causar errores en MUCHOS programas populares simplemente usando muchos sockets. Afortunadamente, la mayoría de las aplicaciones nunca tendrán más de 1024 zócalos.
En lugar de tener una API segura, a los desarrolladores de sistemas operativos les gusta culpar al desarrollador. La página de manual de Linux select () dice
"El comportamiento de estas macros no está definido si el valor de un descriptor es menor que cero o mayor o igual que FD_SETSIZE, que normalmente es al menos igual al número máximo de descriptores admitidos por el sistema".
Eso es engañoso. Linux puede abrir más de 1024 zócalos. Y el comportamiento está absolutamente bien definido: el uso de valores inesperados arruinará la ejecución de la aplicación. En lugar de hacer que las macros sean resilientes a los valores ilegales, los desarrolladores simplemente sobrescriben otras estructuras. FD_SET se implementa como ensamblado en línea (!) En los encabezados de Linux y se evaluará en una sola instrucción de escritura del ensamblador. No es el más mínimo control de los límites que suceda en cualquier lugar.
Para probar su propia aplicación, puede inflar artificialmente la cantidad de descriptores utilizados al abrir mediante programación archivos o sockets FD_SETSIZE directamente después de main () y luego ejecutar su aplicación.
Thorsten79
Esto me sucedió en el momento en que trabajé en una tienda de informática.
Un cliente llegó un día a la tienda y nos dijo que su nueva computadora funcionaba bien por la noche y por la noche, pero no funciona en absoluto al mediodía o al final de la mañana. El problema fue que el puntero del mouse no se mueve en ese momento.
Lo primero que hicimos fue cambiar su mouse por uno nuevo, pero el problema no fue solucionado. Por supuesto, ambos ratones trabajaron en la tienda sin ningún defecto.
Después de varios intentos, encontramos que el problema era con esa marca y modelo de mouse en particular. La estación de trabajo del cliente estaba cerca de una ventana muy grande, y al mediodía el mouse estaba bajo la luz solar directa. Su plástico era tan delgado que en esas circunstancias, se volvió translúcido y la luz del sol impidió la rueda optomecánica para trabajar: |
Esto no me pasó a mí, pero un amigo me lo contó.
Tuvo que depurar una aplicación que se bloqueaba muy raramente. Solo fallaría los miércoles, en septiembre, después del noveno. Sí, 362 días del año, estaba bien, y tres días del año se colgaría inmediatamente.
Formatearía una fecha como "Miércoles, 22 de septiembre de 2008", pero el búfer tenía un carácter demasiado corto, por lo que solo causaría un problema cuando tuviera un DOM de 2 dígitos en un día con el nombre más largo del mes con el nombre más largo.
Esto requiere conocer un poco del ensamblador Z-8000, que explicaré sobre la marcha.
Estaba trabajando en un sistema integrado (en el ensamblador Z-8000). Una división diferente de la compañía estaba construyendo un sistema diferente en la misma plataforma, y había escrito una biblioteca de funciones, que también estaba usando en mi proyecto. El error era que cada vez que llamaba a una función, el programa se bloqueaba. Revisé todas mis entradas; Estaban bien. Tenía que ser un error en la biblioteca, excepto que la biblioteca había sido utilizada (y funcionaba bien) en miles de sitios de POS en todo el país.
Ahora, las CPU Z-8000 tienen 16 registros de 16 bits, R0, R1, R2 ... R15, que también se pueden direccionar como 8 registros de 32 bits, denominados RR0, RR2, RR4..RR14, etc. La biblioteca fue escrita desde cero, refactorizando un grupo de bibliotecas antiguas. Estaba muy limpio y seguía estrictos estándares de programación. Al comienzo de cada función, cada registro que se usaría en la función se insertó en la pila para preservar su valor. Todo estaba limpio y ordenado, eran perfectos.
Sin embargo, estudié la lista de ensambladores para la biblioteca, y noté algo extraño sobre esa función --- Al comienzo de la función, tenía PUSH RR0 / PUSH RR2 y al final tenía POP RR2 / POP R0. Ahora, si no seguiste eso, empujó 4 valores en la pila al comienzo, pero solo eliminó 3 de ellos al final. Esa es una receta para el desastre. Hay un valor desconocido en la parte superior de la pila donde debe estar la dirección de retorno. La función no podría funcionar.
Excepto, puedo recordarte que estaba FUNCIONANDO. Fue llamado miles de veces al día en miles de máquinas. Posiblemente NO podría funcionar.
Después de un tiempo de depuración (que no era fácil en ensamblador en un sistema integrado con las herramientas de mediados de la década de 1980), siempre se bloqueaba en la devolución, porque el valor malo era enviarlo a una dirección aleatoria. Evidentemente tuve que depurar la aplicación en funcionamiento, averiguar por qué no falló.
Bueno, recuerda que la biblioteca era muy buena para preservar los valores en los registros, así que una vez que colocaste un valor en el registro, permanecía allí. R1 tenía 0000 en él. Siempre tendría 0000 cuando se llamó a esa función. Por lo tanto, el error dejó 0000 en la pila. Entonces, cuando la función retornaba, saltaba a la dirección 0000, que resultaba ser un RET, que sacaba el siguiente valor (la dirección de retorno correcta) de la pila, y saltaba a eso. Los datos enmascararon perfectamente el error.
Por supuesto, en mi aplicación, tenía un valor diferente en R1, por lo que simplemente se bloqueó ...
Fue durante mi tesis de diploma. Estaba escribiendo un programa para simular el efecto del láser de alta intensidad en un átomo de helio usando FORTRAN.
Una prueba de ejecución funcionó así:
- Calcule el estado cuántico inicial usando el programa 1, alrededor de 2 horas.
- ejecutar la simulación principal en los datos del primer paso, para los casos más simples de alrededor de 20 a 50 horas.
- luego analiza el resultado con un tercer programa para obtener valores significativos como energía, tork, momentum
Estos deberían ser constantes en total, pero no lo eran. Hicieron todo tipo de cosas raras.
Después de depurar durante dos semanas me volví loco en el registro y registré cada variable en cada paso de la simulación, incluidas las constantes.
¡De esa manera descubrí que escribí sobre un extremo de una matriz, que cambió una constante !
Un amigo dijo que una vez cambió el literal 2 con un error semejante.
Los dos errores más difíciles que se me ocurren son los del mismo tipo de software, solo uno estaba en la versión web y uno en la versión de Windows.
Este producto es un visor / editor de plano. La versión basada en web tiene una interfaz flash que carga los datos como SVG. Ahora, esto funcionaba bien, solo que a veces el navegador se bloqueaba. Solo en algunos dibujos, y solo cuando movía el mouse sobre el dibujo por un momento. Reduje el problema a una sola capa de dibujo, que contiene 1.5 MB de datos SVG. Si tomé solo una subsección de los datos, cualquier subsección, no se colgó. Eventualmente me di cuenta de que el problema probablemente era que había varias secciones diferentes en el archivo que en combinación causaron el error. Efectivamente, después de eliminar al azar secciones de la capa y probar el error, encontré la combinación ofensiva de instrucciones de dibujo. Escribí una solución en el generador de SVG, y el error se solucionó sin cambiar una línea de actionscript.
En el mismo producto en el lado de Windows, escrito en Delphi, tuvimos un problema comparable. Aquí el producto toma archivos autocad DXF, los importa a un formato de dibujo interno y los representa en un motor de dibujo personalizado. Esta rutina de importación no es particularmente eficiente (utiliza una gran cantidad de copias de subcadenas), pero hace el trabajo bien. Solo que en este caso no fue así. Un archivo de 5 megabytes generalmente importa en 20 segundos, pero en un archivo tomó 20 minutos, porque la huella de memoria se disparó a un gigabyte o más. Al principio parecía una fuga de memoria típica, pero las herramientas de pérdida de memoria informaron que estaba limpia, y la inspección manual del código tampoco resultó nada. El problema resultó ser un error en el asignador de memoria de Delphi 5. En algunas condiciones, que este archivo en particular estaba recreando debidamente, sería propenso a la fragmentación de la memoria grave. El sistema seguiría tratando de asignar cadenas de caracteres grandes y no encontraría dónde colocarlas, excepto por encima del bloque de memoria asignado más alto. La integración de una nueva biblioteca de asignación de memoria corrigió el error, sin cambiar una línea de código de importación.
Pensando en el pasado, los errores más difíciles parecen ser aquellos cuya solución implica cambiar una parte diferente del sistema que aquella en la que ocurre el problema.
Mi equipo heredó una aplicación web C ++ de subprocesos múltiples basada en CGI. La plataforma principal era Windows; una plataforma distante y secundaria era Solaris con hilos Posix. La estabilidad en Solaris fue un desastre, por alguna razón. Tuvimos varias personas que analizaron el problema durante más de un año, de vez en cuando (en su mayoría fuera), mientras que nuestro personal de ventas presionó con éxito la versión de Windows.
El síntoma fue una estabilidad patética: una amplia gama de fallos del sistema con poca rima o razón. La aplicación utilizó tanto Corba como un protocolo nacional. Un desarrollador llegó a eliminar todo el subsistema Corba como una medida desesperada: sin suerte.
Finalmente, un desarrollador sénior y original se preguntó en voz alta sobre una idea. Lo investigamos y finalmente encontramos el problema: en Solaris, había un parámetro en tiempo de compilación (¿o tiempo de ejecución?) Para ajustar el tamaño de la pila para el ejecutable. Fue configurado incorrectamente: demasiado pequeño. Por lo tanto, la aplicación se estaba quedando sin pila e imprimiendo trazas de pila que eran pistas falsas.
Fue una verdadera pesadilla.
Lecciones aprendidas:
- Lluvia de ideas, lluvia de ideas, lluvia de ideas
- Si algo se está volviendo loco en una plataforma diferente y descuidada, probablemente sea un atributo de la plataforma de entorno
- Tenga cuidado con los problemas que se transfieren de los desarrolladores que abandonan el equipo. Si es posible, póngase en contacto con las personas anteriores en forma personal para recopilar información y antecedentes. Ruego, suplique, haga un trato. La pérdida de experiencia se debe minimizar a toda costa.
Mi primer trabajo "real" fue para una empresa que escribió software de automatización de fuerza de ventas cliente-servidor. Nuestros clientes ejecutaron la aplicación cliente en sus computadoras portátiles (de 15 libras), y al final del día marcaron en nuestros servidores Unix para sincronizar con la base de datos Mother. Después de una serie de quejas, descubrimos que un número astronómico de llamadas caía desde el principio, durante la autenticación.
Después de semanas de depuración, descubrimos que la autenticación siempre falla si la llamada entrante fue respondida por un proceso getty en el servidor cuyo ID de proceso contenía un número par seguido inmediatamente de un 9. Resulta que la autenticación era un esquema homebrew que dependía de un Representación de cadena de 8 caracteres del PID; un error provocó que un PID ofensivo bloqueara el getty, que reapareció con un nuevo PID. La segunda o tercera llamada solía encontrar un PID aceptable, y la rellamada automática hacía innecesario que los clientes intervinieran, por lo que no se consideró un problema significativo hasta que las facturas del teléfono llegaron a fin de mes.
El "arreglo" (ejem) era convertir el PID a una cadena que representa su valor en octal en lugar de decimal, lo que hace que sea imposible contener un 9 e innecesario para abordar el problema subyacente.
Mientras probaba algunas funcionalidades nuevas que agregué recientemente a una aplicación comercial, me di cuenta de que el código para mostrar los resultados de cierto tipo de comercio nunca funcionaría correctamente. Después de observar el sistema de control de fuente, era obvio que este error había existido durante al menos un año, y me sorprendió que ninguno de los operadores lo hubiera visto nunca.
Después de desconcertarme por un tiempo y consultar con un colega, arreglé el error y seguí probando mi nueva funcionalidad. Aproximadamente 3 minutos después, sonó mi teléfono. En el otro extremo de la línea estaba un comerciante iracundo que se quejaba de que uno de sus oficios no se mostraba correctamente.
Tras una investigación más profunda, me di cuenta de que el comerciante había sido golpeado con el mismo error exacto que había notado en el código 3 minutos antes. Este error había estado rondando por un año, esperando que un desarrollador apareciera para poder atacar de verdad.
Este es un buen ejemplo de un tipo de error conocido como Schroedinbug . Si bien la mayoría de nosotros hemos oído hablar de estas entidades peculiares, es una sensación extraña cuando en realidad se encuentra con uno en la naturaleza.
No es muy difícil, pero me reí mucho cuando fue descubierto.
Cuando mantenía un sistema de procesamiento de pedidos 24/7 para una tienda en línea, un cliente se quejó de que su pedido había sido "truncado". Afirmó que si bien la orden que colocó en realidad contenía N posiciones, el sistema aceptó muchas menos posiciones sin ningún tipo de advertencia.
Después de rastrear el flujo de órdenes a través del sistema, se revelaron los siguientes hechos. Había un procedimiento almacenado responsable de almacenar los artículos de pedido en la base de datos. Aceptó una lista de elementos de pedido como cadena, que codificó la lista de triples (product-id, quantity, price)
esta manera:
"<12345, 3, 19,99> <56452, 1, 8,99> <26586, 2, 12,99>"
Ahora, el autor del procedimiento almacenado era demasiado inteligente para recurrir a cualquier tipo de análisis y bucle ordinarios. Por lo tanto, transformó directamente la cadena en instrucción SQL de inserción múltiple al reemplazar "<"
por "insert into ... values ("
y ">"
con ");"
. ¡Lo cual estuvo bien y muy bien, si solo no almacenara la cadena resultante en una variable varchar (8000)!
Lo que sucedió es que su "insert ...; insert ...;"
se truncó en el carácter 8000a y para ese orden en particular, el corte tuvo la "suerte" de suceder entre las insert
, de modo que el SQL truncado permaneció sintácticamente correcto .
Más tarde descubrí que el autor de sp era mi jefe.
Nuestra interfaz de red, una tarjeta ATM con capacidad para DMA, ocasionalmente entrega datos corruptos en los paquetes recibidos. El AAL5 CRC había verificado que era correcto cuando el paquete salió del cable, pero los datos DMAd en la memoria serían incorrectos. La suma de comprobación de TCP generalmente la atraparía, pero en los vertiginosos días de la gente de ATM se entusiasmó con la ejecución de aplicaciones nativas directamente en AAL5, prescindiendo por completo de TCP / IP. Eventualmente notamos que la corrupción solo ocurría en algunos modelos de la estación de trabajo del vendedor (quienes no tendrán nombre), no en otros.
Al calcular el CRC en el software del controlador pudimos detectar los paquetes dañados, a costa de un gran golpe de rendimiento. Al intentar depurar nos dimos cuenta de que si almacenamos el paquete por un tiempo y volvimos a verlo más tarde, la corrupción de los datos se curaría mágicamente. El contenido del paquete estaría bien, y si el conductor calculaba el CRC por segunda vez vería ok.
Encontramos un error en la memoria caché de datos de una CPU de envío. La memoria caché en este procesador no era coherente con DMA, lo que requiere que el software expulse explícitamente en los momentos adecuados. El error era que a veces el caché no enjuagaba su contenido cuando se lo ordenaban.
Pasé horas o días depurando una cantidad de cosas que terminaron siendo reparables con literalmente solo un par de caracteres.
Algunos ejemplos diversos:
ffmpeg tiene esta desagradable costumbre de producir una advertencia sobre el "recorte brainfart" (refiriéndose a un caso en el que los valores de recorte in-stream son> = 16) cuando los valores de recorte en la secuencia eran realmente perfectamente válidos. Lo arreglé agregando tres caracteres: "h->".
x264 tenía un error donde en casos extremadamente raros (uno en un millón de cuadros) con ciertas opciones producía un bloque al azar de un color completamente equivocado. Arreglé el error agregando la letra "O" en dos lugares del código. Resultó que había mal escrito el nombre de un #define en un compromiso anterior.
Si bien no recuerdo una instancia específica, la categoría más difícil son los errores que solo se manifiestan después de que el sistema ha estado funcionando durante horas o días, y cuando se apaga, deja poca o ninguna pista de lo que causó el bloqueo. Lo que los hace particularmente malo es que no importa qué tan bien creas que has razonado la causa, y has aplicado la solución adecuada para remediarlo, tendrás que esperar otras pocas horas o días para tener confianza en todo lo que Realmente lo he clavado.
Solo quiero señalar un error bastante común y desagradable que puede ocurrir en este momento del área de Google:
código de pegar y el infame menos
Es entonces cuando copia y pega un código con un minus , en lugar de un carácter ASCII normal guión-menos (''-'') .
Además, menos (U + 2212), Guión-Menos (U + 002D)
Ahora, aunque el signo menos se supone que es más largo que el guión negativo, en ciertos editores (o en una ventana de shell de DOS), dependiendo del juego de caracteres utilizado, se representa como un signo ''-'' de guión negativo regular.
Y ... puede pasar horas tratando de entender por qué este código no se compila, eliminando cada línea una a una, ¡hasta que encuentre la causa real!
Puede que no sea el error más difícil, pero es bastante frustrante;)
(Gracias ShreevatsaR por detectar la inversión en mi publicación original - ver comentarios)
Tenía un error en una plataforma con un depurador muy malo en el dispositivo. Obtendríamos un bloqueo en el dispositivo si agregamos un printf al código. Luego se estrellaría en un punto diferente de la ubicación del printf. Si moviéramos el printf, el choque se movería o desaparecería . De hecho, si cambiamos ese código reordenando algunas declaraciones simples, el colapso ocurriría en algún lugar sin relación con el código que cambiamos.
Resultó que había un error en el reubicador de nuestra plataforma. el reubicador no estaba iniciando cero en la sección ZI, sino que usaba la tabla de reubicación para inicializar los valores. Entonces, cada vez que la tabla de reubicación cambiaba en el binario, el error se movía. Así que simplemente agregó un printf cambiaría la tabla de reubicación y allí el error.
Tuve un error en un juego de consola que ocurrió solo después de que peleaste y ganaste una larga batalla de jefes, y luego solo alrededor de 1 vez en 5. Cuando se disparaba, dejaba el hardware 100% acuñado e incapaz de hablar con el mundo exterior en absoluto.
Fue el error más tímido que he encontrado en mi vida; modificar, automatizar, instrumentar o depurar la batalla del jefe ocultaría el error (y por supuesto tendría que hacer 10-20 ejecuciones para determinar que el error se había ocultado).
Al final encontré el problema (una cuestión de caché / DMA / interrupción) leyendo el código una y otra vez durante 2-3 días.
Un analizador de jpeg, que se ejecuta en una cámara de vigilancia, que se estrelló cada vez que el CEO de la compañía entró en la sala.
100% de error reproducible
¡No es broma!
Esta es la razón por:
Para usted, que no sabe mucho acerca de la compresión JPEG, la imagen se divide en una matriz de pequeños bloques que luego se codifican utilizando magia, etc.
El analizador se ahogó cuando el CEO entró en la sala, porque siempre tenía una camisa con un patrón cuadrado, lo que desencadenó algunos casos especiales de algoritmos de contraste y bloqueo de límites.
Verdaderamente clásico.
Un error donde encuentras algún código, y después de estudiarlo concluyes, "¡No hay forma de que esto haya funcionado alguna vez!" y de repente deja de funcionar aunque siempre funcionó antes.
Uno de los productos que ayudé a construir en mi trabajo se ejecutaba en el sitio de un cliente durante varios meses, recopilando y grabando felizmente cada evento que recibía en una base de datos de SQL Server. Funcionó muy bien durante aproximadamente 6 meses, recogiendo aproximadamente 35 millones de registros más o menos.
Entonces, un día, nuestro cliente nos preguntó por qué la base de datos no se había actualizado durante casi dos semanas. Tras una investigación más profunda, descubrimos que la conexión de la base de datos que estaba haciendo las inserciones no había podido regresar de la llamada ODBC. Afortunadamente, el hilo que hace la grabación se separó del resto de los hilos, permitiendo que todo, excepto el hilo de grabación, continúe funcionando correctamente durante casi dos semanas.
Probamos durante varias semanas para reproducir el problema en cualquier máquina que no sea esta. Nunca pudimos reproducir el problema. Lamentablemente, varios de nuestros otros productos comenzaron a fallar de la misma manera, ninguno de los cuales tiene sus hilos de base de datos separados del resto de su funcionalidad, causando que toda la aplicación se cuelgue, que luego debe reiniciar a mano cada vez que se estrelló.
Las semanas de investigación se convirtieron en varios meses y todavía teníamos los mismos síntomas: bloqueos completos de ODBC en cualquier aplicación que usamos una base de datos. En este momento, nuestros productos están plagados de información de depuración y formas de determinar qué salió mal y dónde, incluso hasta el punto de que algunos productos detectarán el punto muerto, recopilarán información, nos enviarán los resultados por correo electrónico y luego se reiniciarán.
Mientras trabajaba en el servidor un día, aún recopilé información de depuración de las aplicaciones mientras se bloqueaban, tratando de descubrir qué estaba pasando, el servidor BSoD en mí. Cuando el servidor volvió a estar en línea, abrí el minivolcado en WinDbg para descubrir qué era el controlador infractor. Obtuve el nombre del archivo y lo remonté al archivo real. Después de examinar la información de la versión en el archivo, me di cuenta de que era parte del paquete de antivirus de McAfee instalado en la computadora.
Deshabilitamos el antivirus y no hemos tenido un solo problema desde !!