【发布时间】:2015-02-23 13:46:46
【问题描述】:
我一直在玩 Delphi 中不允许 RTL 的东西。这是一种dll。
拆开PE(便携式可执行文件)文件格式后,我意识到所有PE文件都有一个“入口点”。这是加载模块(exe 或 dll)后 Windows 调用的第一件事。
驻留在此入口点的函数的名称,以及它的隐含参数,取决于它的PE类型:
-
动态链接库 (*.dll):
DllMainfunction DllMain(hinstDLL: HINST; fdwReason: DWORD; lpvReserved: Pointer): BOOL; stdcall; -
可执行文件 (*.exe):
WinMainfunction WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: LPSTR; nCmdShow: Integer): Integer; stdcall; -
设备驱动程序 (*.sys):
DriverEntryfunction 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 的序言调用确实对尝试提取hinstDLL 和fdwReason 的值造成了一些破坏;但这不是一个无法克服的问题(例如,您仍然可以在 EBP+8、+12 和 +16 找到它们)。
但我的问题是 RTL 链接到的代码并不总是可用的。查看导入目录表,可以看到需要:
-
user32.dll(例如MessageBoxA) -
kernel32.dll(例如VirtualAlloc、VirtualFree、CloseHandle)
我可以避免 RTL 吗?
是否有编译器选项或定义可以去除System._InitLib 和System._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: HINSTANCEhPrevInstance: HINSTANCElpCmdLine: LPSTRnCmdShow: Integer
这就是为什么我无法弄清楚为什么没有将参数传递给 EntryPointFunction:
我在堆栈中越来越远地调试。我尝试了其他调试器。 Windows 只是没有将适当的参数传递给入口点函数。然后我找到了the answer:
操作系统不带参数调用函数
真正的 Windows .exe 入口点函数是:
DWORD CALLBACK RawEntryPoint(void);
又名:
function RawEntryPoint(): DWORD; stdcall;
WinMain 的参数从哪里来,如果它们没有传递到原始入口点?
语言启动代码通过询问操作系统得到它们:
- 可执行文件的实例句柄来自
GetModuleHandle(NULL)- 命令行来自
GetCommandLine- 而
nCmdShow来自GetStartupInfohPrevInstance始终是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
dcutoobjfiles,以便可以使用Microsoft 编译器链接它们。但我不是在问这个。我在询问更多关于标志的信息,以告诉编译器该做什么。使用 AppType dll,它使 _DllInit 成为入口点。使用 AppType application 它使 _ICantRememberWhat 成为入口点。我希望有一个 Apptype none。或-switch更改dcc32/64.exe的行为。 -
它不存在。你需要破解自己定制的
System和SysInit。如果您需要 Pascal 设备驱动程序,FPC 是您的最佳选择。
标签: delphi