在Windows(MinGW)上编译Nasm程序时未定义对WinMain的引用

时间:2018-10-24 10:12:57

标签: windows assembly mingw nasm

我想在Windows上编译Hello World NASM example

我已将上面的代码粘贴到main.asm文件中,并使用以下命令将其编译为obj文件:

nasm -fwin32 .\main.asm -o main.obj

之后,我想将此obj文件编译为一个exe,如下所示:

g++ .\main.obj -o main.exe -m32

但是我得到这个错误:

C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/lib/../lib/libmingw32.a(lib32_libmingw32_a-crt0_c.o):crt0_c.c:(.text.startup+0x39): undefined reference to `WinMain@16'

我想念什么?如何解决此错误?

1 个答案:

答案 0 :(得分:5)

该Hello World程序正在尝试手动创建PE导入表。为了使它起作用,您需要仔细地指示链接器(PE部分未绑定到PE目录,idata仅是名称)。
在该来源中进行了进一步的假设(例如,图像的基址和对CRT的需求)。

老实说,这只是胡说。正确使用链接器,例如Jester shown
老实说,整个Wikipedia部分充其量只是提供信息。
长话短说:永远不要将Wikipedia用作编程教程。

一些简短的理论

主要可以通过两种方式创建32位Windows控制台程序:

  1. 使用C运行时(CRT)
    这使您可以使用通用的C函数(在所有printf之上)。
    有两种使用CRT的方法:

    1. 静态
      CRT源代码的编译产生的目标文件与源代码的编译/组装产生的目标文件链接。
      CRT代码完全嵌入在您的应用程序中。
      在这种情况下,您的主要功能(main / WinMain / DllMain和unicode变体)由CRT调用,该CRT首先由正确设置的PE入口点运行。
      为了使用此方法,您需要CRT对象文件,这些文件可以在Visual Studio或MinGW中找到(命名为二进制)。
      执行顺序为:Windows加载程序调用您的PE入口点,它设置为_mainCRTStartup之类的东西来初始化CRT,而CRT则调用您的主要功能。
    2. 动态
      对于Windows安装随附的版本,CRT主dll为msvcrt.dll,对于Visual Studio安装随附的版本,CRT主dll为msvcrtXX0.dll(其中XX取决于VS版本)。
      CRT dll在DLL入口点具有初始化和拆卸代码,因此只需将其放在PE导入表中,即可对CRT进行自动管理。 执行顺序为:Windows加载器加载您的PE依赖项,包括CRT DLL(已按照上文进行初始化),然后调用您的PE入口点。
  2. 仅使用Windows API
    Windows API是OS公开的功能,这些正是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机制(基本上是libs文件是使链接器找到运行时将由加载器解析的功能的一种方法。 -即在外部DLL中定义的那些)。

你好,世界!

方法2

首先,写您好,世界!使用方法2,请参阅this other answer of mine
它是在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

这是Wikipedia文章试图使用的内容。
在分析该特定代码之前,让我展示一下如何编写它:

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

与Wiki的相比,这非常简单。
制作可执行文件:

nasm -fwin32 helloworld.asm -o helloworld.obj
golink /console /entry _main c:\windows\system32\msvcrt.dll helloworld.obj -fo helloworld.exe

Wikipedia的代码正在创建一个.idata部分,用于存储PE导入地址表。
这是一个愚蠢的举动,链接器用于基于目标文件的动态依赖关系来生成该表。
要建立该程序链接,我们需要:

  1. 告诉链接器基地址为0x400000。可以使用任何链接器来完成此操作(对于golink,请使用/base 0x400000)。
  2. 告诉链接器入口点是.text部分的起点。我不知道link.exe是否可以将.text作为有效的符号名称,或者是否允许指定相对于.text的入口点,但这似乎不太可能。 Golink不允许这样做。简而言之,可能缺少标签。
  3. 告诉链接器以使Import 目录指向.idata部分。我不知道任何允许这样做的链接器(尽管它可能存在)。

简而言之,算了吧。

方法1.1

这就是link Jester pointed out所使用的。
汇编代码与1.2相同,但是您使用MinGW进行链接。