3

I am trying to look up the unit name and function name in the "Detailed" map file that is produced by building a project in Delphi 5. I found some code online that claims to do this but I can't make it work.

Code Requirements:

  • Delphi 5
  • One TForm
  • One TButton
  • One TEdit
  • One TLabel

Goal: The function: 'Log' is supposed to return the address of the calling procedure. Once the address has been determined the unit and function names as well as line number can be looked up in the map file.

Purpose: Wouldn't it be nice... if the name of a function could be obtained just by calling 'Log' from anywhere in a program.

Reality: I am really interested in learning what is going on in the 'Log' function, and why or why not it is working, secondary to this would be an alternitave way of returning the unit and function name as well as line number of a calling procedure.

The Problem: The address I get from TForm1.Button1Click > TForm1.Log > TForm1.LogAddress > TForm1.ShowInfo does not coincide with the corresponding items in the map file I am looking for i.e. M=Unit1, TForm1.Button1Click, etc...

Websites: http://www.haydenr.com/delphi/articles/article002.htm , http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_22596248.html The last website may not be accessible from the link -- I put the Google search string in the 'Log' function.

Delphi Code:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
     Button1: TButton;
     Label1: TLabel;
     Edit1: TEdit;
     procedure Button1Click(Sender: TObject);
     Procedure Log;
     Procedure ShowInfo(hexAddress : Integer);
     Procedure LogAddress(ptr: pointer);
  private
     { Private declarations }
  public
     { Public declarations }
  end;

var
  Form1: TForm1;

Implementation

{$R *.DFM}

    Procedure TForm1.Log;
    // Google Search: capturing a procedure or function's name for logging purposes
    // http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_22596248.html
    Begin
        ASM
             pop EAX
            push EAX
            Call LogAddress
        End;//ASM
    End;

    Procedure TForm1.ShowInfo(hexAddress: Integer);
    // http://www.haydenr.com/delphi/articles/article002.htm
    Var
        iMapFileAddress : Integer;
        sMapFileAddress : String;
        ImageBase        : Integer;
        SubOffset        : Integer;
        Offset           : Integer;
    Begin
        ImageBase := $00400000; // Project > Options... > Linker Tab > Memory sizes group box > Image Base
        SubOffset := $1000;
        Offset    := ImageBase + SubOffset;

        iMapFileAddress := hexAddress - Offset;
        sMapFileAddress := IntToHex(iMapFileAddress,8);
        Edit1.Text := sMapFileAddress; //This is the value I get: sMapFileAddress = 00542214
        {
         Here are some excerpts from: Project1.map
         |Detailed map of segments
         | 0001:00040498 000002D4 C=CODE     S=.text    G=(none)   M=Unit1    ACBP=A9
         | 0001:0004076C 000001A5 C=CODE     S=.text    G=(none)   M=Project1 ACBP=A9
         |   :
         |   :
         |   :
         |  Address         Publics by Name
         |
         | 0001:0004071C       TForm1.Button1Click
         | 0001:00040668       TForm1.Log
         | 0001:00040700       TForm1.LogAddress
         | 0001:0004067C       TForm1.ShowInfo
        }
    End;

    procedure TForm1.LogAddress(ptr: pointer);
    begin
      ShowInfo(Integer(ptr));
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    begin
        Log;
    end;

end.
Ken White
  • 123,280
  • 14
  • 225
  • 444
Mark Davich
  • 512
  • 1
  • 5
  • 16

2 Answers2

2

You're not calling LogAddress properly. As a method of TForm1, it's really a two-argument function. The two arguments are Self and ptr, in that order, which means that register EAX is Self and EDX is ptr. However, you're passing the address parameter in EAX, and you're doing nothing about EDX. That means you're looking up the name of who-knows-what, which probably isn't the address of any code section in your program. On top of that, the Self value isn't valid anymore, so any references to member variables (such as Edit1) won't work; you'll get an access violation or some form of memory corruption.

Change the Log method to this:

procedure TForm1.Log;
asm
  // fetch return address from top of stack
  mov edx, [esp+4]
  call LogAddress
end;

Keep in mind that the address you're passing in the ptr parameter is the return address of the Log call. In practice, that's usually the address of the instruction after the CALL instruction used to get into the Log method. Therefore, you should not expect to find that exact address in the list of functions in the map file. Instead, you'll need to look for the function whose address is the closest without exceeding the address you have. Furthermore, if you convert that address to a line number, it will likely be the line after the actual call site.

