If a report shows addresses but no useful names, distinguish two jobs before changing settings:
tracing finds return addresses on a thread's stack; symbol lookup maps each address to a
module, routine and source line. ECallStack does the first job and EDebugInfo coordinates the
second. A successful trace does not prove that matching debug information is available.
For one location, use CurrentAddress or ReturnAddress with a lookup (section 11.2). For the
current thread's whole stack, use GetCurrentCallStack (section 11.4). Capture another thread only
when needed: suspension has real deadlock risks (section 11.5). Use DebugDetail to judge how much
of a resolved location is actually known, rather than assuming a line number will always exist.
The examples require a correctly processed EurekaLog build and matching symbols for useful source names. Chapters 2 and 4 cover preserving and locating those symbols; this chapter covers the runtime API.
11.1 How a call stack is represented
A stack object is a list you can traverse through Count and Items. Each item carries a
location and a thread ID. Tracing populates addresses; optional debug lookup fills the location's
names and line number. Turn lookup off when you need raw addresses only.
The unit publishes four classes, one of which is the base, plus two type aliases and a metaclass:
TEurekaBaseStackList = class(TCheckedInterfacedPersistent); // base, abstract
TEurekaStackListV6 = class(TEurekaBaseStackList); // EurekaLog 6 method
TEurekaStackListV7 = class(TEurekaBaseStackList); // EurekaLog 7 RAW method
TGenericStackList = class(TEurekaBaseStackList); // arbitrary tracing methods
TEurekaCallStack = TEurekaStackList; // ECallStack
TEurekaStackList = TEurekaStackListV7; // ECallStack
TEurekaCallStackClass = class of TEurekaBaseStackList; // ECallStack
TEurekaBaseStackList is described as "Base call stack class" and as a base abstract class whose instances
you must not create; TGenericStackList, the fourth class, is the common stack tracing class for arbitrary
tracing methods (section 11.3).
Both aliases point at the V7 class: they are "generic name, which will be changed in next EurekaLog
versions. Use this name to refer the latest possible method." The class reference type is what lets a
variable hold "whichever stack class is current" - both the global EurekaCallStackClass and the result of
GetTracerClass have it.
The three stack pointers
These are the raw CPU stack positions, and they are the arguments the constructor later asks for. The CPU stack grows backwards, so their descriptions read inverted, and the documentation says so.
public
class function GetCurrentStackPointer: Pointer; static; // ECallStack - TEurekaBaseStackList
class function GetCurrentTopOfStack: Pointer; static; // ECallStack - TEurekaBaseStackList
class function GetCurrentStackFrame: ETypes.PStackFrame; static; // ECallStack - TEurekaBaseStackList
GetCurrentStackPointer returns the most recent position of the stack: the logical end, physical
beginning. GetCurrentTopOfStack returns the greatest pointer possible: the logical beginning, physical
end. GetCurrentStackFrame is "very similar to GetCurrentStackPointer, except it is adjusted to point to
stack's frame block" - with two notes: on x86-64 use GetCurrentStackPointer instead, and, in bold,
do not use this function to walk through the stack; use Caller or GetTracer.
The fourth position helper supplies the address the stack is missing:
public
class function GetCurrentInstruction: Pointer; static; // ECallStack - TEurekaBaseStackList
GetCurrentInstruction supplies the current execution point in the code that called it. An
ordinary call stack contains callers, not that current instruction; pass this pointer as
AFirstAddr when you want it at the start of the list. Section 11.3 demonstrates that call.
11.2 The addresses you start from
function CurrentAddress: Pointer; // ECallStack
function ReturnAddress: Pointer; // ECallStack
CurrentAddress is a pointer to the currently executing CPU opcode; ReturnAddress is a pointer to the
CPU opcode following the instruction that called the current function - an address inside the caller.
Both are documented for the same two uses: to build a call stack, or to obtain a human-readable description
of a location. These complete helper functions belong in a unit using ECallStack and
EDebugInfo:
function DescribeHere: string;
begin
Result := GetLocationInfoStr(CurrentAddress);
end;
function DescribeCaller: string;
begin
Result := GetLocationInfoStr(ReturnAddress);
end;
DescribeHere resolves an instruction inside itself; DescribeCaller resolves the return location
in its caller. Call them from an ordinary application procedure when investigating the distinction,
or put the lookup directly at the location you want to record. The exact routine, line and offsets
depend on the build and available symbols; a source line such as "42" is not a promised output.
Neither helper captures the location of an earlier exception—use its recorded information for that.
TEurekaStackList.Caller
TEurekaStackList.Caller is another documented way to obtain the caller. This VCL example belongs
in an existing TForm1 with Label1; invoke it through the form's ordinary main-thread button event,
not from a EurekaLog callback. Add these units to the form unit and enable the compiler's
Stack Frames option:
uses
ECallStack, // for TEurekaStackList
EBaseClasses, // for TELLocationInfo
EDebugInfo; // for GetLocationInfo
procedure TForm1.Button1Click(Sender: TObject);
procedure Test;
var
Caller: Pointer;
Info: TELLocationInfo;
begin
Caller := TEurekaStackList.Caller;
Info := GetLocationInfo(Caller);
Label1.Caption := Info.UnitName + '.' + Info.ClassName + '.' + Info.ProcedureName;
// With matching symbols, this identifies the routine that called Test.
end;
begin
Test;
end;
Use EBaseClasses for TELLocationInfo.
You can also pass EurekaCallStackClass.Caller to the stack constructor as its first address.
That chooses a starting code location, not ownership of any exception information object.
It does not establish the assignment semantics of TEurekaExceptionInfo.CallStack (section 8.3).
11.3 Choosing the tracer
EurekaCallStackClass: TEurekaCallStackClass = TEurekaStackList; // ECallStack
DefaultTracerMask: Cardinal = TracerRawEurekaLogMask; // ECallStack
EurekaCallStackClass is the stack class used "when you want to create stack tracing class to get call
stack without knowing current preferences" - the default being TEurekaStackListV7 - and
DefaultTracerMask is the tracing-method mask that class uses. The same warning is printed on both
pages: when you change one of these variables, be sure to change the other as well. The pair is also the
extension point for a custom stack class - the variable can be replaced "to use your own custom call
stacks with all EurekaLog's routines" - and the documentation's verdict on that is not recommended. The
customizing chapter's own list of extension points, dated "as of EurekaLog 7.0.02", names
ECallStack.TracerMethodsClasses for the specific stack classes beside ECallStack.EurekaCallStackClass
for the default one.
function GetTracer(const ATracerNo: Integer): TEurekaBaseStackList; // ECallStack
function GetTracerClass(const ATracerNo: Integer): TEurekaCallStackClass; // ECallStack
GetTracer returns a ready instance for the number you name - created and initialised, with the stack
not built yet. GetTracerClass returns the class, which you then instantiate with Create. The
numbers are TracerXYZ constants of the EStackTracing unit, and the only enumeration the documentation
gives is by name: TracerRawEurekaLogV7, TracerRawEurekaLogV6, TracerRawEurekaLog, TracerRawJCL,
TracerRawMadExcept, TracerLeaksRAW, TracerFramesEurekaLogV7, TracerFramesRecovered,
TracerFramesEurekaLog, TracerFramesJCL, TracerLeaksFrame, TracerWindows, "any other TracerXYZ
value".
Prefer GetTracer over GetTracerClass when selecting a tracer, and use
EurekaCallStackClass.Create when you want the configured default rather than a particular
implementation. Do not select a method by changing the mask arguments to Create or Build.
This function demonstrates the documented TracerWindows selection and includes the current
instruction. Place it in a unit using ECallStack and EStackTracing; it returns text rather than
touching a form, and frees the stack it creates:
function CaptureWindowsStack: string;
var
CallStack: TEurekaBaseStackList;
begin
CallStack := GetTracer(TracerWindows);
try
CallStack.Build(CallStack.GetCurrentInstruction);
Result := CallStack.ToString;
finally
CallStack.Free;
end;
end;
EStackTracing is the unit that "contains actual code that performs stack tracing"; its page says its code
is not to be used directly - always use ECallStack.
public
constructor Create(const ATracerNo: Integer = TracerUndefined); reintroduce; // ECallStack - TGenericStackList
TGenericStackList is the class for tracing methods EurekaLog does not own. The number passed to Create
is kept in its Tracer property, which "holds ATracerNo argument, which was used in Create
constructor"; its own documented list of tracer names is the third-party one (TracerRawJCL,
TracerRawMadExcept, TracerLeaksRAW, TracerFramesJCL, TracerLeaksFrame, TracerWindows). The
warning is unambiguous: "Do not use this class for EurekaLog stack tracing." Like GetTracer, this
constructor builds nothing - GetTracer's own return value is "created and initialized, but call stack is
not build yet" - so you call Build afterwards in either case.
11.4 Creating the list and building it
TEurekaBaseStackList has two constructors. The one that takes arguments both creates and traces.
public
constructor Create(
const AFirstAddr: Pointer;
const AStackFrame: Pointer = nil;
const ATopOfStack: Pointer = nil;
const AStartLevel: Integer = -1;
const ALevelsNumber: Integer = -1;
const ADebugDetails: TEurekaDebugDetails = [ddUnit, ddProcedure, ddSourceCode];
const AThreadID: DWord = 0;
const ARunningThread: Boolean = True;
const AGetDebugInfo: Boolean = True;
const AAllowedMethods: DWord = TracerInvalidMask;
const AStackOffset: PtrUInt = 0;
const AContext: Pointer = nil
); overload; virtual;
constructor Create; overload; virtual;
Create without arguments "will create object instance without building call stack" - you trace on request
with Build - while Create with arguments "will create object instance AND build call stack", and that
overload "is equal to Create + Build". The warning attached to the constructor is a hard one: do not
create instances of TEurekaBaseStackList class. Use EurekaCallStackClass for the default class, or
GetTracerClass for a specific one.
public
procedure Build(
const AFirstAddr: Pointer = nil;
const AStackFrame: Pointer = nil;
const ATopOfStack: Pointer = nil;
const AStartLevel: Integer = -1;
const ALevelsNumber: Integer = -1;
const ADebugDetails: TEurekaDebugDetails = [ddProcedure, ddUnit, ddSourceCode];
const AThreadID: DWord = 0;
const ARunningThread: Boolean = True;
const AGetDebugInfo: Boolean = True;
const AMethods: DWord = TracerInvalidMask;
const AStackOffset: PtrUInt = 0;
const AContext: Pointer = nil
);
Build takes the same twelve arguments, one of them under a different name - AMethods here,
AAllowedMethods in Create. Item by item:
| Argument | Documented meaning |
|---|---|
AFirstAddr |
Code address to add to the stack; nil adds no additional items. When given: must point to a code location, not to the stack, and becomes the first item of the final call stack. |
AStackFrame |
Where tracing starts; nil means use the current stack pointer, i.e. trace the entire stack. If given: must point to the stack, must be a valid stack frame, and must be less than ATopOfStack. |
ATopOfStack |
Where tracing ends; nil means use the current stack limits. Usually left unspecified, "unless you want to build call stack for another (external) thread"; if given, must be greater than AStackFrame. |
AStartLevel |
How many items (levels) to skip from the start; -1 means "do not remove any items". |
ALevelsNumber |
Limits the length of the call stack; -1 means "do not limit call stack". |
ADebugDetails |
Only store locations with this level of detail. |
AThreadID |
A thread id, not a handle, marking the items as belonging to that thread; 0 means the current thread. A target thread "must be a current thread or a suspended thread" - and there is an easier route (section 11.5). |
ARunningThread |
True "when build call stacks manually"; False is used to indicate an error (exception) thread. |
AGetDebugInfo |
False retrieves only RAW pointers, leaving UnitName, ClassName, ProcedureName and LineNumber empty; True fills every possible property - "This works slower." |
AMethods / AAllowedMethods |
Mask of tracing methods; the description names DefaultTracerMask ("which is 'EurekaLog 7 RAW'") as the default, then instructs: "Do not use this argument. Use GetTracer or GetTracerClass functions." |
AContext |
"A pointer to CPU context record. Supply this value when available." |
AStackOffset |
No public semantics are specified - see section 11.9. |
Both constructors are virtual; the published Build declaration has no such modifier.
For an ordinary current-thread capture, the simpler documented entry point is
GetCurrentCallStack. Place this function in a unit using ECallStack:
function CaptureCurrentStack: string;
var
CallStack: TEurekaBaseStackList;
begin
CallStack := GetCurrentCallStack;
try
Result := CallStack.ToString;
finally
CallStack.Free;
end;
end;
This is a snapshot taken when the helper runs, not the saved stack of a previous exception.
No full public declaration is specified for GetCurrentCallStack; use the call form above.
For a single caller location, avoid allocating a whole stack and use section 11.2.
11.5 A thread's own stack, and another thread's stack
Building your own stack is "manual" mode and uses ARunningThread = True. Building a stack for a thread
that is not the caller passes that thread's id, its stack bounds and, ideally, its CPU context:
ATopOfStack is documented as usually specified only in this case, AThreadID as marking the items as
belonging to the specified thread, and AContext as the CPU context record to supply when available. The
documentation warns that the target thread must be the current thread or a suspended thread, and points
at the simpler entry point - "if you want to build a call stack for another (external) thread - there is an
easier way: use the BuildForThread method". BuildForThreadByID is listed beside it.
BuildForThread is the higher-level entry point for an on-demand background-thread snapshot.
The following adapts the documented call form into a function returning text. Place it in a unit
using Winapi.Windows, ECallStack, EStackTracing and ETypes (Windows on older Delphi).
The caller must supply a valid handle and matching ID for a different live thread. The handle
needs THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION and THREAD_SUSPEND_RESUME rights.
Do not pass GetCurrentThread or GetCurrentThreadId; use the current-thread helper instead.
Keep the target and handle valid for the duration of the capture. This function does not own or
close that supplied handle.
function CaptureWorkerStack(const AThreadHandle: THandle;
const AThreadID: DWORD; out WasSuspended: Boolean): string;
var
CallStack: TEurekaBaseStackList;
begin
CallStack := EurekaCallStackClass.Create;
try
CallStack.BuildForThread(AThreadHandle, AThreadID,
nil, // no additional first address
[ddUnit, ddProcedure, ddSourceCode], // requested detail levels
True, // resolve debug information
False, // do not show begin calls
TracerRawEurekaLogV7Mask, // documented explicit tracer choice
True, // running-thread capture
@WasSuspended);
Result := CallStack.ToString;
finally
CallStack.Free;
end;
end;
WasSuspended reports whether the thread was suspended before the capture. It is not a promise
that the thread remains stopped afterwards: the background thread continues to run, so its live
stack can already differ from your snapshot. Use the returned text on the UI thread if you want
to display it; collecting it does not grant permission to access a VCL control from a worker.
The parameterless constructor avoids tracing the current thread only to replace that stack
immediately. BuildForThreadByID is the variant that needs no handle, but no full public
declaration is specified for it. Use the BuildForThread call above rather than guessing
arguments for that variant. For a full error report about one thread rather than a stack string,
use RaiseFreezeException (chapter 14).
There is also a passive route: turn on "Capture stack of RTL threads" or "Capture stack of Windows threads"
and background threads' stacks arrive inside the same .CallStack property of the exception information
object (chapter 8) - the exception thread's items first, the optional calling thread's after them, the items
from other threads distinguished by their .ThreadID, and the exception's own address as the property's
first item. Only the exception thread's stack is captured by default, and the price of the rest is stated
plainly: capturing another thread requires suspending it, which "can cause deadlock issues (for example:
thread may be suspended when it is running memory allocation function; thus, any further memory alloc/release
operation will block application forever). Do not enable this option until really needed."
11.6 From address to location
function GetLocationInfo(const Addr: Pointer): TELLocationInfo; overload; // EDebugInfo
function GetLocationInfo(const Addr: Pointer; var Info: TELLocationInfo): Boolean; overload;
function GetLocationInfoStr(const Addr: Pointer): String; overload; // EDebugInfo
function GetLocationInfoStr(const Addr: Pointer; out Info: String): Boolean; overload;
The first GetLocationInfo overload returns the record; the second writes it through Info and returns
"success / failure state". Inside, "EurekaLog will load debug information (if any available) and look it up
for the specified address" - the input is "a pointer to a code (method, function, line, etc.)", the output
the human-readable description: unit, class, routine, line, offsets. GetLocationInfoStr is the same
lookup as a string, "a simple combination of GetLocationInfo and LocationToStr functions", and the
documentation gives the result's shape as an example worth reading symbol by symbol:
(003BDD73){Project1.exe} [007BDD73] Unit1.TForm1.Button1Click (Line 32, "Unit1.pas") + $5
Read it against the formatter output in section 11.8: (003BDD73) is the offset from the start of the module
and [007BDD73] the absolute address (Offset = Address - Module, and the two columns carry exactly these
two values), {Project1.exe} the module, then unit.class.routine, the line and source file, and finally
+ $5 the offset from the routine.
function LocationToStr(
const AInfo: TELLocationInfo;
const AIncludeModuleName: Boolean = True;
const AIncludeAddressOffset: Boolean = True;
const AIncludeStartProcLineOffset: Boolean = False;
const AIncludeVAddress: Boolean = True;
const AIncludePointer: Boolean = True;
const AIncludeLine: Boolean = True;
const AUseParent: TELUseParent = eupNone
): String;
It "converts location information record into single string, which can be shown to user". Every switch is
documented: AIncludeModuleName the module name, AIncludeAddressOffset the offset from the routine,
AIncludeStartProcLineOffset the offset from the line (the one that defaults to False),
AIncludeVAddress the offset from the module, AIncludePointer the RAW pointer, AIncludeLine the line
number, and AUseParent the information about parent or child units - "for includes only". The
five-False call used throughout the documentation's examples therefore leaves exactly the human part:
'Unit1.TForm1.Button1Click (Line 42, "Unit1.pas")'.
The location record
GetLocationInfo fills a record of the EBaseClasses unit, "A code location information", with a pointer
type beside it:
PELLocationInfo = ^TELLocationInfo; // EBaseClasses
Its fields answer, one by one, the questions the report's call stack columns ask:
| Field | Type | What it holds |
|---|---|---|
Address |
Pointer |
RAW pointer - "the original address to code which was used to obtain information". |
Module |
HMODULE |
Start of the module; the .exe, .dll or .bpl the code belongs to. |
ModuleName |
String |
The module name, as a full absolute file name. |
ModuleType |
TEurekaModuleType |
Kind of module - .exe, .dll or .bpl. |
Offset |
DWORD |
= Address - Module: offset from the start of the module, in bytes. |
UnitName |
String |
Unit symbol name (not file name), for example Unit1. |
SourceName |
String |
File name without path, for example Unit1.pas - "Unit file name (not unit name!)". |
ClassName |
String |
Class symbol name of the method the code belongs to; empty outside a method. |
ProcedureName |
String |
Method name when ClassName is not empty; otherwise the function or procedure name. |
LineNumber |
Integer |
Number of the text line the code belongs to. |
OffsetFromProcName |
Integer |
= Address - StartOfRoutine, in bytes. |
OffsetFromLineNumber |
Integer |
= Address - StartOfLine, in bytes. |
ProcOffsetLine |
Integer |
= LineOf(Address) - LineOf(StartOfRoutine), in lines. |
DebugDetail |
TEurekaDebugDetail |
Level of detail; "it indicates what fields in this record are filled". |
DebugInfo |
TELDebugInfoSource |
The provider instance "which was used to load debug information". |
BinaryFileName |
String |
Full absolute name of the file the debug information came from. "Typically it is the same as ModuleName. It will be different if debug information was loaded from external file (such as .map, .jdbg, etc.)." |
ParentSourceName |
String |
Declared in the record; not described. |
ParentLineNumber |
Integer |
Declared in the record; not described. |
ParentProcOffsetLine |
Integer |
Declared in the record; not described. |
ParentOffsetFromLineNumber |
Integer |
Declared in the record; not described. |
No public semantics are specified for the four Parent* fields. The include-file handling in
section 11.7 defines a parent/child distinction, but does not establish a contract for those fields.
Address and Module are the address the lookup was asked about and the module that contains it -
GetLocationInfo(@Test) fills both without any stack being traced. DebugInfo and BinaryFileName
say where the symbols came from; DebugDetail says how much of the record was filled.
Where the symbols come from
DebugInfo's type, TELDebugInfoSource (= class(TCheckedInterfacedObject)), is the base class for debug
information providers - "a class which can load and parse debug information in one specific format. Debug
info provider can be registered in EDebugInfo unit" (RegisterDebugInfoSource is the registration entry the
customizing chapter names). Child classes "must override at least InitializeSource and GetLocationInfo
methods. Most providers will also override Heuristic method." The providers are separate units, not part of
EDebugInfo itself: that unit "does not implement any support for debug information by itself", so each
format you want has to be included as well - EDebugMAP for .map files, EDebugJCL for JEDI, and so on -
and more than one can be included at a time. One provider ships per format, each in its own unit:
- EurekaLog's own formats:
TELDebugInfoNative(EDebugEL; the native format for the current version, documented as "EurekaLog Debug Info format 8.0.0+", and supporting debug information embedded (injected) in the executable, external.eldbgfiles and additional standalone.elsymfiles) andTELDebugInfoNativeOld(EDebugEL7; the previous native format, "EurekaLog Debug Info format from 4.x up to (including) 7.x", embedded). - Third-party debug formats:
TELDebugInfoJedi(EDebugJCL; JDBG/JclDebug, "JclDebug Debug Info Format v1", embedded or in.jdbgfiles),TELDebugInfoTD32(EDebugTD32; TD32, embedded or in.tdsfiles),TELDebugInfoMap(EDebugMap;.mapfiles - "Only RAD Studio (Delphi and C++ Builder) .map files are supported."),TELDebugInfoMadExcept(EDebugME; "madExcept Debug Info Format v3", embedded or in.madfiles - "Experimental, use with caution. For migration/backward compatibility only."), andTELDebugInfoMab/TELDebugInfoMab2(EDebugMAB,EDebugMAB2; Synopse mORMot "Debug Info Format v1" and "v2", embedded or in.mabfiles, the first one described as obsolete). - Microsoft and heuristic sources:
TELDebugInfoSymbols(EDebugMS) wrapsDbgHelp.dllandSymSrv.dll, reading the formats those DLLs understand (DBG, PDB);TELDebugInfoExportsBase(EDebugExportBase) reads DLL export tables as a heuristic, and bothTELDebugInfoExports(EDebugExports) - the concrete export provider - andTELDebugInfoSymbolsderive from it. DWARF and Stabs -TELDebugInfoDWARF(EDebugDWARF),TELDebugInfoStabs(EDebugStabs) - are documented with a single remark each: "Not implemented yet."
Two warnings a deployment needs. The DbgHelp-based provider "Requires these DLLs to be present/deployed",
and "DbgHelp is a single thread DLL, which means routing all debug info requests to a single thread. Which
can cause a lot of slow down in multi-threaded applications. We don't recommend to use it on production.
Consider converting debug information to some other supported format." The export-table providers are marked
"Heuristic." - that is how they find a name, guessed from what the module exports, not how precise the
result is described as being. A missing answer is described by a different word, the detail level ddModule:
"Code belongs to module, but module does not have debug information, or code lies outside of debug
information scope. Only module info is available."
Detail levels
The ADebugDetails argument of Create and Build and the DebugDetail field of the record come from the
same pair of types - the argument is the set, the field is one member of it:
TEurekaDebugDetails = set of TEurekaDebugDetail; // ETypes
TEurekaDebugDetail = (ddNone, ddModule, ddUnit, ddProcedure, ddSourceCode); // ETypes
| Level | Meaning |
|---|---|
ddNone |
No debug information is available. |
ddModule |
Module exists but has no debug info, or the code lies outside its scope; only module information is available. |
ddUnit |
Unit name is available. |
ddProcedure |
Routine name is available. |
ddSourceCode |
Line number is available. |
AGetDebugInfo = False stops the whole lookup - raw pointers only - while ADebugDetails is the filter
that decides which levels are worth storing. The documentation's examples pass [ddUnit, ddProcedure, ddSourceCode], and a custom formatter used in the customizing chapter filters stack items with
Stack.Items[I].Location.DebugDetail in [ddUnit..ddSourceCode] - the range form that the published
declaration order makes valid.
11.7 Where you are standing: the six helpers
The EDebugInfo unit also answers the current-location question without a pointer. Each helper
combines GetLocationInfo and GetCaller to describe the place it is called from.
function __LOCATION__: String; // EDebugInfo - for example 'Unit1.TMyClass.DoSomething 42'
function __MODULE__: String; // EDebugInfo - for example 'Project1.exe'
function __UNIT__: String; // EDebugInfo - for example 'Unit1'
function __FUNCTION__: String; // EDebugInfo - for example 'DoSomething', or 'TMyClass.DoSomething'
function __FILE__: String; // EDebugInfo - for example 'Unit1.pas', 'Unit1.cpp', 'Code.inc'
function __LINE__: Integer; // EDebugInfo - for example 42
__LOCATION__ returns "human-readable description of the current location", __MODULE__ the executable
module name, __UNIT__ the unit symbol name, __FUNCTION__ the routine name (class-prefixed for methods),
__FILE__ the source file name, and __LINE__ the line number - the only one of the six that is not a
string. For example, the following expression belongs at the point you want to log, in a unit
using EDebugInfo and System.SysUtils:
'Entering ' + __FILE__ + ' ' + __FUNCTION__ + ' ' + IntToStr(__LINE__)
Pass that expression to your logger (chapter 13). The explicit conversion is required:
__LINE__ returns an integer, not text.
Two of the six carry notes about include files and generics, saying the same thing from different
directions. __LINE__: the line number "belongs to source code, not the unit. While these are the same
most of the time, it is not always so. For example, if you add an include file with code, then unit name
will be something like 'Unit1', while line numbers will be from Code.inc for all code inside the include
file. The same is true for generics." __FILE__ makes the mirror-image observation about the source name,
with 'Code.inc' and 'System.Generics.Collections.pas' as examples. Prefer __FILE__ when the
consumer needs the actual source file containing the line rather than the compiled unit's name.
The same include-file problem is what LocationToStr's AUseParent argument addresses; its value type is
documented by three identifiers of the EDebugInfo unit:
| Identifier | Documented meaning |
|---|---|
eupNone |
Use debug information for the non-include file. |
eupParent |
Use debug information for the file with {$I} directive. |
eupChild |
Use debug information for the included file. |
11.8 Turning a stack into text
A TEurekaBaseStackList renders itself, and the documentation gives five options: E.StackTrace (Delphi
2009+) for an exception's stack as a string; CallStack.ToString for the default formatting as one string;
SomeStrings.Assign(CallStack) - the destination receives the stack - for the same into a TStrings; and
then CallStackToString and CallStackToStrings from the ECallStack unit, which take the stack, an
optional header caption and an optional formatter in place of the default one - the first returns the text,
the second writes it into the TStrings you name, and a formatter you construct is yours to free. The
documented call forms are CallStackToString(CallStack), CallStackToString(CallStack, 'Error Details:')
and CallStackToString(CallStack, '', Formatter), and the same three for CallStackToStrings with the
destination TStrings as the second argument (CallStackToStrings(CallStack, Memo1.Lines), and so on).
These are formatting call forms, not standalone programs. They assume a live stack and, for
CallStackToStrings, a live destination TStrings from System.Classes. A Memo1.Lines
destination is appropriate only on that control's UI thread. If the stack came from
TEurekaExceptionInfo.CallStack, do not apply the creator-owned .Free pattern from the capture
helpers above: the property does not document transfer of ownership to your code.
Four formatters are built in: TEurekaStackFormatter (the general fixed-width table with columns, "best to
be used in text files"), TEurekaStackFormatterV6 (the EurekaLog V6 format, "less
columns"), TSimpleStackFormatter (list-like, no columns, for variable-width fonts and message boxes), and
TCompactStackFormatter (the same, more compact and with less details - "good for quick preview"). The
general formatter's output looks like this:
|Methods |Details|Stack |Address |Module |Offset |Source |Unit |Class |Procedure/Method|Line |
|7FFFFFFE|04 |00000000|007CD41D|Project183.exe|0048D41D|Unit1.pas |Unit1 |TForm1 |Button1Click |37[1] |
|00000060|04 |019DF02C|006F5043|Project183.exe|003B5043|Vcl.Controls.pas|Vcl.Controls|TControl |Click |7707[9]|
The Address and Offset columns are TELLocationInfo.Address and .Offset of section 11.6; the unit,
class, procedure, source and line columns are the same record's symbol fields. The full table also
carries a header block above the rows, one per thread, giving ID, Parent, Priority, Class,
Name, DeadLock and Wait Chain. The compact formatter reduces each row to an address and a
location line:
[0132D41D] Unit1.TForm1.Button1Click (Line 37, "Unit1.pas").
Formatters can be replaced, too: the customizing chapter shows a class descending from
TEurekaBaseStackFormatter overriding GetItemText and GetStrings, walking the stack with Stack.Count,
Stack.Items[I], Stack.Delete and Stack.Assign, and reading each item through
CallStack.GetItem(AIndex, Cache) and CallStack.ComposeName(ClassName, ProcedureName) plus .Location and
.ThreadID; the item type it names in its signature is TEurekaDebugInfo (with PEurekaDebugInfo as the
pointer it receives), and the formatter copies the original stack's Formatter and sets its own
CaptionHeader.
11.9 Contract limits
- No public semantics are specified for
AStackOffset. The tracingCreateandBuilddeclarations give it the default0; do not infer its behavior from the name. - The tracing mask's default has two descriptions. The declarations give
TracerInvalidMask, while the parameter contract identifiesDefaultTracerMask("EurekaLog 7 RAW"). Their relationship is not specified. Do not set this argument directly; useGetTracerorGetTracerClass. The twoADebugDetailsdefaults, in contrast, differ only in the order of the same three levels:[ddUnit, ddProcedure, ddSourceCode]onCreate,[ddProcedure, ddUnit, ddSourceCode]onBuild. - Full public declarations are not specified for
GetCurrentCallStack,CallStackToString,CallStackToStrings,Caller,BuildForThreadorBuildForThreadByID. Use the documented call forms in this chapter rather than guessing additional parameters or overloads. - No public item-layout contract is specified for
TEurekaDebugInfoorPEurekaDebugInfo. The stack operationsCount,Items,Delete,Assign,ToStringand the formatter operations described above do not establish a complete contract for extending those internals. ETypes.PStackFrameis given as the return type ofGetCurrentStackFrameand described only as pointing to stack data "which points to current stack frame".- DWARF and Stabs providers are documented as "Not implemented yet", and the DbgHelp-based one with a recommendation against production use in multi-threaded applications.
For ordinary applications, these gaps are not reasons to write a custom tracer or provider. Use the documented capture forms, retain matching symbols, and inspect the reported detail level. If the report still lacks usable frames, follow the build/symbol workflow in chapter 2 and the diagnostic sequence in chapter 5 before replacing tracing internals.