EurekaLog
Menu

The EurekaLog Developer Guide

18. ELowLevel: debugger, stack and memory helpers

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".

__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 is Next minus Start.
  • Accessible - "Is instruction readable?" - it reports whether Start is readable, and reading it when False raises an Access Violation.
  • Valid - True when the instruction "was decoded successfully"; it gates the rest, since "all platform-specific fields in TInstruction record are valid only when Valid is True".
  • CPU-specific fields - x86-32 includes fields such as Prefix66, OpCodeSignificantByte, OperandSize and ModRMPresent. There is no real PlatformSpecific field.

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.