I've been playing with something in Delphi where the RTL is not allowed. It's a kind of dll.
After picking apart the PE (Portable Executable) file format, i realized that all PE files have an "Entry Point". This is the first thing that Windows calls after the module (exe or dll) is loaded.
The name of function that resides at this entry point, and it's implied parameters, depend on the kind of PE it is:
Dynamic Link Library (*.dll): DllMain
function DllMain(hinstDLL: HINST; fdwReason: DWORD;
lpvReserved: Pointer): BOOL; stdcall;
Executable (*.exe): WinMain
function WinMain(hInstance: HINST; hPrevInstance: HINST;
lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
Device Driver (*.sys): DriverEntry
function DriverEntry(DriverObject: PDriverObject;
RegistryPath: PUnicodeString): NTSTATUS; stdcall;
In Delphi, this Entry Point is the code that sits in your main project file.
In the case of a DLL, it is possible to read the parameters passed to our "DllMain" by LoadLibrary
. If you put a breakpoint at the EntryPointAddress:
you can see the three parameters on the stack:
All you would have to do is capture them:
library Project1;
function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): Integer; stdcall;
begin
Result := 1; //Windows uses FALSE=0, TRUE=1. Delphi uses False=0, True=-1
end;
begin
//Code in here is run during DllMain.
//i.e. DllMain does not return until this code completes.
asm
{ Get the parameters to DllMain that Windows passed to us:
[ESP+4] hinstDLL
[ESP+8] fdwReason
[ESP+12] lpvReserved
}
MOV eax, [ESP+12]; //push lpvReserved onto the stack
PUSH eax;
MOV eax, [ESP+8]; //push fdwReason onto the stack
PUSH eax
MOV eax, [ESP+4]; //push hinstDLL onto the stack
PUSH eax;
CALL DllMain; //Call our DllMain function
//DllMain leaves BOOL result in EAX
end;
end.
The problem is that there is more than just my code in there. The compiler inserts hidden code just before, and just after, my code block in the project file:
Essentially, the real DllMain
code contains:
function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): LongBool; stdcall;
begin
SysInit._InitLib(InitTable, hinstDLL, fdwReason, lpvReserved);
asm
MOV eax, [ESP+12]; //push lpvReserved onto the stack
PUSH eax;
MOV eax, [ESP+8]; //push fdwReason onto the stack
PUSH eax
MOV eax, [ESP+4]; //push hinstDLL onto the stack
PUSH eax;
CALL DllMain; //Call our DllMain function
//DllMain leaves BOOL result in EAX
end;
System._Halt0;
end;
Now, this preamble call to _InitLib
does wreak some havoc with trying to pull the values of hinstDLL
and fdwReason
; but that's not an insurmountable problem (e.g. you can still find them at EBP+8
,+12
, and +16
).
But my issue is that the RTL links to code that isn't always available. Looking at the Import Directory Table, you can see that it needs:
user32.dll
(e.g. MessageBoxA
)kernel32.dll
(e.g. VirtualAlloc
, VirtualFree
, CloseHandle
)Is there a compiler option, or a define, that can strip out all the guts of System._InitLib
and System._Halt0
? Or just have the compiler not place them into:
function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): LongBool; stdcall;
begin
SysInit._InitLib(InitTable, hinstDLL, fdwReason, lpvReserved);
//...
System._Halt0;
end;
It's obviously a special bit of smarts on the part of the compiler that knows to create them. And what gets implicitly placed in the EntryPointProcedure changes depending if it's a Library or Application binary.
WinMain
argumentsThe WinMain
entry point is documented to pass four parameters:
function WinMain(hInstance: HINST; hPrevInstance: HINST;
lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
hInstance: HINSTANCE
hPrevInstance: HINSTANCE
lpCmdLine: LPSTR
nCmdShow: Integer
Which is why i couldn't figure out why the arguments weren't being passed to the EntryPointFunction:
I was debugging further and further back in the stack. I tried other debuggers. Windows just wasn't passing the appropriate parameters to the Entry Point function. Then i found the answer:
The operating system calls the function with no parameters
The real Windows .exe
entry point function is:
DWORD CALLBACK RawEntryPoint(void);
aka:
function RawEntryPoint(): DWORD; stdcall;
Where do the parameters to WinMain come from, if they aren't passed to the raw entry point?
The language startup code gets them by asking the operating system:
- the instance handle for the executable comes from
GetModuleHandle(NULL)
- the command line comes from
GetCommandLine
- and the
nCmdShow
comes fromGetStartupInfo
hPrevInstance
is alwaysNULL
So that explains that.
KOL is still alive and kicking and the official website http://kolmck.net (maintained by me) contains examples on how to rewrite the system unit.
There is no way you can achieve what you desire using the stock compiler/RTL. The compiler expects the presence of System
and SysInit
units and indeed uses those units to supply runtime support for many language features. As an example, strings are a language feature that is implemented by way of support functions implemented in the System
unit.
If you provide replacement System
and SysInit
units that the compiler will accept then it is possible to remove the dependencies on user mode modules. However, this is not for the feint of heart.
Since you seem to wish to write kernel mode driver code, I would suggest using FPC.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With