Ian Boyd
Ian Boyd

Reputation: 256641

Is there a way to avoid the RTL?

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:

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:

enter image description here

you can see the three parameters on the stack:

enter image description here

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.

But there's an RTL

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:

enter image description here

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:

Can i avoid the RTL?

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.

Bonus Chatter: The case of the missing WinMain arguments

The WinMain entry point is documented to pass four parameters:

function WinMain(hInstance: HINST; hPrevInstance: HINST; 
          lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;

Which is why i couldn't figure out why the arguments weren't being passed to the EntryPointFunction:

enter image description here

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 from GetStartupInfo
  • hPrevInstance is always NULL

So that explains that.

Upvotes: 5

Views: 693

Answers (3)

Remy Lebeau
Remy Lebeau

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

David Heffernan
David Heffernan

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

Thaddy de Koning
Thaddy de Koning

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

Related Questions