2009-12-10 10 views
5

Soy miembro de un equipo que usa Delphi 2007 para una aplicación más grande y sospechamos que hay corrupción en el montón porque a veces hay errores extraños que no tienen otra explicación. Creo que la opción Rangechecking para el compilador es solo para matrices. Quiero una herramienta que dé una excepción o un registro cuando hay una escritura en una dirección de memoria que no está asignada por la aplicación.¿Cuál es la herramienta adecuada para detectar la corrupción de VMT o de montón en Delphi?

Saludos

EDIT: El error es de tipo:

Error: Acceso violación en la dirección 00404E78 en el módulo 'BoatLogisticsAMCAttracsServer.exe'. Lectura de la dirección FFFFFFDD

EDIT2: Gracias por todas las sugerencias. Lamentablemente, creo que la solución es más profunda que eso. Usamos una versión parcheada de Bold for Delphi ya que poseemos la fuente. Probablemente hay algunos errores introducidos en el marco de negrita. Sí, tenemos un registro con callstacks manejados por JCL y también traza mensajes. Por lo que una pila de llamadas con la excepción puede bloquear de esta manera:

20091210 16:02:29 (2356) [EXCEPTION] Raised EBold: Failed to derive ServerSession.mayDropSession: Boolean 
OCL expression: not active and not idle and timeout and (ApplicationKernel.allinstances->first.CurrentSession <> self) 
Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD. At Location BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016) 

Inner Exception Raised EBold: Failed to derive ServerSession.mayDropSession: Boolean 
OCL expression: not active and not idle and timeout and (ApplicationKernel.allinstances->first.CurrentSession <> self) 
Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD. At Location BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016) 
Inner Exception Call Stack: 
[00] System.TObject.InheritsFrom (sys\system.pas:9237) 

Call Stack: 
[00] BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016) 
[01] BoldSystem.TBoldMember.DeriveMember (BoldSystem.pas:3846) 
[02] BoldSystem.TBoldMemberDeriver.DoDeriveAndSubscribe (BoldSystem.pas:7491) 
[03] BoldDeriver.TBoldAbstractDeriver.DeriveAndSubscribe (BoldDeriver.pas:180) 
[04] BoldDeriver.TBoldAbstractDeriver.SetDeriverState (BoldDeriver.pas:262) 
[05] BoldDeriver.TBoldAbstractDeriver.Derive (BoldDeriver.pas:117) 
[06] BoldDeriver.TBoldAbstractDeriver.EnsureCurrent (BoldDeriver.pas:196) 
[07] BoldSystem.TBoldMember.EnsureContentsCurrent (BoldSystem.pas:4245) 
[08] BoldSystem.TBoldAttribute.EnsureNotNull (BoldSystem.pas:4813) 
[09] BoldAttributes.TBABoolean.GetAsBoolean (BoldAttributes.pas:3069) 
[10] BusinessClasses.TLogonSession._GetMayDropSession (code\BusinessClasses.pas:31854) 
[11] DMAttracsTimers.TAttracsTimerDataModule.RemoveDanglingLogonSessions (code\DMAttracsTimers.pas:237) 
[12] DMAttracsTimers.TAttracsTimerDataModule.UpdateServerTimeOnTimerTrig (code\DMAttracsTimers.pas:482) 
[13] DMAttracsTimers.TAttracsTimerDataModule.TimerKernelWork (code\DMAttracsTimers.pas:551) 
[14] DMAttracsTimers.TAttracsTimerDataModule.AttracsTimerTimer (code\DMAttracsTimers.pas:600) 
[15] ExtCtrls.TTimer.Timer (ExtCtrls.pas:2281) 
[16] Classes.StdWndProc (common\Classes.pas:11583) 

La parte excepción interna es la pila de llamadas en el momento de re-subió una excepción.

EDIT3: La teoría en este momento es que la Tabla de memoria virtual (VMT) se ha roto de alguna manera. Cuando esto sucede, no hay indicios de ello. Solo cuando se llama a un método se genera una excepción (SIEMPRE en la dirección FFFFFFDD, -35 decimal) pero entonces es demasiado tarde. Usted no sabe la verdadera causa del error. Cualquier sugerencia de cómo atrapar un error como este es realmente apreciada. Lo hemos intentado con SafeMM, pero el problema es que el consumo de memoria es demasiado alto incluso cuando se utiliza el indicador de 3 GB. Así que ahora trato de dar una recompensa a la comunidad SO :)

