Reach for ELowLevel when you have a specific boundary to inspect: stop only when a debugger is
attached, allocate a page-aligned buffer without losing its owning pointer, identify the module
calling EurekaLog, or investigate a damaged stack. These helpers do not replace application
ownership, synchronization or ordinary exception handling.
Choose the smallest operation that answers the question. The most dangerous mistakes are using a successful pointer probe as permission to dereference later, freeing an aligned view instead of its backing allocation, and treating stack recovery as recovery of the interrupted application's state. The examples below keep those boundaries visible.
DebugBreak, MemWipeStack and the stack-cookie pair also appear in chapter 6, sections 6.6 and 6.8.
This chapter adds their preconditions and the related low-level APIs.
18.1 The unit itself, and the one compatibility routine worth knowing
Warning for anyone editing EurekaLog's source rather than calling it: do not add units to
ELowLevel's uses clause or add an initialization/finalization section. If you depart from
these rules, ensure that there is no memory allocation during startup.
IfElse
function IfElse(const AOK: Boolean; const AStr1: String; const AStr2: String = ''): String; // ELowLevel
IfElse replaces the ternary conditional operator: AStr1 when AOK is True, AStr2 when it is
False, the last parameter defaulting to an empty string. Its Warning is the reason to mention it:
"Both arguments (AStr1 and AStr2) are calculated" - unlike the C++ AOK ? AStr1 : AStr2 - so it
cannot guard the evaluation of the branch not taken.
For example, IfElse(Assigned(Obj), Obj.ClassName, 'none') is not a safe nil check: the
Obj.ClassName expression is evaluated even when Obj is nil. Use an ordinary if:
if Assigned(Obj) then
Description := Obj.ClassName
else
Description := 'none';
Here Obj is a TObject reference whose lifetime the caller already controls, and Description is
a String. Assigned does not make a freed or concurrently destroyed object safe.
18.2 Breaking into the debugger
IsDebugged
function IsDebugged: Boolean; // ELowLevel
Whether the calling process is being debugged by a user-mode debugger: True when it "is running in
the context of a debugger". The documented purpose is behaviour that depends on being watched - for
instance to "provide additional information using the OutputDebugString function if it is being
debugged".
Use the guard at a deliberate diagnostic checkpoint in ordinary application code. With ELowLevel
in the unit's uses, the complete statement is:
if ELowLevel.IsDebugged then
ELowLevel.DebugBreak;
DebugBreak raises an exception; it is not logging and should not be inserted unconditionally in a
deployed failure path. The debugger is the intended consumer of that stop.
18.3 Wiping the unused stack
CanCallMemWipeStack
function CanCallMemWipeStack(const ASkipEurekaLogActivationCheck: Boolean = False): Boolean; // ELowLevel
Indicates whether MemWipeStack should be called. Checking it before wiping is highly recommended.
It answers True only when the "Wipe Stack" option is enabled, the current thread has
EurekaLog enabled, that thread "is not about to exhaust the stack space", and it "is not in a 'weird'
state (for example: invalid StackLimit value)". That third condition is a safety rail: a thread near
the end of its stack is exactly the thread on which writing to the unused stack is a bad idea.
ASkipEurekaLogActivationCheck defaults to False; keep that default unless you have verified an
implementation-specific reason to override it. In an existing idle handler on the thread whose
unused stack you intend to wipe, with ELowLevel in uses, use:
if CanCallMemWipeStack then
MemWipeStack;
What MemWipeStack writes ($CCCCCCCC), why that helps, and the caution that it clears "the entire
unused stack to the end" and so should not be called too frequently: chapter 6, section 6.6.
18.4 Stack guarantee, overflow recovery, top of the main stack
_ThreadStackGuarantee
_ThreadStackGuarantee: Cardinal; // ELowLevel, global variable
"The minimum size of the stack associated with the calling thread or fiber that will be available
during any stack overflow exceptions" - the reserve that lets exception handling run when the stack has
already been exhausted. Its value is "the size of the stack, in bytes", and it "cannot be larger than
the reserved stack size". It "is used by the EnsureThreadStack function" and "is set to your
EurekaLog project options on startup"; a caller that wants a different reserve sets this variable and
then calls EnsureThreadStack, which applies what it finds here.
EnsureThreadStack
function EnsureThreadStack: Boolean; // ELowLevel
Applies that reserve to the calling thread or fiber, so that "the application can safely use the
specified number of bytes during exception handling". True on success, False on failure - "to get
extended error information, call GetLastError" - and for a fiber "you must first call the
SwitchToFiber function to execute the fiber".
Apply the guarantee on the thread that needs it, before stack-intensive work. A successful call
reserves handling capacity; it does not enlarge the application's ordinary work budget or fix
unbounded recursion. _ThreadStackGuarantee is global configuration, while the operation affects the
calling thread/fiber: do not race workers that keep changing the global to different values.
_resetstkoflw
function _resetstkoflw: Boolean; // ELowLevel
Recovers from a stack overflow. A thread that raises EStackOverflow "has left its stack in a damaged
state" - unlike EAccessViolation or EDivByZero - and the overflow has consumed the guard page at
the end of the stack. Without this call "there are no guard pages after the previous exception", so the
next overflow has "no exceptions at all and the process terminates without warning".
The return value has to be checked: the function "can fail even if used in a correct location", and "safe usage of this function should include checking the return value instead of assuming that the stack is safe to use".
It "shouldn't be called from" an except block, a finally block, or "any type of exception hook or
filter function", because at those points "the stack isn't yet sufficiently unwound". Call it only
after exiting the except block, using "as little stack space as possible" inside it meanwhile.
Catch EStackOverflow, set a flag, leave the handler, then call _resetstkoflw outside it.
Keep the handler minimal: set the flag, leave the handler, check _resetstkoflw, and stop using the
thread if recovery fails. Do not format a report, show a dialog or retry the overflowing operation
inside that handler. Even a successful reset only restores the guard-page condition; it does not
roll back partially updated data or establish that application-level recovery is safe.
MainThreadTopOfStack
function MainThreadTopOfStack: Pointer; // ELowLevel
"Returns top of the stack for the main thread" - where a hand-written stack walk has to stop. For a DLL, "main thread is a thread which loaded DLL".
18.5 The stack guard cookie pair
__initialize_security_cookie and __security_check_cookie
procedure __initialize_security_cookie(out ASecurityCookie: Pointer); // ELowLevel
procedure __security_check_cookie(var ASecurityCookie: Pointer; const ALocation: PChar = nil); // ELowLevel
The pair itself - what the cookie is, that it must be the first local variable, and what a changed
cookie means - is chapter 6, section 6.8. Three further details matter: what ALocation
is for, what the failure costs, and why the report can be short.
ALocation is "an optional human-readable description of the caller. For example, a function's name",
used "only if stack corruption is detected and an error is thrown"; the example fills it with nothing,
with 'Unit1.DoSomething', or with PChar(__LOCATION__), which "uses EDebugInfo, not recommended".
The failure is terminal in a defined way: a changed cookie triggers a EurekaLog crash report, and
with EurekaLog disabled a run-time error is raised instead; unhandled, it terminates "the process ...
with $C0000409 (STATUS_STACK_BUFFER_OVERRUN) exit code". The pair also protects the return address:
if it changes between the two calls, an error is triggered.
The report may be short: it "may not display full call stack - that is because stack corruption will damage stack frames and return addresses". Because corruption inside a sub-routine may prevent the return to the caller, the page recommends putting the pair "in all available routines".
18.6 Memory pages: size, granularity, a page-aligned block
PageSize and AllocationGranularity
function PageSize: Cardinal; // ELowLevel - inline
function AllocationGranularity: Cardinal; // ELowLevel - inline
PageSize returns "current memory's page size" in bytes - "Default is 4Kb (4096)" - "the page size and
the granularity of page protection and commitment" used by VirtualAlloc: the dwPageSize field of
GetSystemInfo. AllocationGranularity returns "current memory's allocation granularity" in bytes -
"Default is 64 Kb (65'536)" - "the granularity for the starting address at which virtual memory can be
allocated". They answer different questions; do not assume either number is fixed across platforms.
AllocatePageAlign
function AllocatePageAlign(const ASize: Cardinal; out AHolder: Pointer): Pointer; // ELowLevel
Allocates ASize bytes aligned on the page-size boundary - "the same as AllocMem except it also
aligns result on memory's page size boundary" - so on a 4 Kb page the result always has "12 zeros at
low part of pointer when represented in binary". AHolder is the "pointer to backbone storage. This is
the pointer which must be passed to FreeMem to release this memory block."
The free rule is stated twice, and it is the mistake this function invites: "You shouldn't pass
returned pointer to any memory releasing routines (like FreeMem). Instead, call FreeMem for
AHolder." The cost is documented rather than implied ("an overhead of maximum 2 size of memory's
page"), it raises System.SysUtils.EOutOfMemory, and a closing Note warns that "page size may be
different on different platforms."
Keep the view and the owner separate
This complete Delphi procedure demonstrates the lifetime rule. Put it in an ordinary diagnostic
unit whose implementation uses ELowLevel and SysUtils. The returned address is the usable view;
Holder alone owns the storage. The procedure checks alignment, writes one byte inside the
requested page, then releases the backing block even if the check raises.
procedure ExercisePageAlignedBuffer;
var
Buffer, Holder: Pointer;
BytesPerPage: Cardinal;
begin
BytesPerPage := PageSize;
Buffer := AllocatePageAlign(BytesPerPage, Holder);
try
if (NativeUInt(Buffer) mod BytesPerPage) <> 0 then
raise Exception.Create('Unexpected buffer alignment');
PByte(Buffer)^ := $5A;
finally
FreeMem(Holder);
end;
end;
The example targets Delphi versions with NativeUInt; the API signature itself is shown above.
Do not call FreeMem(Buffer), do not return Buffer after freeing Holder, and do not reallocate
the view with an ordinary allocator. If another component retains the view, its ownership protocol
must also keep the holder alive until that component has finished.
18.7 Reserving the lower address space
ReserveLowerMemory
procedure ReserveLowerMemory; // ELowLevel
Reserves "lower 2 Gb (32-bit app) or 4 Gb (64-bit app)" of address space, so allocations come "from higher addresses, thus helping you to catch bugs in your code (like Integer-Pointer conversion, using signed Integers, etc)" - the class of bug that hides while addresses happen to be low. It "does not allocate any memory. Address space is simply reserved" - but "the available address space will be decreased, so your code may run out of memory". Treat it as a deliberate pointer-truncation diagnostic, not an out-of-memory remedy. The documentation recommends it for 64-bit applications with ample address space, but the same reservation still changes the environment you are measuring. For 32-bit builds:
- Warning: "Do not call this function in a 32-bit app without marking your app as 'large address awared'. Otherwise your application will have no memory to run!"
- Caution! a 32-bit large-address-aware application "will have just 1 Gb (when run on 32-bit system) or 2 Gb (when run on 64-bit system) of memory after calling this function".
The recommendation follows: call it "for 32-bit apps only when debugging, not in production".
18.8 Checking a pointer or a handle before you touch it
These calls describe what memory or a handle looked like a moment ago. They are diagnostic heuristics, not an ownership protocol and not a security boundary for untrusted pointers. Prefer an API that supplies a valid lifetime and length, or hold the lock/reference that prevents release. Adding a probe before every dereference cannot repair use-after-free or a race.
GetReadableSize
function GetReadableSize(const AAddress: Pointer; const ASize: Cardinal; const AAttributes: Cardinal = 0): Cardinal; // ELowLevel
"Verifies that the caller has specified access to the specified range of memory". If the caller has the
specified read access to all bytes in the range the result is ASize; otherwise it is less than
ASize and "may be 0"; a zero ASize returns zero. AAttributes is "a set of PAGE_XYZ flags, which
indicate access to check" - 0, the default, means any read access - and "by passing appropriate flags,
this function can be used to verify write access to memory".
One remark is worth reading twice: "Dereferencing potentially invalid pointers can disable stack expansion in other threads", and a thread exhausting its stack afterwards terminates the parent process "with no pop-up error window or diagnostic information".
Warning: "Use this routine with caution. It's heuristic. Even when the function indicates that the process has read access to the specified memory, you should use structured exception handling when attempting to access the memory - because other thread may release this memory between checking and actual access."
IsValidBlockAddr and IsValidCodeAddr
function IsValidBlockAddr(const Address: Pointer; const Size: Cardinal): Boolean; // ELowLevel
function IsValidCodeAddr(const AAddress: Pointer; const ASize: Cardinal): Boolean; // ELowLevel
The boolean wrappers over GetReadableSize, differing in the access they ask for: read for
IsValidBlockAddr, execute for IsValidCodeAddr. In both, "if this parameter is zero, the return
value is True" - a zero size is valid whatever the address. The pointer-validity caveats above still
apply. IsValidCodeAddr answers the narrower question - may the CPU execute
the bytes there - which is the access a disassembler or a hook needs; it says nothing about whether
those bytes are a valid instruction, and GetReadableSize with the right flags is the call that asks
about write access.
IsValidClass
function IsValidClass(ClassType: TClass): Boolean; // ELowLevel
"Verifies that the specified pointer is a valid class": True if ClassType "is a real class". The
remarks say only that it "performs many complex checks" - Warning: "Use this routine with caution.
It's heuristic."
IsValidObject
function IsValidObject(const AObj: TObject; const AStrict: Boolean): Boolean; // ELowLevel
Checks whether the supplied address appears to be a valid object, using multiple heuristics.
The parameter prose describes AStrict as optional with default True, but the published declaration
has no default: pass the Boolean explicitly. With True, a valid Delphi 2007 object checked from
Delphi 2009 can fail the version-sensitive check. With either value, a successful check is not
permission to call methods without owning the object's lifetime.
IsValidHandle
function IsValidHandle(const AHandle: THandle): Boolean; // ELowLevel
"Verifies that this is a valid kernel handle", using "GetHandleInformation and DuplicateHandle to
verify handle", so the answer comes from the kernel rather than from guesswork about pointers. Its
Warning still applies - "Use this routine with caution. It's heuristic" - and it adds what the
memory checks cannot say: "a handle may be immediately closed by another thread."
Two further checks - IsValidRunningThread and IsValidSymbolName - appear as bare names in the "see
also" lists of the IsValid* pages, without pages of their own, so nothing more about them can be
reported here.
18.9 Files and folders, before you write to them
IsReadableFile, IsWritableFile and IsWritableFolder
function IsReadableFile(const AFileName: String): Boolean; // ELowLevel
function IsWritableFile(const AFileName: String): Boolean; // ELowLevel
function IsWritableFolder(const AFolderName: String; const ACanCreateFolder: Boolean = True): Boolean; // ELowLevel
Both file checks work by opening the file - with GENERIC_READ and GENERIC_WRITE respectively - and
answer whether that succeeded; the parameter is "a full absolute file name". IsReadableFile "returns
False, if file doesn't exist at all", so a caller cannot tell "not there" from "no permission".
IsWritableFile does more than a plain open: "If file doesn't exists, function tries to create it.
However, file is deleted after the check".
IsWritableFolder "verifies that the caller has write access to the specified folder" by writing rather
than by asking about permissions: it "tries to create a test file in the specified folder", and answers
False when the folder does not exist and ACanCreateFolder is False. That parameter defaults to
True - worth turning off when the answer should concern an existing folder only.
Use these probes to improve diagnostics, not to promise the subsequent open will succeed.
IsWritableFile can create and delete a file; IsWritableFolder can create a test file and,
with the default argument, a folder. For a report destination that must already exist, call
IsWritableFolder(TargetDirectory, False) and still handle failure from the real report/file write.
Do not run a write probe on a path where those side effects are unacceptable.
18.10 Addresses, return addresses, and stack frames
ConvertAddress
function ConvertAddress(const AAddr: Pointer): Pointer; // ELowLevel
Converts "address to code to real address of code", because an address you hold "can points to code
itself or to JMP instruction to code. Typical example is import table." The rule is two cases: "If there
is no JMP - then function returns value unchanged"; "If there is JMP opcode - then function returns
target of that JMP". The result "can be the same value as AAddr", so a caller need not distinguish a
direct code address from a stub before calling, and the function "doesn't raise access violation in
case of unavailable memory".
ConvertRetAddr
function ConvertRetAddr(const AReturnAddr: Pointer): Pointer; // ELowLevel
Converts a return address into "instruction pointer" - the address of the CALL that produced it - with
the result "always less than AReturnAddr". A return address points to the instruction after the
call, "the next line in source file", so the raw value does not point at the call; the converted value
does. The parameter comes "from analyzing call stack or via ReturnAddress" (chapter 6, section 6.7).
The Warning is the difference between it and ConvertAddress: "Function does NOT check if the
AReturnAddr is a valid return address. Do not pass arbitrary pointers, pass return addresses only."
IsStackFramesProc and IsRTLCompiledWithStackFrames
function IsStackFramesProc(const AAddr: Pointer): Boolean; // ELowLevel
function IsRTLCompiledWithStackFrames: Boolean; // ELowLevel
IsStackFramesProc "determines whether the specified function has a stack frame" - the question "can
this procedure be walked the simple way?" - and its input has to be a real function: "Make sure that you
pass a pointer to a real function. Not to some arbitrary code or function's stub".
IsRTLCompiledWithStackFrames asks the same about the whole RTL. Both pages close with the same
Warning: "This is heuristic function."
18.11 Reading the instruction at an address: TInstruction and EDisAsm
ConvertAddress and ConvertRetAddr answer "where does this point"; EDisAsm answers "what is
written there". Its unit page calls it "disassembler for current CPU", used by EurekaLog "to
disassemble machine code for bug report", and warns that it "does not hold any specific
implementation. It is just a stub to other units" - the per-CPU code lives in EDisAsmX8632,
EDisAsmX8664 and so on.
TInstruction
The common fields are listed below rather than presented as a compilable declaration: the actual
TInstruction record is platform-specific and must come from EDisAsm. Do not redeclare it locally.
"A CPU instruction" - "This platform-specific record represents single CPU instruction. Exact fields of the record depends on target CPU."
Start- "Start of the CPU instruction", including "any prefixes".Next- a pointer to "end of current CPU instruction and start of the next (following) CPU instruction"; the length in bytes isNextminusStart.Accessible- "Is instruction readable?" - it reports whetherStartis readable, and reading it whenFalseraises an Access Violation.Valid-Truewhen the instruction "was decoded successfully"; it gates the rest, since "all platform-specific fields inTInstructionrecord are valid only whenValidisTrue".- CPU-specific fields - x86-32 includes fields such as
Prefix66,OpCodeSignificantByte,OperandSizeandModRMPresent. There is no realPlatformSpecificfield.
Two independent conditions, then the extent: Accessible guards reading the memory at Start, while
Valid says the bytes there were decoded, and Start with Next gives the instruction's span.
PInstruction = ^TInstruction is the matching pointer type.
For a bounded disassembly task, start from a known live code address, decode one instruction, check
Accessible and Valid, then inspect it or advance to Next. Bound the number of instructions or
address range independently; a readable page is not proof that it contains an entire function.
Stop on failure rather than interpreting invalid CPU-specific fields.
DecodeInstruction
function DecodeInstruction(Address: PAddress; const AExternalProcess: Boolean = False): TInstruction; // EDisAsm
"Decodes a single CPU instruction" into that record: "prefixes, opcode, MOD/SIB bytes, displacement and
immediate values - plus evaluated information: such as pointer to next instruction, flags, sizes of
various data, etc." The result "can be used 'as is' or it can be converted into textual representation
by InstructionToString". Of the two parameters printed, only Address - "a pointer to CPU
instruction to decode" - is described.
InstructionToString
function InstructionToString(const AInstr: TInstruction; const AEncrypt: Boolean = False): String; // EDisAsm
"Converts decoded CPU instruction to string representation", with an output documented field by field - the address ("always 8 hex characters for 32-bit process and 16 hex characters for 64-bit process"), the raw bytes, the instruction itself and an optional comment, "such as call/jmp destination or string data dump":
006989A2 E805C2F8FF CALL -$073DFB ; ($00624BB0) Unit1.TForm1.CheckVisible
DisasmInstruction
function DisasmInstruction(var Address: PAddress; const AEncrypt: Boolean = False): String; // EDisAsm
The one-shot version: it "disassembles a single CPU instruction, returns its textual representation and
advances Address to the next instruction", which "can be used to walk through code while disassembling
it". It is "a simple combination of low-level DecodeInstruction and InstructionToString".
GetAsmSize and GetFunctionSize
function GetAsmSize(const AStart: Pointer; var ASize: Byte): Boolean; // EDisAsm
function GetFunctionSize(const AAddr: Pointer; const AMaxSize: Cardinal): Cardinal; // EDisAsm
GetAsmSize "returns size of a single machine opcode": True if the operation succeeded - the size in
bytes then arrives through the Byte parameter ASize - and False otherwise.
GetFunctionSize "returns size of the function in bytes" by scanning - it "stops at first ret
instruction and returns distance between AAddr and end of ret" - and the result is "always in range
0..AMaxSize", AMaxSize existing to "limit scan area and prevent unlimited reading".
Both raise System.SysUtils.EAccessViolation, so unlike the address helpers above they do not tolerate
a bad address. GetAsmSize notes that a JMP stub yields the stub's size, not the first instruction's,
whereas GetFunctionSize requires a pointer to a real function and not to a stub. On
GetFunctionSize the page states the limit of the method plainly: "This is heuristic function.
Results are not 100% reliable."
18.12 What kind of build am I in?
Six questions answered at run time rather than from a conditional symbol. Three are about the project
(IsCBuilder, IsLLVM, IsDynamicRTL), one about the module (IsCompiledWithPackages), and two
about the process that is running now (IsVirtualized, IntoIDE). The split matters, because a unit
compiled into a different kind of project sees the project's answers, not its own.
IsCBuilder, IsLLVM, IsDynamicRTL, IsCompiledWithPackages, IsVirtualized and IntoIDE
function IsCBuilder: Boolean; // ELowLevel
function IsLLVM: Boolean; // ELowLevel
function IsDynamicRTL: Boolean; // ELowLevel
function IsCompiledWithPackages: Boolean; // ELowLevel
function IsVirtualized: Boolean; // ELowLevel
function IntoIDE(const AVirtualize: Boolean = True): Boolean; // ELowLevel
IsCBuilder - True if the project "was compiled by C++ Builder"; IsLLVM - True if it was
compiled by the "new" C++ Builder compiler, "(e.g. LLVM)" - so a classic C++ Builder project answers
True for the first and False for the second. Both answer about the project rather than about the
unit that asks: the pages' note is that a unit compiled by Delphi but used in a C++ Builder project
sees the project's answer, "while any compile-time check inside the unit will return False".
IsDynamicRTL - True if the project "was compiled by C++ Builder with dynamic RTL DLL"; "This
value is always False for Delphi projects." IsCompiledWithPackages - True "for packaged
module"; the answer "is used to switch EurekaLog's behaviour between packaged and normal mode".
IsVirtualized - True if "the current process is instrumented or virtualized (for example,
running by AQTime)"; an "informational routine", with a Warning that explains confusing reports:
"EurekaLog may not operate with full features when application is instrumented".
IntoIDE - True if "current process is development IDE (Delphi, C++ Builder and so on)"; it is
"used to disable EurekaLog functionality in IDE expert", and "the check is based on .exe file name".
AVirtualize has a printed default of True and no published explanation.
18.13 Instance handles: which module am I?
CurrentInstance and PrimaryInstance
function CurrentInstance: HINST; // ELowLevel
function PrimaryInstance: HINST; // ELowLevel
CurrentInstance "returns current instance (i.e. module handle)", and "current" is defined precisely:
it is the instance "with EurekaLog which corresponds to caller of this function", which "can be a
different value in case of packaged application ... EurekaLog's code may reside in package which is not
the same module as caller". Hence the Important! line: "Do not use HInstance when working with
EurekaLog. Use CurrentInstance instead."
PrimaryInstance is "the same as MainInstance" and returns "the instance handle for the main
executable", with the matching instruction ("Do not use MainInstance ... Use PrimaryInstance
instead") and the defect it routes around: "In some rare cases MainInstance may be 0 instead of actual
value."
18.14 Wrapping initialization and finalization: SafeExec
SafeExec
procedure SafeExec(const AProc: ETypes.TProcedure; const ASection: String); // ELowLevel
"Executes routine with exception handling. Any exception is handled as fatal exception." This is how
EurekaLog's own units wrap the code of an initialization or finalization section. AProc "must not
throw exceptions as normal operation. Any exception inside this procedure is considered as fatal" - it
becomes an internal EurekaLog error rather than escaping the wrapper - and ASection is the "fully
qualified name of procedure. Must have form 'UnitName.ProcName'. ProcName must be either 'Init'
or 'Done'" - names that are recognised, because the routine "also changes IntoInitialization and
IntoFinalization variables"; an exception is "handled by _InitDoneErrorHandler (if assigned)".
Every EurekaLog unit with initialization/finalization code must follow this pattern exactly.
No other code is allowed in its initialization/finalization section, and no alternative names
are allowed for the Init and Done procedures.
This strict naming pattern applies to EurekaLog's internal units. It is not a prescription to wrap
every application's initialization in SafeExec: doing so changes exceptions into fatal internal
errors. Ordinary application initialization should keep its own explicit failure and cleanup policy.
18.15 Data EurekaLog writes into the executable: CheckELMagicCode
CheckELMagicCode
function CheckELMagicCode(const ACode: DWord): Boolean; // ELowLevel
"Checks double word for being one of the known EurekaLog's 'magic codes' (signatures)": True if ACode
"is MagicNumber7, MagicNumber6 or MagicNumber5 constants". A magic code "is just a 4 bytes
signature, which is used to indicate EurekaLog's data in executable. Each version of EurekaLog uses its
own magic code", and it is "also used to determinate correctness of password for encrypted debug
information". The routine "is primary used in EDebugEL unit", so it is here for completeness.