среда, 1 февраля 2017 г.

#1346. Draft of objects spy. Размышления. Заготовочка для следилки за объектами

https://bitbucket.org/lulinalex/mindstream/src/c3db0b6368040992e0f4eae64854d6e4ea34844a/Lab/RefCount/l3ObjectsSpy.pas?at=B284_Inheritance_Try&fileviewer=file-view-default

unit l3ObjectsSpy;

interface

procedure Test;

implementation

uses
 System.SysUtils
 , System.Classes
 , System.Generics.Collections
 , WinApi.Windows
 ;

type
 Tl3SpiedObjects = class(TList<Pointer>)
 end;//Tl3SpiedObjects

 Tl3ObjectsSpy = class(Tl3SpiedObjects)
  strict private
   class var g_Instance: Tl3ObjectsSpy;
  public
   class procedure LogNew(anObject: TObject);
   class procedure LogFree(anObject: TObject);
   class procedure HookClasses;
   class procedure CheckUnfreed;
 end;//Tl3ObjectsSpy

class procedure Tl3ObjectsSpy.LogNew(anObject: TObject);
begin
 if (g_Instance = nil) then
  g_Instance := Tl3ObjectsSpy.Create;
 g_Instance.Add(anObject);
end;

class procedure Tl3ObjectsSpy.LogFree(anObject: TObject);
begin
 if (g_Instance <> nil) then
  g_Instance.Remove(anObject);
end;

procedure DoHookClasses; forward;

class procedure Tl3ObjectsSpy.HookClasses;
begin
 DoHookClasses;
end;

class procedure Tl3ObjectsSpy.CheckUnfreed;
begin
 if (g_Instance <> nil) then
  if (g_Instance.Count > 0) then
   WriteLn(g_Instance.Count);
end;

type
 PMem = PAnsiChar;

function NewInstancePtr(aClass: TClass): PPointer;
begin
 Result := PPointer(PMem(aClass) + vmtNewInstance);
end;

function FreeInstancePtr(aClass: TClass): PPointer;
begin
 Result := PPointer(PMem(aClass) + vmtFreeInstance);
end;

procedure LogNew(anObject: TObject);
begin
 Tl3ObjectsSpy.LogNew(anObject);
end;

procedure LogFree(anObject: TObject);
begin
 Tl3ObjectsSpy.LogFree(anObject);
end;

type
 THackObject = class(TObject)
  public
   class function NewInstance: TObject; override;
   procedure FreeInstance; override;
 end;//THackObject

class function THackObject.NewInstance: TObject;
begin
 Result := inherited NewInstance;
 LogNew(Result);
end;

procedure THackObject.FreeInstance;
begin
 LogFree(Self);
 inherited FreeInstance;
end;

type
 THackInterfacedObject = class(TInterfacedObject)
  public
   class function NewInstance: TObject; override;
   procedure FreeInstance; override;
 end;//THackInterfacedObject

class function THackInterfacedObject.NewInstance: TObject;
begin
 Result := inherited NewInstance;
 LogNew(Result);
end;

procedure THackInterfacedObject.FreeInstance;
begin
 LogFree(Self);
 inherited FreeInstance;
end;

procedure HookPtr(aPointer: PPointer; aValue: Pointer);
var
 l_Old : DWORD;
 l_New : DWORD;
begin
 VirtualProtect(aPointer, SizeOf(Pointer), PAGE_EXECUTE_READWRITE, l_Old);
 try
  aPointer^ := aValue;
 finally
  VirtualProtect(aPointer, SizeOf(Pointer), l_Old, l_New);
 end;
end;

function HookPtrDone(aPointer: PPointer; aFrom: Pointer; aTo: Pointer): Boolean;
begin
 Result := (aPointer^ = aFrom);
 if Result then
  HookPtr(aPointer, aTo);
end;

function HookNew(aClass: TClass): Boolean;
var
 l_Ptr : PPointer;
begin
 Result := true;
 l_Ptr := NewInstancePtr(aClass);
 if HookPtrDone(l_Ptr, @TObject.NewInstance, @THackObject.NewInstance) then
  Exit;
 if HookPtrDone(l_Ptr, @TInterfacedObject.NewInstance, @THackInterfacedObject.NewInstance) then
  Exit;
 Result := false;
end;

function HookFree(aClass: TClass): Boolean;
var
 l_Ptr : PPointer;
begin
 Result := true;
 l_Ptr := FreeInstancePtr(aClass);
 if HookPtrDone(l_Ptr, @TObject.FreeInstance, @THackObject.FreeInstance) then
  Exit;
 if HookPtrDone(l_Ptr, @TInterfacedObject.FreeInstance, @THackInterfacedObject.FreeInstance) then
  Exit;
 Result := false;