EDIT4: Una sugerencia es que según el registro a menudo hay (o incluso siempre) otra excepción antes de esto. Puede ser, por ejemplo, bloqueo optimista en la base de datos. Hemos tratado de plantear excepciones por la fuerza, pero en el entorno de prueba simplemente funciona bien.

EDIT5: La historia continúa ... Realicé una búsqueda en los registros durante los últimos 30 días. El resultado:

  • "Read de dirección FFFFFFDB" 0
  • "Read de dirección FFFFFFDC" 24
  • "Read de dirección FFFFFFDD" 270
  • "Read de dirección FFFFFFDE" 22
  • " leer de dirección FFFFFFDF" 7
  • "Read de dirección FFFFFFE0" 20
  • "Read de dirección FFFFFFE1" 0

Así que la teoría actual es que una enumeración (hay muchos en negrita) sobrescribe un puntero. Recibí 5 hits con la dirección diferente anterior. Podría significar que la enumeración contiene 5 valores donde el segundo es el más utilizado. Si hay una excepción, debe producirse una reversión para la base de datos y se deben destruir Boldobjects. Tal vez exista una posibilidad de que no todo se destruya y una enumeración aún pueda escribir en una ubicación de dirección.Si esto es cierto, ¿es posible buscar el código por un regexpr para una enumeración con 5 valores?

EDIT6: Resumiendo, no hay solución al problema todavía. Me doy cuenta de que puedo engañarte un poco con la pila de llamadas. Sí, hay un temporizador, pero hay otros callstacks sin temporizador. Lo siento por eso. Pero hay 2 factores comunes.

  • Una excepción con la lectura de la dirección FFFFFFxx.
  • Parte superior de la pila de llamadas es System.TObject.InheritsFrom (SYS \ system.pas: 9237)

Este convencerme de que VilleK mejor describen el problema. También estoy convencido de que el problema está en algún lugar del marco de Bold. Pero la pregunta GRANDE es, ¿cómo se pueden resolver problemas como este? No es suficiente tener un Assert como VilleK, ya que el daño ya ocurrió y el callstack se ha ido en ese momento. Así que para describir mi visión de lo que puede provocar el error:

  1. En algún lugar un puntero se le asigna un valor de 1 mala, pero puede ser también 0, 2, 3, etc.
  2. Un objeto se asigna a ese puntero .
  3. Hay una llamada al método en la clase base de los objetos. Esto causa que se llame al método TObject.InheritsForm y aparezca una excepción en la dirección FFFFFFDD.

Esos 3 eventos pueden estar juntos en el código, pero también pueden usarse mucho más tarde. Creo que esto es cierto para la última llamada al método.

EDIT7: Trabajamos en estrecha colaboración con el autor de Bold Jan Norden y recientemente encontró un error en el evaluador de OCL en el marco de Bold. Cuando esto se solucionó, este tipo de excepciones disminuyó mucho, pero aún así ocasionalmente llegan. Pero es un gran alivio que esto esté casi resuelto.

+1

Por favor, háganos saber si acerca del fallo es de diálogo violación de acceso en su programa o simplemente un comportamiento inesperado lógica. Te pregunto porque si tienes un puntero con un contenido aleatorio que apunta "en algún lugar", es probable que obtengas un error de infracción de acceso. Deberías ser realmente afortunado de tener acceso a direcciones aleatorias y obtener solo errores lógicos. – Maksee

+0

Como escribí en la última edición, pensamos que el VMT se rompió, tal vez por un puntero roto, por lo que más adelante, cuando la aplicación llama a un método virtual, genera una excepción. –

+0

¿Es esta una aplicación multihilo? – Nat

Respuesta

5

no tengo una solución, pero hay algunas pistas sobre ese mensaje de error en particular.

System.TObject.InheritsFrom resta la constante vmtParent de la auto-puntero (la clase) para obtener el puntero a la dirección . de la clase padre

En Delphi 2007 vmtParent se define:

vmtParent = -36;

Así que el error $ FFFFFFDD (-35) suena como el puntero de la clase es de 1 en este caso.

Aquí es un caso de prueba para reproducirlo:

procedure TForm1.FormCreate(Sender: TObject); 
var 
    I : integer; 
    O : tobject; 
begin 
    I := 1; 
    O := @I; 
    O.InheritsFrom(TObject); 
