有没有办法避免RTL?
Is there a way to avoid the RTL?
我一直在玩 Delphi 中不允许 RTL 的东西。这是一种dll。
拆开PE(Portable Executable)文件格式后,我发现所有的PE文件都有一个"Entry Point"。这是 Windows 在加载模块(exe 或 dll)后调用的第一件事。
驻留在该入口点的函数的名称及其隐含参数,取决于它的PE类型:
动态 Link 库 (*.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
问题是那里不仅仅是 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
的序言调用确实在尝试提取 hinstDLL
和 fdwReason
的值时造成了一些破坏;但这并不是无法克服的问题(例如,您仍然可以在 EBP+8
、+12
和 +16
中找到它们)。
但我的问题是 RTL 链接到的代码并不总是可用。查看导入目录Table,可以看到需要:
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: HINSTANCE
hPrevInstance: HINSTANCE
lpCmdLine: LPSTR
nCmdShow: Integer
这就是为什么我无法弄清楚参数没有传递给 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 无法实现您想要的效果。编译器期望存在 System
和 SysInit
单元,并且确实使用这些单元为许多语言功能提供运行时支持。例如,字符串是一种语言特性,它是通过 System
单元中实现的支持函数实现的。
如果您提供编译器将接受的替换 System
和 SysInit
单元,则可以删除对用户模式模块的依赖性。不过,这可不是虚惊一场
既然你想写内核模式驱动代码,我建议使用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.
我一直在玩 Delphi 中不允许 RTL 的东西。这是一种dll。
拆开PE(Portable Executable)文件格式后,我发现所有的PE文件都有一个"Entry Point"。这是 Windows 在加载模块(exe 或 dll)后调用的第一件事。
驻留在该入口点的函数的名称及其隐含参数,取决于它的PE类型:
动态 Link 库 (*.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
问题是那里不仅仅是 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
的序言调用确实在尝试提取 hinstDLL
和 fdwReason
的值时造成了一些破坏;但这并不是无法克服的问题(例如,您仍然可以在 EBP+8
、+12
和 +16
中找到它们)。
但我的问题是 RTL 链接到的代码并不总是可用。查看导入目录Table,可以看到需要:
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: HINSTANCE
hPrevInstance: HINSTANCE
lpCmdLine: LPSTR
nCmdShow: Integer
这就是为什么我无法弄清楚参数没有传递给 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 fromGetStartupInfo
hPrevInstance
is alwaysNULL
这就解释了。
KOL 仍然活跃,官方网站 http://kolmck.net(由我维护)包含有关如何重写系统单元的示例。
使用库存 compiler/RTL 无法实现您想要的效果。编译器期望存在 System
和 SysInit
单元,并且确实使用这些单元为许多语言功能提供运行时支持。例如,字符串是一种语言特性,它是通过 System
单元中实现的支持函数实现的。
如果您提供编译器将接受的替换 System
和 SysInit
单元,则可以删除对用户模式模块的依赖性。不过,这可不是虚惊一场
既然你想写内核模式驱动代码,我建议使用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.