end;

function HookClassPrim(aClass: TClass): Boolean;
begin
 Result := false;
 if HookNew(aClass) then
  Result := true;
 if HookFree(aClass) then
  Result := true;
end;

procedure HookClass(aClass: TClass);
var
 l_Class : TClass;
begin
 l_Class := aClass;
 while (l_Class <> nil) do
 begin
  if not HookClassPrim(l_Class) then
   Exit;
  l_Class := l_Class.ClassParent;
 end;//l_Class <> nil
end;

procedure DoHookClasses;
begin
 HookClass(TObject);
 HookClass(TStream);
 HookClass(TInterfacedObject);
 HookClass(TThread);
 HookClass(TComponent);
 //HookClass(TExternalThread);
end;

procedure Test;
var
 l_O : TObject;
 l_S : TStream;
 l_IO : TInterfacedObject;
 l_T : TThread;
begin
 //HookClasses;
 l_O := TObject.Create;
 FreeAndNil(l_O);

 l_S := TStream.Create;
 FreeAndNil(l_S);

 l_IO := TInterfacedObject.Create;
 FreeAndNil(l_IO);

 l_T := TThread.Create;
 FreeAndNil(l_T);
end;

initialization
 Tl3ObjectsSpy.HookClasses;
finalization
 Tl3ObjectsSpy.CheckUnfreed;
end.

(+) http://edn.embarcadero.com/article/28344

Вызывать CheckUnfreed в секции финализации на самом деле бессмысленно, т.к. могли ещё не вызваться другие секции финализации, в которых ещё не были освобождены объекты. И следилка покажет утечку, которой на самом деле нет.

Надо встраиваться в NotifyUnloadModule, который вызывается после всех секций инициализации. Я над этим сейчас работаю.

Ну и потокозащищённость надо конечно добавить.

Ну и ещё надо запоминать адрес места аллокации. Чтобы потом можно было найти по коду проблемное место. Это тоже делается достаточно просто.

Можно даже стек снять, но это сильно скажется на производительности.

Вскорости опубликую доработанную версию данного класса.

Ещё появилась мысль, что можно поставить Hook на классы своего приложения и включать его в ini-файле и в хуке звать методы из скриптов, которые можно править не останавливая приложения. Таким образом через подобные "разьёмы" снимать диагностику с классов реализации не останавливая и не перекомпилируя приложение.

Над этим я тоже работаю.

Update. Опробовал сегодня следилку "в боевых условиях".Даже с раскруткой стека. Удобно. Нашёл пару утечек. Скоро выложу обновлённый вариант.

15 комментариев:

  1. Александр, а скриптовый двиг какой используете?

    ОтветитьУдалить
    Ответы
    1. Свой собственный. Так исторически сложилось. На forth-подобной архитектуре. В этом блоге есть описание скриптов и много отсылокте ним. Вечером могу накидать ссылок.

      Удалить
    2. На forth-подобной архитектуре. Паскалеподобный в ключевых словах. И с элементами функциональщины типа итераторов, лямбд и кешированных вычислений. В общем - адская смесь.

      Удалить
    3. Спасибо, ищу нечто паскалевское, но так, без огонька ищу. Главное чтобы код был открыт - брать на борт проект с числом трамвая=1 страшно.

      Удалить
    4. Ну так я и исходники приводил.

      Удалить
    5. https://bitbucket.org/lulinalex/mindstream/wiki/Home

      Удалить
    6. https://bitbucket.org/lulinalex/mindstream/wiki/Articles%20in%20English/Script%20engine%20organisation/Introduction

      Удалить
    7. http://programmingmindstream.blogspot.ru/2015/12/1145.html

      Удалить
    8. http://programmingmindstream.blogspot.ru/2015/08/blog-post_26.html

      Удалить
    9. http://programmingmindstream.blogspot.ru/2015/12/1147.html

      Удалить
    10. http://programmingmindstream.blogspot.ru/2015/12/1148.html

      Удалить
    11. http://programmingmindstream.blogspot.ru/2015/12/1149.html

      Удалить
    12. http://programmingmindstream.blogspot.ru/2015/12/1204.html

      Удалить
    13. https://bitbucket.org/lulinalex/mindstream/wiki/%D0%A1%D1%82%D0%B0%D1%82%D1%8C%D0%B8%20%D0%BD%D0%B0%20%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%BC/%D0%9E%20%D0%BA%D0%BE%D0%B4%D0%BE%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8/%D0%9A%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D0%BE.%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%D0%BB%D0%BE%D0%B2

      Удалить
  2. Опробовал сегодня следилку "в боевых условиях".Даже с раскруткой стека. Удобно. Нашёл пару утечек. Скоро выложу обновлённый вариант.

    ОтветитьУдалить