end; 

lo he probado en Delphi 2010 y el mensaje 'Read de dirección FFFFFFD1' porque el vmtParent es diferente entre las versiones de Delphi.

El problema es que esto ocurre en el interior del marco de Bold, por lo que puede tener problemas para evitarlo en el código de la aplicación.

Usted puede intentar esto en sus objetos que se utilizan en el DMAttracsTimers de código (que supongo es el código de aplicación):

Assert(Integer(Obj.ClassType)<>1,'Corrupt vmt'); 
+0

Otra respuesta útil, pero acepta que no es fácil detectar el error en negrita ya que no es reproducible. –

+0

Creo que le diste la respuesta más útil. Lamentablemente, el Assert que sugiere activar solo cuando es demasiado tarde. El puntero malo ya está asignado en alguna parte. Honestamente, todavía no tengo una buena idea realista de cómo resolver esto ... –

+0

Gracias. Es difícil hacer más conjeturas sobre este problema. Mencionas que has aplicado parches a Bold y la posibilidad de que hayas introducido errores. Quizás puedas hacer una comparación detallada de tus parches con la fuente original y buscar errores cuidadosamente, agregar afirmaciones a los cambios, etc. solo para estar seguro. Además, si "RemoveDanglingLogonSessions" está a menudo en su pila de bloqueo, entonces busque en su código los lugares donde crea/destruye las sesiones de inicio de sesión, quizás destruya una instancia en algún lugar sin eliminarla de su lista de LogonSessions. –

6

Usted escribe que usted quiere que haya una excepción si

there is a write on a memory address that is not allocated by the application

pero eso sucede todos modos, tanto el hardware y la OS asegurarse de ello.

Si quiere decir que quiere comprobar las escrituras de memoria no válidas en el rango de direcciones asignado de su aplicación, entonces hay mucho que puede hacer. Debería usar FastMM4, y usarlo con su configuración más detallada y paranoica en el modo de depuración de su aplicación. Esto atrapará muchas escrituras no válidas, accesos a la memoria ya liberada y demás, pero no puede captar todo. Considere un puntero colgante que apunta a otra ubicación de memoria grabable (como el medio de una cadena grande o una matriz de valores flotantes): la escritura tendrá éxito y destruirá otros datos, pero no hay forma de que el administrador de memoria los atrape. acceso.

+2

Depende de su definición de asignada. La CPU/sistema operativo solo proporciona errores si la memoria es un acceso que no está asignado desde el sistema operativo. El OP habla sobre memoria asignada como memoria asignada por el gestor de montón. Hay una diferencia –

2

mghie es por supuesto. (fastmm4 llama a la bandera fulldebugmode o algo así).

Tenga en cuenta que eso funciona normalmente con barreras justo antes y después de una asignación de montón que se comprueban regularmente (en cada acceso al heapmgr?).

Esto tiene dos consecuencias:

  • el lugar donde FastMM detecta el error podría desviarse del punto donde ocurre
  • una escritura aleatoria total (sin desbordamiento de la asignación existente) podría no ser detectado.

Así que aquí están algunas otras cosas en que pensar:

  • permiten comprobar el tiempo de ejecución
  • revisión todas las advertencias de su compilador.
  • Intente compilar con una versión delphi o FPC diferente. Otros compiladores/rtls/heapmanagers tienen diferentes diseños, y eso podría llevar a que el error sea capturado más fácilmente.

Si todo eso no produce nada, intente simplificar la aplicación hasta que desaparezca. Luego investigue las partes comentadas/ifdefed más recientes.

1

La primera cosa que hacer es añadir MadExcept a su aplicación y obtener una retraza de la pila que imprime el árbol llamando exacta, lo que le dará una idea de lo que está pasando aquí. En lugar de una excepción aleatoria y una dirección de memoria binaria/hexadecimal, necesita ver un árbol de llamadas, con los valores de todos los parámetros y variables locales de la pila.

Si sospecho de corrupción de memoria en una estructura que es clave para mi aplicación, a menudo se escribir código extra para hacer el seguimiento de este error posible.

Por ejemplo, en estructuras de memoria (clase o grabar tipos) puede estar dispuesto para tener un Magic1: Word al principio y un Magic2: Word al final de cada registro en la memoria. Una función de verificación de integridad puede verificar la integridad de esas estructuras buscando para cada registro que Magic1 y Magic2 no se hayan cambiado de lo que se establecieron en el constructor. El Destructor cambiaría Magic1 y Magic2 a otros valores como $ FFFF.

