当尝试实现我发现的一些示例代码时,我遇到一个错误,我不十分了解其原因。这样就可以了。
在标题中,我看到它声明了类似这样的内容。但是,当我尝试编译它时,VS使用此“无法解析的外部符号...”给我一个错误。
NTSYSAPI
NTSTATUS
NTAPI
NtOpenSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
但是,当我将其更改为类似内容时,我再也看不到错误了。
NTSTATUS(NTAPI *NtOpenSection)(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
我不确定是什么原因造成的。如果有人也发布一些在线参考资料对我来说更好,这将是很棒的。
答案 0 :(得分:3)
您需要了解编译器和链接器如何工作。编译器创建Common Object File Format (COFF)个文件。源文件中声明的每个符号都存在COFF Symbol Table。以二进制形式实现为IMAGE_SYMBOL
(从winnt.h
或ntimage.h
看)。对我们来说最有趣的是部分编号值(SHORT SectionNumber;
)。一般而言-符号可以已定义 –已创建的符号,并为其分配了文件中的存储地址和空间。 (指向节表的基于索引的索引)或未定义 –已在文件中引用但尚未分配存储地址的符号。
IMAGE_SYM_UNDEFINED
-符号记录尚未分配 部分。零值表示对外部的引用 符号在其他地方定义。非零值是常见符号 的大小由值指定。
使用时
NTSYSAPI
NTSTATUS
NTAPI
NtOpenSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
编译器使用__imp_NtOpenSection
部分值创建__imp__NtOpenSection@12
(x64,arm,arm64)或IMAGE_SYM_UNDEFINED
(x86)符号-实际上,您在这里声明函数,但是您不实施。此功能(NtOpenSection
)必须在其他位置定义(实现)。当链接器链接时-它在所有obj和lib文件中搜索实现(__imp_NtOpenSection
符号),并将其作为输入传递给他。如果找不到它的实现-他说IMAGE_SYMBOL
记录到节表中有一个基于索引的索引-未解析的外部符号。因此,您必须自己或自己实现功能(符号),或者将链接器lib或obj文件交给实现该功能的链接器。在用户模式下,它是在ntdll.lib
或ntdllp.lib
中实现的。因此,您需要将此lib文件之一传递给链接器输入-此解决错误。
第二种情况
NTSTATUS(NTAPI *NtOpenSection)(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
您声明和实施变量。它已经被创建并在文件中分配了存储地址和空间。结果,这里没有任何未解决的外部因素。
也请阅读Symbol Processing,以更好地了解此过程。
我还建议-编译不带/GL
选项的c / c ++文件(只有/HEADERS
DUMPBIN选项可用于由/GL
编译器选项生成的文件。 )并运行
link.exe /dump /symbols your.obj > some.txt
并在此处查找NtOpenSection
:
00000000 UNDEF notype External | __imp_NtOpenSection
在第一种情况下
00000000 SECT4 notype External | ?NtOpenSection@@3P6AJPEAPEAXKPEAUOBJECT_ATTRIBUTES@@@ZEA (long (__cdecl* NtOpenSection)(void * *,unsigned long,struct OBJECT_ATTRIBUTES *))
第二种情况。
UNDEF
与 SECTx
声明指针大小(4或8字节)变量(__imp_NtOpenSection
或?NtOpenSection@@...
),该变量将用作函数的保存地址。在两种情况下,二进制级别的间接调用都将相同:
call [__imp_NtOpenSection]
或call [?NtOpenSection@@...]
。不同-在__imp_
中-此变量(__imp_NtOpenSection
)的地址将被写入PE结构Import Lookup Table
中,并将其放置在Import Address Table
中。结果,加载程序(ntdll中的代码)将自动解析地址或NtOpenSection
并将此地址存储在__imp_NtOpenSection
变量中。或无法加载您的PE(如果地址无法解析)。因此,当您的代码开始执行时-在__imp_NtOpenSection
内部已经是NtOpenSection
函数的有效地址,您可以使用它-从c / c ++代码调用NtOpenSection
或从call [__imp_NtOpenSection]
调用asm。
在第二种情况下(如果使用?NtOpenSection@@...
或[_]NtOpenSection
或从c代码中声明,extern "C"
或_
仅适用于x86)-这将是简单的变量。当您开始执行代码时-这将是0(如果您声明为全局/静态)或未定义的值(堆栈中的局部变量)。在通过此类变量调用NtOpenSection
之前,您需要首先对其进行初始化-分配NtOpenSection
的实际地址。说成*(void**)&NtOpenSection = GetProcAddress(GetModuleHandle(L"ntdll"), "NtOpenSection");
。在此之后,您可以使用它。以及我怎么说-通过#1或#2声明在调用NtOpenSection
中不会有任何不同-代码是绝对相同的