Reputation: 256641
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.
Upvotes: 5
Views: 693
Reputation: 595827
When a DLL is loaded and its main DPR code starts running, you can get the hinstDLL
value from the RTL's global SysInit.HInstance
variable, and you implicitly know that the fdwReason
value must be DLL_PROCESS_ATTACH
. The only value you cannot retrieve during DLL_PROCESS_ATTACH
is the lpvReserved
value because the RTL ignores it rather than save it somewhere. However, if you want your code to react to other fdwReason
values, and receive lpvReserved
values in those other reason states, all you have to do is assign a DllProcEx
callback that the RTL calls from within its internal DllMain
entry point, eg:
library Project1;
{$R *.res}
procedure MyDllMain(Reason: Integer; Reserved: Pointer);
begin
// use SysInit.HInstance, Reason, and Reserved as needed...
case Reason of
//...
end;
end;
begin
DllProcEx := @MyDllMain;
// The RTL does not expose access to lpvReserved here because
// DllProcEx has not been assigned yet when the RTL processes
// DLL_PROCESS_ATTACH before calling unit initializations. If
// you need lpvReserved here, you will have to manually pull
// it from the call stack directly...
DllProcEx(DLL_PROCESS_ATTACH, nil);
end.
Upvotes: 2
Reputation: 612894
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.
Upvotes: 3
Reputation: 99
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.
Upvotes: 9