También consideraría agregar registro de seguimiento a mi aplicación. el registro de seguimiento de las aplicaciones Delphi a menudo comienza conmigo declarar una forma TraceForm, con un TMemo de allí, y el TraceForm.Trace (msg: String) la función comienza como "Memo1.Lines.Add (msg)". A medida que mi aplicación madura, las funciones de registro de rastreo son la manera en que veo ejecutar aplicaciones para patrones generales en su comportamiento y mala conducta. Luego, cuando ocurre un bloqueo "aleatorio" o corrupción de la memoria con "sin explicación", tengo un registro de seguimiento para volver y ver qué ha llevado a este caso en particular.

A veces no se trata de corrupción de memoria sino de errores básicos simples (Olvidé comprobar si X está asignada, entonces voy a desreferenciarlo: X.DoSomething (...) que asume que X está asignado, pero no lo está.

+0

Encontré esta publicación http://stackoverflow.com/questions/1106358/fastmm4-says-the-block-header-has-been-corrupted. No sabía que FastMM puede escanear memorypool peridically o incluso después de cada operación. ¡Muy interesante! –

3

Parece que usted tiene daños en la memoria de datos de instancia de objeto.

El VMT en sí no se está dañando, FWIW: el VMT está (normalmente) almacenado en el ejecutable y las páginas que se asignan a él son de solo lectura. Más bien, como dice VilleK, parece que el primer campo de los datos de instancia en su caso se sobreescribió con un entero de 32 bits con valor 1. Esto es bastante fácil de verificar: verifique los datos de instancia del objeto cuya llamada al método falló, y verifique que el primer DWORD es 00000001.

Si es de hecho el puntero del VMT en los datos de la instancia que está viciado, así es como me iba a encontrar el código que lo corrompe:

  1. Asegúrese hay una forma automática de reproducir el problema que no requiere la participación del usuario. El problema solo puede reproducirse en una sola máquina sin reinicios entre reproducciones debido a la forma en que Windows puede elegir distribuir la memoria.

  2. Reproduzca el problema y anote la dirección de los datos de la instancia cuya memoria está dañada.

  3. Vuelva a ejecutar y compruebe la segunda reproducción: asegúrese de que la dirección de los datos de la instancia que se dañó en la segunda ejecución sea la misma que la de la primera ejecución.

  4. Ahora, en una tercera ejecución, coloque un punto de interrupción de datos de 4 bytes en la sección de memoria indicada por las dos ejecuciones anteriores. El punto es romper con cada modificación de esta memoria. Al menos un salto debe ser la llamada TObject.InitInstance que rellena el puntero de VMT; puede haber otros relacionados con la construcción de instancias, como en el asignador de memoria; y en el peor de los casos, los datos de instancia relevantes pueden haber sido memoria reciclada de instancias previas. Para reducir la cantidad de pasos necesarios, haga que el punto de interrupción de datos inicie sesión en la pila de llamadas, pero no se rompa. Al verificar las pilas de llamadas después de que falla la llamada virtual, debería poder encontrar la escritura incorrecta.

+0

Lamentablemente, el caso no es reproducible pero su respuesta aún agrega algo de información a la pregunta. El error ocurre tal vez 5 - 20 veces por semana en producción. Si hubiera un caso en que pudiéramos decir que era reproducible, sería mucho más fácil (pero no muy fácil ...). Una sugerencia que vi en los registros es que hay una excepción antes de este error, vea edit4 en cuestión. –

+0

Estoy de acuerdo con Barry. Agregaría un campo FMagicFlag1: Int64 al principio y FMagicFlag2: campo Int64 al final de cada clase principal que podría estar dañado, y en el constructor, establezca FMagicFlag1 = cMagic1, FMagicFlag2 = cMagic2 y Assert (FMagicFlag1 = cMagic1)/Assert (FMagicFlag2 = cMagic2) en algún método importante de esa clase. Mire por ejemplo la corrupción de datos de esa manera. –

1

Noté que hay un temporizador en el seguimiento de la pila.
He visto una gran cantidad de errores extraños, donde la causa fue el evento del temporizador se dispara después de la forma que he freeed.
La razón es que se coloca un coorde de evento de temporizador en la cola de mensajes, y se procesa noge para la destrucción de otros componentes.
Una forma de evitar ese problema es deshabilitar el temporizador como la primera entrada en la destrucción del formulario. Después de deshabilitar el tiempo, llame a Application.processMessages, de modo que cualquier evento del temporizador se procese antes de destruir los componentes.
Otra forma es verificar si el formulario está destruyendo en el tiempo previsto. (csDestruir en el estado de los componentes).

+0

Benny tiene un buen punto aquí. Los temporizadores pueden ser la fuente de todo tipo de problemas técnicos. Intente reemplazar el TTimer con un TJvThreadTimer que utiliza un hilo de fondo y TTHread.Synchronize en lugar de un mensaje de temporizador WIN32. –

+0

No estoy de acuerdo, ver mi cambio edit6 arriba. –

0

¿Se puede publicar el código fuente de este procedimiento?

BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)