The JclDebug unit, in the JCL, already has functions that will do this for you. You can use the ProcByLevel function to get the name of the caller and LiveByLevel to get the line number.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • It did back when Delphi 5 was remotely current, @Ken. Nowadays, it might be necessary to check out an older revision rather than using the trunk. – Rob Kennedy Feb 06 '15 at 21:32
  • Yeah, I knew it did a decade and a half ago. I just wasn't sure it still did now. (Nothing else remotely supports Delphi 5; almost everything I'm aware of requires at least D7.) – Ken White Feb 06 '15 at 21:35
  • Thanks Rob, I made the changes you suggested, the value (address) that populates Edit1 is now: FFD2E40C, which still does not reside in any of the address ranges in Project1.map. For example the address of Unit1 is between 40498-4076C (Integer: 263320-264044) the value FFD2E40C is 4292011020 which is well out of the address range. Maybe I'm not interpreting the map file correctly? – Mark Davich Feb 06 '15 at 21:44
  • @Ken, the JCL dropped support for Delphi 5 around early 2010. That's when [my contribution](https://gist.github.com/rkennedy/4566675) was removed. – Rob Kennedy Feb 06 '15 at 21:47
  • Mark, when your program is running, what is the *actual* address of the `Button1Click` method? (To determine this myself, I'd probably set a breakpoint at the `Log` call, go to the CPU debugger window, and check the addresses displayed alongside the disassembly view. The address of the `end` instruction is what you *should* get later in EDX.) How does that address compare with what you see in the map file? – Rob Kennedy Feb 06 '15 at 21:51
  • Mark, while you're in the CPU window, you can also step in to the `Log` function and check the values in the stack. For example, confirm that the value being read from ESP+4 is the right value — maybe I've gotten the offset wrong. Then step into `LogAddress` and confirm that the value in `ptr` is the same as the value you found in `Button1Click`. – Rob Kennedy Feb 06 '15 at 21:55
  • Rob, I am not familiar with the CPU debugging window. I put a break point at the "Begin" in Button1Click, on the CPU window I see an address of "0044171C 55" in the top left window??? – Mark Davich Feb 06 '15 at 22:02
  • Yep. That's the address (0044171C) and the machine-code representation of the instruction (55, for `push ebp`). You should see the assembler representation next. Subtracting 00400100 yields 0004071C, which is exactly what we see in the map file. That's good. Next, I'd look at the disassembly of `Log` and check whether the compiler has inserted any instructions for the `asm` line; if it has added a function prologue, then the `mov edx, [esp+4]` instruction will be wrong. Check whether the prologue pushes anything else onto the stack, and adjust the ESP offset accordingly. – Rob Kennedy Feb 06 '15 at 22:26
  • Rob, thanks for working with me on this, very helpful. – Mark Davich Feb 06 '15 at 22:44
1

After making the code replacements and adjusting the "esp offset" as per Rob Kennedy's suggestions the working code follows...

In order to produce a "Map File"; in the Delphi IDE follow these steps:

  1. Project > Options... > Compiler Tab > Code generation pane: Optimization = FALSE

  2. Project Options Window > Linker Tab > Map file pane: Select "Detailed"

  3. Project > Build All Projects

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
      StdCtrls;
    
    type
      TForm1 = class(TForm)
         Button1 : TButton;
         Label1  : TLabel;
         Edit1   : TEdit;
         procedure Button1Click(Sender: TObject);
         Procedure Log;
         Procedure ShowInfo(hexAddress : Integer);
         Procedure LogAddress(ptr: pointer);
      private
         { Private declarations }
      public
         { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    Implementation
    
    {$R *.DFM}
    
        Procedure TForm1.Log;
        // Google Search: capturing a procedure or function's name for logging purposes
        // http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_22596248.html
        Begin
            ASM
                // fetch return address from top of stack
                 mov edx,[esp+8]
                call LogAddress
            End;//ASM
        End;
    
        Procedure TForm1.ShowInfo(hexAddress: Integer);
        // http://www.haydenr.com/delphi/articles/article002.htm
        Var
            iMapFileAddress : Integer;
            sMapFileAddress : String;
            ImageBase       : Integer;
            SubOffset       : Integer;
            Offset          : Integer;
        Begin
            ImageBase := $00400000; // Project > Options... > Linker Tab > Memory sizes group box > Image Base
            SubOffset := $1000;
            Offset    := ImageBase + SubOffset;
    
            iMapFileAddress := hexAddress - Offset;
            sMapFileAddress := IntToHex(iMapFileAddress,8);
            Edit1.Text := sMapFileAddress; //This is the value I get: sMapFileAddress = 00040730
            {
             |Here are some excerpts from: Project1.map
             |*******************************************************************************************************************
             |Detailed map of segments
             | 0001:00040498 000002D4 C=CODE     S=.text    G=(none)   M=Unit1    ACBP=A9
             | 0001:0004076C 000001A5 C=CODE     S=.text    G=(none)   M=Project1 ACBP=A9
             |   :
             |   :
             |   V
             |  Address         Publics by Name
             |
             | 0001:0004071C       TForm1.Button1Click
             | 0001:00040668       TForm1.Log
             | 0001:00040700       TForm1.LogAddress
             | 0001:0004067C       TForm1.ShowInfo
             |   :
             |   :
             |   V
             |Line numbers for Unit1(Unit1.pas) segment .text
             |
             |    82 0001:00040728    83 0001:00040730    85 0001:00040764    85 0001:0004076B
             |*******************************************************************************************************************
             |
             |     Returned Hex (H) = 00040730
             | Returned Integer (I) = 263984
             |-------------------------------------------------------------------------------------------------------------------
             |Address Of                Min Hex     Max Hex     Min Integer    Max Integer  |                Evaluation
             |------------------------------------------------------------------------------|------------------------------------
             |Unit1                     00040498    000406E5    263320         263909       | Hmin < H < Hmax   AND   Imin < I < Imax
             |Function/Procedure                    0004071C                   263964       |        H < Hmax   AND          I < Imax
             |Line Number                           00040730                   263984       |        H = Hmax   AND          I = Imax
             |-------------------------------------------------------------------------------------------------------------------
             |
             |NOTE: The actual line number where 'Log' is called is: Returned Line Number - 1 (83 - 1 = 82)
            }
        End;
    
        procedure TForm1.LogAddress(ptr: pointer);
        begin
          ShowInfo(Integer(ptr));
        end;
    
        procedure TForm1.Button1Click(Sender: TObject);
        begin
            Log;
        end;
    
    end.
    
Mark Davich
  • 512
  • 1
  • 5
  • 16