【问题标题】:Is there a way to avoid the RTL?有没有办法避免 RTL?
【发布时间】:2015-02-23 13:46:46
【问题描述】:

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

拆开PE(便携式可执行文件)文件格式后,我意识到所有PE文件都有一个“入口点”。这是加载模块(exe 或 dll)后 Windows 调用的第一件事。

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

  • 动态链接库 (*.dll):DllMain

    function DllMain(hinstDLL: HINST; fdwReason: DWORD; 
          lpvReserved: Pointer): BOOL; stdcall;
    
  • 可执行文件 (*.exe):WinMain

    function WinMain(hInstance: HINST; hPrevInstance: HINST; 
          lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall;
    
  • 设备驱动程序 (*.sys):DriverEntry

    function DriverEntry(DriverObject: PDriverObject; 
          RegistryPath: PUnicodeString): NTSTATUS; stdcall;
    

在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

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

基本上,真正的 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 链接到的代码并不总是可用的。查看导入目录表,可以看到需要:

  • user32.dll(例如MessageBoxA
  • kernel32.dll(例如VirtualAllocVirtualFreeCloseHandle

我可以避免 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;
  • hInstance: HINSTANCE
  • hPrevInstance: HINSTANCE
  • lpCmdLine: LPSTR
  • nCmdShow: Integer

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

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

操作系统不带参数调用函数

真正的 Windows .exe 入口点函数是:

DWORD CALLBACK RawEntryPoint(void);

又名:

function RawEntryPoint(): DWORD; stdcall;

WinMain 的参数从哪里来,如果它们没有传递到原始入口点?

语言启动代码通过询问操作系统得到它们:

  • 可执行文件的实例句柄来自GetModuleHandle(NULL)
  • 命令行来自GetCommandLine
  • nCmdShow 来自GetStartupInfo
  • hPrevInstance 始终是 NULL

这就解释了。

【问题讨论】:

  • 您将不得不放弃动态数组、字符串、异常和许多其他东西。你准备好了吗?您可能正在尝试制作 WinRT DLL?
  • @DavidHeffernan .exe -> WinMain(...), .dll -> DllMain(...), .sys - >DriverEntry(...)。我不想提及内核模式设备驱动程序开发,因为担心它会使问题变得模糊。 (“它是一种 dll。” 驱动程序是什么;一个 DLL,具有导出和入口点)
  • 我认为使用 FPC 会更好:wiki.freepascal.org/Target_NativeNT 你应该提到你的目标是驱动程序。这会让一切变得更加清晰。
  • @DavidHeffernan 我已经看过了。还有a kit where a guy converts dcu to obj files,以便可以使用Microsoft 编译器链接它们。但我不是在问这个。我在询问更多关于标志的信息,以告诉编译器该做什么。使用 AppType dll,它使 _DllInit 成为入口点。使用 AppType application 它使 _ICantRememberWhat 成为入口点。我希望有一个 Apptype none。或-switch 更改dcc32/64.exe 的行为。
  • 它不存在。你需要破解自己定制的SystemSysInit。如果您需要 Pascal 设备驱动程序,FPC 是您的最佳选择。

标签: delphi


【解决方案1】:

KOL 还活着,官网http://kolmck.net(我维护)包含了如何重写系统单元的示例。

【讨论】:

  • 我下载了。我看到了FAQ问题(“是否可以在不卸载VCL的情况下使用KOL(MCK)。如果我安装KOL(MCK),是否可以在不重新安装Delphi的情况下使用VCL?”)。我读过read1st.txt;但我不知道如何使用它。看来您设法替换了整个 RTL;这是隐藏每个项目的 RTL 的一步。但是我包含什么会导致编译器/链接器找不到/使用 RTL?
  • 2016 年 8 月,该链接似乎已失效。也许链接应该指向这里:sourceforge.net/projects/keyobjectslibrary
【解决方案2】:

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

如果您提供编译器将接受的替换 SystemSysInit 单元,则可以删除对用户模式模块的依赖关系。但是,这不是为了装腔作势。

由于您似乎希望编写内核模式驱动程序代码,我建议使用 FPC。

【讨论】:

  • 由于我必须进行构建后操作才能将 IMAGE_OPTIONAL_HEADER.SubsystemIMAGE_SUBSYSTEM_NATIVE 更改为 IMAGE_SUBSYSTEM_WINDOWS_GUI,因此我可以借此机会向 nop 拨打电话。我只是希望在某个时候 Bo...Imp...Cod...Embarcadero,以及他们的新操作系统目标,已经暴露出不使用 Windows dll 的方式。
【解决方案3】:

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

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.

【讨论】:

    猜你喜欢
    • 2019-09-07
    • 2019-05-29
    • 2011-09-13
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-02
    相关资源
    最近更新 更多