Por lo que podemos ver lo que está sucediendo en la línea 4016.

Y también el punto de vista de la CPU de esta función?
(simplemente establezca un punto de interrupción en la línea 4016 de este procedimiento y ejecútelo, y copie y pegue los contenidos de la vista de la CPU si llega al punto de interrupción).
Para que podamos ver qué instrucción de CPU es en la dirección 00404E78.

+0

En la línea 4016 solo hay un aumento EBold.Create después de un intento, excepto. Por lo tanto, normalmente nunca llega a esa línea a excepción de este error. Y no puedo reproducirlo en prueba. Pero hay algún progreso, se mi edición. –

0

¿Podría haber un problema con el código de reentrada?

trate de poner un cierto código de protección alrededor del código de controlador de eventos TTimer:

procedure TAttracsTimerDataModule.AttracsTimerTimer(ASender: TObject); 
begin 
    if FInTimer then 
    begin 
    // Let us know there is a problem or log it to a file, or something. 
    // Even throw an exception 
    OutputDebugString('Timer called re-entrantly!'); 
    Exit; //======> 
    end; 

    FInTimer := True; 
    try 

    // method contents 

    finally 
    FInTimer := False; 
    end; 
end; 

N @

+0

Todo es posible, por supuesto, pero creo más en la teoría enum. Se mi Edit5 arriba. –

0

Creo que hay otra posibilidad: el temporizador se dispara para comprobar si hay "Colgando inicios de sesión" . Luego, se realiza una llamada en un objeto TLogonSession para verificar si se puede descartar (_GetMayDropSession), ¿verdad? Pero, ¿y si el objeto ya está destruido? Tal vez debido a problemas de seguridad del hilo o simplemente una llamada .Free y no una llamada FreeAndNil (por lo que una variable sigue siendo <> nil) etc etc. Mientras tanto, se crean otros objetos para que la memoria se reutilice. Si intenta acces la variable algún tiempo más tarde, puede/obtendrá errores aleatorios ...

Un ejemplo:

procedure TForm11.Button1Click(Sender: TObject); 
var 
    c: TComponent; 
    i: Integer; 
    p: pointer; 
begin 
    //create 
    c := TComponent.Create(nil); 
    //get size and memory 
    i := c.InstanceSize; 
    p := Pointer(c); 
    //destroy component 
    c.Free; 
    //this call will succeed, object is gone, but memory still "valid" 
    c.InheritsFrom(TObject); 
    //overwrite memory 
    FillChar(p, i, 1); 
    //CRASH! 
    c.InheritsFrom(TObject); 
end; 

violación de acceso en la dirección 004619D9 en el módulo 'Project10.exe'. Lectura de la dirección 01010101.

0

¿No es el problema que "_GetMayDropSession" hace referencia a una variable de sesión liberada?

He visto este tipo de errores antes, en TMS donde los objetos fueron liberados y referenciados en un intercambio, etc. (solo en algunas situaciones dio errores, muy difíciles/imposibles de reproducir, ahora está resuelto por TMS :-)) También con las sesiones de RemObjects obtuve algo similar (debido a un error de programación).

me gustaría tratar de añadir una variable ficticia a la clase de sesión y comprobar su valor:

  • iMagicNumber variable pública: entero;
  • constructor create: iMagicNumber: = 1234567;
  • destructor destroy: iMagicNumber: = -1;
  • "otros procedimientos": aserción (iMagicNumber = 1234567)
Cuestiones relacionadas