Autor Beitrag
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19335
Erhaltene Danke: 1751

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 23.08.10 09:11 
Hallo,

nach einiger Zeit kommt von mir auch wieder einmal eine Frage. :mrgreen:

Ich möchte bei einer Exception einen StackTrace ausgeben, dabei aber auch die Komponenten mit ausgeben, zu denen die Methoden gehören.

Ich habe dafür die JclDebug.pas benutzt und erweitert. Es funktioniert auch. Nur ist mir klar geworden, dass mir wohl doch nicht so ganz klar ist was da auf dem Stack liegt. Bzw. wie ich herausfinde, ob da ein Objekt liegt (falls das geht).

Nun, ich poste einmal wie ich es versucht habe. Zunächst wollte ich feststellen, ob es sich um ein Objekt handelt, ohne dabei aber Exceptions auszulösen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
function IsValidObject(Obj: TObject): Pointer;
type
  PPVmt = ^PVmt;
  PVmt = ^TVmt;
  TVmt = record
    SelfPtr : TClass;
    Other   : array[0..17of pointer;
  end;
var
  Vmt: PVmt;
begin
  if Assigned(Obj) and (Integer(Obj) > $FFFF)  and (Integer(Obj) < $FFFFFF)
    and not IsBadReadPtr(Obj, 4and not IsBadReadPtr(PPointer(Obj)^, 4then
    try
      Result := Obj;
      Vmt := PVmt(Obj.ClassType);
      Dec(Vmt);
      if IsBadReadPtr(Vmt, 4or IsBadReadPtr(PPointer(Vmt)^, 4or (Obj.ClassType <> Vmt.SelfPtr) then
        Result := nil;
    except
      Result := nil;
    end
  else
    Result := nil;
end;
Die Prüfung der Bereiche innerhalb der beiden Konstanten funktioniert zwar in meinen Tests, aber so wirklich gefallen tut es mir nicht. Ich habe aber erst einmal auch nicht weiter darauf geschaut.

Dann zu dem Problem selbst, dafür bin ich in TJclStackInfoList.TraceStackRaw gegangen, der neue Teil ist markiert:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
type
  TStackInfo = record
    CallerAddr: TJclAddr;
    Level: DWORD;
    CallerFrame: TJclAddr;
    DumpSize: DWORD;
    ParamSize: DWORD;
    ParamPtr: PDWORD_PTRArray;
    CallerObj: TJclAddr;
    case Integer of
      0:
        (StackFrame: PStackFrame);
      1:
        (DumpPtr: PJclByteArray);
  end;

procedure TJclStackInfoList.TraceStackRaw;
var
  StackInfo: TStackInfo;
  StackPtr: PJclAddr;
  ObjectPtr: PJclAddr;
  PrevCaller: TJclAddr;
  CallInstructionSize: Cardinal;
  StackTop: TJclAddr;
begin
  Capacity := 32// reduce ReallocMem calls, must be > 1 because the caller's EIP register is already in the list

  if DelayedTrace then
  begin
    if not Assigned(FStackData) then
      Exit;
    StackPtr := PJclAddr(FStackData);
  end
  else
  begin
    // We define the bottom of the valid stack to be the current ESP pointer
    if BaseOfStack = 0 then
      BaseOfStack := TJclAddr(GetStackPointer);
    // Get a pointer to the current bottom of the stack
    StackPtr := PJclAddr(BaseOfStack);
  end;

  StackTop := TopOfStack;

  if Count > 0 then
    StackPtr := SearchForStackPtrManipulation(StackPtr, Pointer(Items[0].StackInfo.CallerAddr));

  // We will not be able to fill in all the fields in the StackInfo record,
  // so just blank it all out first
  ResetMemory(StackInfo, SizeOf(StackInfo));
  // Clear the previous call address
  PrevCaller := 0;
  // Loop through all of the valid stack space
  while (TJclAddr(StackPtr) < StackTop) and (inherited Count <> MaxStackTraceItems) do
  begin
    StackInfo.CallerObj := 0;
    // If the current DWORD on the stack refers to a valid call site...
    if ValidCallSite(StackPtr^, CallInstructionSize) and (StackPtr^ <> PrevCaller) then
    begin
      // then pick up the callers address
      StackInfo.CallerAddr := StackPtr^ - CallInstructionSize;
      // remember to callers address so that we don't report it repeatedly
      PrevCaller := StackPtr^;
      // increase the stack level
      Inc(StackInfo.Level);
      ObjectPtr := StackPtr;
      Inc(ObjectPtr);
      if Assigned(ValidateObj(TObject(ObjectPtr^))) then
        StackInfo.CallerObj := ObjectPtr^;

      // then report it back to our caller
      StoreToList(StackInfo);
      StackPtr := SearchForStackPtrManipulation(StackPtr, Pointer(StackInfo.CallerAddr));
    end;
    // Look at the next DWORD on the stack
    Inc(StackPtr);
  end;
  if Assigned(FStackData) then
  begin
    FreeMem(FStackData);
    FStackData := nil;
  end;
end;
Ich erhöhe also einfach den Pointer und schaue ob ich dahinter ein Objekt finde.

Ja, geht das besser? Oder geht es nur so mit raten und ausprobieren?

Wie gesagt: Funktionieren tut es und Schutzverletzungen kommen auch keine mehr dank IsBadReadPtr. Ich würde es aber gerne besser machen, wenn es geht.

Vielen Dank,
schönen Gruß,
Sebastian


Zuletzt bearbeitet von jaenicke am Mo 23.08.10 18:50, insgesamt 1-mal bearbeitet
delfiphan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Mo 23.08.10 16:50 
Ich kann dir die Antwort nicht geben aber ein StackTrace ist immer mit ein bisschen Raten verbunden. Soweit ich weiss gibt es keine bombensichere Lösung.

Aber jetzt mal unabhängig vom Stacktrace: Eine verlässliche IsValidObject-Funktion kann man evtl. bauen, wenn man über FastMM geht. Offenbar hat dieser ja in irgend einer Form eine Liste der Objekte. Zu mindest kann er die Memoryleaks ja ausgeben. Daran könntest du anknüpfen.

Der Ausdruck "Objekte auf dem Stack" stört mich, aber wir wissen ja, was gemeint ist
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mo 23.08.10 17:01 
Naja, gibt noch ne andere Variante, die mir grad einfällt, die man mal ausprobieren müsste.

Und zwar sind die Pointer von Objekten/Typdaten innerhalb der Instanz-Daten IMMER an nem festen Offset (vmtClassInfo IIRC). Das dereferenzieren und du bekommst die TypeInfo der Klasse. Die bekommt man über die normalen RTTI-Info-Funktionen dann ausgewertet.

Bzgl. IsBadReadPtr ggf. soviel: Das löst intern ne Exception aus. Besser wäre hier, selber mit VirtualQuery zu schauen, ob die Speicherbereiche gültig sind.

Alternativ bzgl. StackTraces solltest Du ggf. auch mal beim ODbgInterface bei Omorphia reinschauen. Das geht zwar dafür nicht so schön zu nutzen, liefert dafür aber wirklich alle Funktionsaufrufe. In Kombination mit ODbgMapfile (und ggf. nem Parser, der TDI32-Debug-Infos liest) wäre das eigentlich nahezu perfekt.

Anm: Gültigkeitsprüfung für Typ-Informationen: Liegen innerhalb des Code-Segmentes des deklarierenden Moduls.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.