有没有办法避免RTL?

Is there a way to avoid the RTL?

我一直在玩 Delphi 中不允许 RTL 的东西。这是一种dll。

拆开PE(Portable Executable)文件格式后,我发现所有的PE文件都有一个"Entry Point"。这是 Windows 在加载模块(exe 或 dll)后调用的第一件事。

驻留在该入口点的函数的名称及其隐含参数,取决于它的PE类型:

在 Delphi 中,此 入口点 是位于主项目文件中的代码。

在 DLL 的情况下,可以通过 LoadLibrary 读取传递给我们 "DllMain" 的参数。如果在 EntryPointAddress:

处设置断点

可以看到栈上的三个参数:

您所要做的就是捕获它们:

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.

但是有一个RTL

问题是那里不仅仅是 my 代码。编译器在之前之后插入隐藏代码,我在项目文件中的代码块:

本质上,真实DllMain代码包含:

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;

现在,这个对 _InitLib 的序言调用确实在尝试提取 hinstDLLfdwReason 的值时造成了一些破坏;但这并不是无法克服的问题(例如,您仍然可以在 EBP+8+12+16 中找到它们)。

但我的问题是 RTL 链接到的代码并不总是可用。查看导入目录Table,可以看到需要:

我可以避免 RTL 吗?

是否有编译器选项或定义可以去掉 System._InitLibSystem._Halt0 的所有内容?或者只是让编译器不将它们放入:

function DllMain(hinstDLL: HINST; fdwReason: Cardinal; lpvReserved: Pointer): LongBool; stdcall;
begin
   SysInit._InitLib(InitTable, hinstDLL, fdwReason, lpvReserved);

   //...
   System._Halt0;
end;

这显然是知道创建它们的编译器的特殊智慧。隐含在 EntryPointProcedure 中的内容会根据它是 Library 还是 Application 二进制文件而变化。

Bonus Chatter:缺少 WinMain 个参数的情况

WinMain 入口点被记录为传递四个参数:

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

这就是为什么我无法弄清楚参数没有传递给 EntryPointFunction 的原因:

我在堆栈中越来越深入地进行调试。我尝试了其他调试器。 Windows 只是没有将适当的参数传递给入口点函数。然后我发现 the answer:

The operating system calls the function with no parameters

realWindows.exe入口函数是:

DWORD CALLBACK RawEntryPoint(void);

又名:

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

这就解释了。

KOL 仍然活跃,官方网站 http://kolmck.net(由我维护)包含有关如何重写系统单元的示例。

使用库存 compiler/RTL 无法实现您想要的效果。编译器期望存在 SystemSysInit 单元,并且确实使用这些单元为许多语言功能提供运行时支持。例如,字符串是一种语言特性,它是通过 System 单元中实现的支持函数实现的。

如果您提供编译器将接受的替换 SystemSysInit 单元,则可以删除对用户模式模块的依赖性。不过,这可不是虚惊一场

既然你想写内核模式驱动代码,我建议使用FPC。

当加载一个 DLL 并且其主要 DPR 代码开始 运行 时,您可以从 RTL 的全局 SysInit.HInstance 变量中获取 hinstDLL 值,并且您隐含地知道 fdwReason 值必须是 DLL_PROCESS_ATTACH。在 DLL_PROCESS_ATTACH 期间您无法检索的唯一值是 lpvReserved 值,因为 RTL 会忽略它而不是将其保存在某处。但是,如果您希望您的代码对其他 fdwReason 值做出反应,并在其他原因状态下接收 lpvReserved 值,您所要做的就是分配一个 RTL 调用的 DllProcEx 回调来自其内部 DllMain 入口点,例如:

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.