Pregunta

Soy un miembro de un equipo que utilice Delphi 2007 para una aplicación más grande y sospechamos daños en la pila porque a veces hay errores extraños que no tienen otra explicación. Creo que la opción Rangechecking para el compilador es sólo para matrices. Quiero una herramienta que da una excepción o ingrese cuando hay una escritura en una dirección de memoria que no tiene asignada por la aplicación.

Regards

editar : El error es de tipo:

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

Edit2 : Gracias por todas las sugerencias. Desafortunadamente, creo que la solución es más profundo que eso. Utilizamos una versión parcheada de Negrita para Delphi que poseemos la fuente. Probablemente hay algunos errores introducidos en el framwork negrita. Sí, tenemos un registro con callstacks que son manejados por JCL y también los mensajes de seguimiento. 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 en que se re-subió una excepción.

Edit3: La teoría actual es que la tabla de memoria virtual (VMT) es de alguna manera roto. Cuando esto suceda no hay ninguna indicación de ello. Sólo cuando se llama a un método se produce una excepción ( Siempre en la dirección FFFFFFDD, -35 decimal), pero entonces es demasiado tarde. Usted no sabe la causa real del error. Cualquier indicio de cómo atrapar un insecto como esto es muy apreciado !!! Hemos tratado con SafeMM, pero el problema es que el consumo de memoria es demasiado alto, incluso cuando se utiliza la bandera 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, el bloqueo optimista en la base de datos. Hemos tratado de lanzar excepciones por la fuerza sino en el entorno de prueba sólo funciona bien.

EDIT5: La historia continúa ... Hice una búsqueda en los registros de 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
  • "Read 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 un montón en negrita) sobrescribir un puntero. Tengo 5 golpes con diferente dirección antes mencionada. Esto podría significar que la enumeración tiene 5 valores en el que el segundo es más utilizado. Si hay una excepción una reversión debe ocurrir para la base de datos y Boldobjects debe ser destruido. Tal vez hay una posibilidad de que no todo es destruido y una enumeración todavía puede escribir en una ubicación de dirección. Si esto es cierto tal vez es posible buscar el código por un RegExpr para una enumeración con 5 valores?

EDIT6: Para resumir, no hay ninguna solución para el problema todavía. Me doy cuenta que es posible que engañar un poco con la pila de llamadas. Sí, hay un contador de tiempo en eso, pero hay otras callstacks sin temporizador. Lo siento por eso. Sin embargo, hay 2 factores comunes.

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

Esto me convence de que VilleK mejor describa el problema. También estoy convencido de que el problema está en algún lugar en el marco negrita. Sin embargo, el GRANDE pregunta es, ¿cómo se pueden resolver problemas como éste? No es suficiente tener una aserción como VilleK sugieren que el daño ya ha ocurrido y la pila de llamadas 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. No es llamada a un método en el baseclass objetos. Este método TObject.InheritsForm hacer que se llama y una excepción aparece en la dirección FFFFFFDD.

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

EDIT7: Trabajamos estrechamente con el autor de Negrita Ene Norden y se encontró recientemente un error en el OCL-evaluador en el marco negrita. Cuando esto se fijó este tipo de excepciones disminuyeron mucho, pero todavía de vez en cuando vienen. Pero es un gran alivio que esto está casi resuelto.

¿Fue útil?

Solución

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 1 es en este caso.

Este 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 intentado en Delphi 2010 y conseguir 'Read de dirección FFFFFFD1' porque el vmtParent es diferente entre las versiones de Delphi.

El problema es que esto sucede en el interior del marco negrita lo que puede tener problemas para protegerse contra él en el código de 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');

Otros consejos

Usted escribe que quiere que haya una excepción si

  

hay una escritura en una dirección de memoria que no tiene asignada por la aplicación

pero eso sucede todos modos, tanto los href="http://en.wikipedia.org/wiki/Memory_management_unit" y la OS asegurarse de ello.

Si usted quiere decir que desea comprobar de memoria no válida escribe en el rango de direcciones asignado su aplicación, entonces no es mucho lo que puede hacer. Debe utilizar FastMM4 , y utilizarlo con sus ajustes más detallados y paranoides en el modo de depuración de la aplicación . Esto cogerá una gran cantidad de operaciones de escritura no válidas, accede a la memoria ya liberado y tal, pero no puede detener todo. Considere una referencia colgante que apunta a otra ubicación de memoria grabable (como el centro de una gran cadena o matriz de valores de coma flotante) - escribir en él tendrá éxito, y va a la basura otros datos, pero no hay manera para que el administrador de memoria para coger tales el acceso.

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

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

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

  1. Asegúrese de que hay una forma automatizada para reproducir el problema que no requiere intervención del usuario. La cuestión puede ser sólo reproducibles en una sola máquina, sin reinicios entre reproducciones debido a cómo Windows puede optar por presentar a la memoria.

  2. Reproducir el problema y tenga en cuenta la dirección de los datos de instancia cuya memoria está dañado.

  3. Volver a ejecutar y comprobar la segunda reproducción:. Asegurarse de que la dirección de los datos de instancia que fue corrompida en la segunda pasada es la misma que la dirección de la primera carrera

  4. Ahora, paso en un tercer plazo, poner un punto de interrupción de datos de 4 bytes en la sección de memoria indicada por las dos carreras anteriores. El punto es romper en cada modificación a esta memoria. Al menos una ruptura debe ser la llamada TObject.InitInstance que rellena el puntero del VMT; puede haber otros relacionados con la construcción ejemplo, tal como en el asignador de memoria; y en el peor de los casos, los datos de instancia pertinentes pueden haber sido reciclado de memoria de las instancias anteriores. Para reducir la cantidad de dar un paso necesario, hacer que los datos de punto de interrupción ingrese la pila de llamadas, pero en realidad no romper. Al marcar las pilas de llamadas después de la llamada virtual falla, usted debería ser capaz de encontrar la mala escritura.

