The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My work

  • My badges

  • Twitter Updates

  • My Flickr Stream

    20140508-Delphi-2007--Project-Options--Cannot-Edit-Application-Title-HelpFile-Icon-Theming

    20140430-Fiddler-Filter-Actions-Button-Run-Filterset-now

    20140424-Windows-7-free-disk-space

    More Photos
  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,338 other followers

Delphi – hardcore debugging the intialization of your app, dlls and packages

Posted by Jeroen Pluimers on 2009/10/15

Recently we got involved with a client having a large and complex application that (historically) consists of

  1. A main .EXE that loads
  2. Many DLLs
  3. Underlying BPLs

One of the biggest problems is debugging the startup sequence.
Somehow, when the Delphi IDE loads DLLs in the initialization sequences of units, it looses its ability symbol tables.

This article describes a few tips on how to debug those, especially where to put breakpoints.

Yes, we have done the obvious things like:

In the end, it appears that when DLL’s with symbols get loaded during unit initialization sections, the Delphi IDE (in this case 2007) loses the Symbol Tables either for the EXE or for the DLL (or both).

In order to work around this:

  • We had to know which DLLs were loaded and when
  • We rewrote all the DLLs to be ‘demand/delay loaded‘ (i.e. not statically linked, see code sample below)
  • We rewrote all the stuff that referenced the DLLs to initialize on demand as well

Here are some tips for setting breakpoints to debug the startup sequence, including some links to relevant documentation, samples or articles:

  1. Unit System; procedure InitUnits; line “TProc(P)();”
    (this line gets called often: for each unit that is being initialized, either because the unit has an initialization section, or it has initialized consts; see an example call stack) .
  2. Unit SysUtils; procedure SafeLoadLibrary;
    (we call this for every DLL that gets loaded; this method saves and restores the FPU control word, especially handy for 3rd party DLLs that think they own the FPU).
  3. unit System; procedure _InitLib; line “MOV EDX,offset Module”
    (this gets called in a DLL when Windows is loading it with LoadLibrary)
  4. unit SysInit; procedure InitProcessTLS;
    (inside a DLL this reserves the Thread Local Storage to be used by this DLL; it usually is the first thing called in a DLL)
  5. unit System; procedure ExitDll; line “RET 12″ (assembler)
    (this gets called in a DLL when Windows is almost finished processing the LoadLibrary for it)
  6. DLL kernel32.LoadLibraryA and DLL kernel32.LoadLibraryW, CPU code “cmp dword ptr [ebp+$08],$00″, then in the data portion of the CPU viewer, go to the address “ebp+$08″ , then press Ctrl-D to follow “long data”; now you see the module that is going to be loaded
    (this gets called by Windows when loading DLLs)
  7. ntdll.LdrInitializeThunk, CPU code “call dword ptr[ebp+$08]“
    (this gets called by Windows just before loading the DLL)

For the demand loading, we used this small unit:

unit BLibrary;

interface

uses
  SysUtils, Windows;

const
  NoLibraryHandle = 0;

type
  ELibrary = class(Exception);
  TLibraryRecord = record
    Name: string;
    Handle: HModule;
  end;

  /// See also this method in the ComObj unit:
  /// procedure RegisterComServer(const DLLName: string);
function LoadLibrary(var LibraryRecord: TLibraryRecord): Boolean; overload;

procedure FreeLibrary(var LibraryRecord: TLibraryRecord); overload;

function GetProcAddress(var LibraryRecord: TLibraryRecord; const ProcName: string): FARPROC; overload;

implementation

function IsInvalidModuleHandle(const Handle: HModule): Boolean;
begin
  Result := (Handle <= HINSTANCE_ERROR);
end;

function SysErrorMessageLastError(): string;
begin
  Result := SysErrorMessage(GetLastError);
end;

function LoadLibrary(var LibraryRecord: TLibraryRecord): Boolean;
begin
  Result := False;
  if IsInvalidModuleHandle(LibraryRecord.Handle) then
  begin
    LibraryRecord.Handle := SafeLoadLibrary(LibraryRecord.Name);
    if IsInvalidModuleHandle(LibraryRecord.Handle) then
      raise ELibrary.CreateFmt('DLL "%s" nicht geladen wegen: "%s"',
        [LibraryRecord.Name, SysErrorMessageLastError]);
    Result := True;
  end;
end;

procedure FreeLibrary(var LibraryRecord: TLibraryRecord);
var
  Success: Boolean;
begin
  if not IsInvalidModuleHandle(LibraryRecord.Handle) then
  begin
    Success := Windows.FreeLibrary(LibraryRecord.Handle);
    if not Success then
      raise ELibrary.CreateFmt('DLL "%s" entladen fehler: "%s"',
        [LibraryRecord.Name, SysErrorMessageLastError]);
    LibraryRecord.Handle := NoLibraryHandle;
  end
end;

function GetProcAddress(var LibraryRecord: TLibraryRecord; const ProcName: string): FARPROC;
begin
  LoadLibrary(LibraryRecord);
  Result := Windows.GetProcAddress(LibraryRecord.Handle, PChar(ProcName));
  if Result = nil then
    raise ELibrary.CreateFmt('DLL "%s" fehler beim laden von ohne Eintrittspunkt "%s": "%s"',
      [LibraryRecord.Name, ProcName, SysErrorMessageLastError]);
end;

end.

This article is work-in-progress, so you will see updates in the next coming days.

–jeroen

3 Responses to “Delphi – hardcore debugging the intialization of your app, dlls and packages”

  1. It loses symbols for the System unit (among other things) when you load a package, because the package has to load RTL???.bpl instead of the RTL you’ve got linked in, if you had one, and Embarcadero doesn’t ship a debug-enabled version of the runtime packages. We have debug DCUs, but not debug DCPs.

    • jpluimers said

      I think you might be right, my first guess was also that having more than one RTL in memory at the same time would confuse the IDE.
      Chris Hesik hinted that debug DCPs might become available in the comments thread of this post.

      But on the other hand, so far when loading those DLLs outside the initialization section of the EXEs units, everything goes dandy.
      So I’m not completely convinced :-)

      –jeroen

      • I don’t think it’s the DLLs; it’s the BPLs that are breaking things. (Either that or you’ve got something completely different going on.)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 1,338 other followers

%d bloggers like this: