那个“Hello World”程序试图手动创建PE导入表。为使其工作,需要仔细指示链接器(PE节不绑定于PE目录,
idata
只是一个名称)。在该源代码中做出了进一步的假设(例如,镜像的基地址和CRT的必要性)。
老实说,这完全是无稽之谈。正确使用链接器,如
Jester shown所示。说实话,整个维基百科部分充其量只是提供信息而已。
长话短说:永远不要将维基百科用作编程教程。
编辑:Peter Cordes已更新了维基百科页面上的x86-64 Linux示例;其他示例可能仍然具有误导性。
简单理论介绍
你可以通过两种主要方式创建32位Windows控制台程序:
使用C运行时库(CRT)
这使您可以使用常见的C函数(尤其是printf
)。
有两种使用CRT的方法:
- 静态方式
从编译CRT源代码得到的对象文件与从编译/汇编源代码得到的对象文件链接。
CRT代码完全嵌入在应用程序中。
在此场景下,CRT先通过适当设置的PE入口点调用您的主函数(main
/WinMain
/DllMain
和unicode变体)。
要使用此方法,您需要CRT对象文件,这些文件可以在Visual Studio或MinGW中找到。
执行顺序为:Windows加载器调用您的PE入口点,这被设置为类似于_mainCRTStartup
的东西,该初始化CRT并且CRT调用您的主函数。
- 动态方式
CRT主dll是msvcrt.dll
,对于随Windows安装提供的版本或对于随Visual Studio安装提供的版本,则为msvcrtXX0.dll
(其中XX
取决于VS版本)。
CRT dll在DLL入口点中具有初始化和撤销代码,因此仅将其放入PE导入表中即可自动管理CRT。
执行顺序为:Windows加载器加载您的PE依赖项,包括CRT DLL(按照上述方式初始化),然后调用您的PE入口点。
仅使用Windows API
Windows API是操作系统公开的函数,这些是CRT实现最终调用的内容。
您可以使用Windows API和CRT(常见情况是图形应用程序静态链接CRT并使用WinMain
作为入口点 - 其中Windows API与C实用程序函数交错使用)或仅使用Windows API。
仅使用它们时,您将获得更小、更快且易于制作的可执行文件。
要使用1.1版本,您需要CRT对象文件,通常这些文件会随编译器一起发货(曾经随Windows SDK一起发货,但现在由于VS是免费的,Microsoft将它们移到了VS包中-公平,但VS比SDK重得多)。
1.2和2版本不需要这些对象文件。
请注意,编译器/汇编器/链接器的兼容性可能会是一个棘手的问题,特别是用于链接外部API的.lib机制(基本上,lib文件是使链接器在运行时找到将由加载程序解析的函数的方法 - 即在外部DLL中定义的函数)。
你好,世界!
方法2
首先,使用方法2写“Hello, World!”,请参见
我的另一个答案。
当Windows SDK中有链接器时编写该答案,今天我使用
GoLink。
它是一个极简主义、易于使用的链接器。
其中一个关键点是它
不需要.lib文件,而是可以传递外部函数所在的DLL路径。
NASM命令相同,要进行链接,请使用:
golink /console /entry main c:\windows\system32\kernel32.dll hello.obj -fo hello.exe
未经测试 - 如果您的代码可以处理,可以选择添加/largeaddressaware
该示例适用于64位编程,比32位编程更复杂,但仍可能有用。
方法1.2
这是维基百科文章试图使用的内容。
在分析具体代码之前,让我展示一下我会如何编写它:
BITS 32
GLOBAL _main
EXTERN printf
EXTERN exit
SECTION .text
_main:
push strHelloWorld
call printf
add esp, 04h
push 0
call exit
SECTION .data
strHelloWorld db "Hello, world!", 13, 10, 0
这与维基的相比非常简单。要生成可执行文件:
nasm -fwin32 helloworld.asm -o helloworld.obj
golink /console /entry _main c:\windows\system32\msvcrt.dll helloworld.obj -fo helloworld.exe
维基百科的代码正在创建一个名为
.idata
的部分,用于存储PE导入地址表。
这是一个愚蠢的举动,链接器使用对象文件的动态依赖关系生成该表。
为了使该程序链接成功,我们需要做到以下几点:
- 告诉链接器基地址是
0x400000
。任何链接器都可以实现这一点(对于golink,请使用/base 0x400000
)。
- 告诉链接器入口点在
.text
部分开始的地方。我不知道link.exe
是否可以将.text
作为有效符号名称或允许指定相对于.text
的入口点,但这似乎很不可能。Golink不允许这样做。简而言之,可能缺少一个标签。
- 告诉链接器使导入目录指向
.idata
部分。我不知道有哪个链接器可以实现这一点(虽然可能存在),
总之,忘记它吧。
方法1.1
这就是
Jester指出的链接所使用的内容。
汇编代码与1.2版本相同,但需要使用MinGW进行链接。
section .text
后插入两行global _main
和_main:
。这样至少可以链接,但我使用wine
无法运行。也许在 Windows 上可以。这里有一个更简单的方法可行。 - Jester