mghie es correcta, por supuesto. (Fastmm4 llama al fulldebugmode bandera o algo por el estilo).

Tenga en cuenta que por lo general que trabaja con barreras justo antes y después de una asignación del montón que se controlen con regularidad (en cada acceso 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:

  • permitir la comprobación en tiempo de ejecución
  • revisar todas las advertencias de su compilador.
  • intenta compilar con una versión diferente o delphi FPC. Otros compiladores / RTLS / heapmanagers tienen diferentes diseños, y que podrían conducir al error de ser capturado más fácil.

Si todos los rendimientos que nada, tratar de simplificar la aplicación hasta que desaparece. Luego de investigar las partes más recientes comentadas / ifdefed.

Lo primero que haría es añadir MadExcept a su aplicación y obtener una retraza de la pila que imprime el árbol de llamadas exacta, lo que le dará una idea de lo que está pasando aquí. En lugar de una excepción al azar y una dirección de memoria binaria / hex, necesita ver un árbol que llama, 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 la clave de mi solicitud, voy a menudo 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 comprobación de integridad puede comprobar la integridad de las estructuras examinado para ver para cada registro Magic1 y Magic2 no se han cambiado de lo que se establecieron en el constructor. El Destructor cambiaría Magic1 y Magic2 a otros valores como $ FFFF.

También me gustaría considerar la adición de trazas de registro de mi solicitud. 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)". Como mi solicitud madura, las instalaciones de registro de seguimiento son la manera Miro aplicaciones que se ejecutan en los patrones generales en su comportamiento, y mal comportamiento. Entonces, cuando un accidente o daños en la memoria "al azar" con "ninguna explicación" pasa, tengo un registro de seguimiento para volver a través y ver lo que ha dado lugar a este caso particular.

A veces no es una corrupción de memoria, pero los errores básicos simples (se me olvidó para comprobar si se ha asignado X, entonces voy eliminar la referencia:. X.DoSomething (...) que asume X se le asigna, pero no es

Me di cuenta de que es un contador de tiempo en el seguimiento de la pila.
He visto un montón de errores extraños en que la causa fue se dispara el evento de temporizador después de la Forma I free'ed.
La razón es que un evento cound temporizador se pone en la cola de mensajes y noge se procesan brfor la destrucción de otros componentes.
Una forma de evitar este problema es desactivar el temporizador como la primera entrada en el destruir del formulario. Después de desactivar las Application.ProcessMessages tiempo de atención, por lo que cualquier evento de temporizador se procesa antes de la destrucción de los componentes.
Otra forma es comprobar si el formulario está destruyendo en el TimerEvent. (CsDestroying en componentstate).

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?
(Acaba de establecer un punto de interrupción en la línea 4016 de este procedimiento y correr. Y copiar + pegar el contenido de vista de la CPU si se golpea el punto de interrupción).
Así podemos ver que la enseñanza de la CPU está en la dirección 00404E78.

¿Puede haber un problema con el código reentrante?

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 @

Creo que hay otra posibilidad: el temporizador se dispara para comprobar si hay "Colgando inicios de sesión". A continuación, una llamada se realiza en un objeto TLogonSession para comprobar si se puede eliminar (_GetMayDropSession), ¿verdad? Pero lo que si el objeto está ya destruido? Tal vez debido a enhebrar los problemas de seguridad o sólo una llamada .Free y no una llamada FreeAndNil (por lo que una variable es todavía <> nil), etc, etc Por el momento, se crean otros objetos por lo que la memoria se reutiliza. 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;

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

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

he visto este tipo de errores antes, en el TMS, donde fueron liberados y se hace referencia en un onchange etc (sólo en algunas situaciones se dio errores, muy difícil / imposible de reproducir, se ha resuelto ahora por TMS :-)) objetos. También con sesiones RemObjects Tengo algo similar (debido al mal fallo de programación por mí mismo).

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 crear: iMagicNumber: = 1234567;
  • destructor destruir: iMagicNumber: = -1;
  • "otros procedimientos": assert (iMagicNumber = 1.234.567)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top