如何在Windows下的汇编器中编写Hello World?


89

我想在Windows下用汇编语言写一些基本的东西,我正在使用NASM,但是我什么也做不了。

如何在Windows上没有C函数帮助的情况下编写和编译hello world?


2
另请参阅史蒂夫·吉布森(Steve Gibson)的《美丽窗户》组装入门套件。
杰里米

不使用c库是一个有点奇怪的约束。必须在MS-Windows操作系统中调用某些库。可能是kernel32.dll。Microsoft是否已经用c或Pascal编写了此代码似乎无关紧要。这是否意味着只能调用OS提供的功能,在Unix类型的系统中将什么称为系统调用?
艾伯特·凡德·霍斯特

对于C库,我认为他或她的意思是不使用C运行时库,例如GCC或MSVC附带的库。当然,他(她)将不得不使用一些标准的Windows DLL,例如kernel32.dll。
Rudy Velthuis '17

2
kernel32.dll和gcc运行时库之间的区别不是格式(都是dll),也不是语言(都可能是c,但这是隐藏的)。区别在于操作系统是否提供。
艾伯特·范德·霍斯特

香港专业教育学院一直在寻找这一点,而且如果没有包含,大声笑也找不到任何东西
bluejayke

Answers:


37

NASM的例子

调用libc stdio printf,实现int main(){ return printf(message); }

; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits.  It needs to be linked with a C library.
; ----------------------------------------------------------------------------

    global  _main
    extern  _printf

    section .text
_main:
    push    message
    call    _printf
    add     esp, 4
    ret
message:
    db  'Hello, World', 10, 0

然后跑

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

还有《 Nasm中的Hello World世界的无知新手指南》不使用C ++类库。然后,代码将如下所示。

带有MS-DOS系统调用的16位代码:在DOS仿真器中或在具有NTVDM支持的32位Windows中工作。不能在任何64位Windows上“直接”(透明地)运行,因为x86-64内核不能使用vm86模式。

org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'

将其构建为.com可执行文件,以便将其加载到cs:100h与所有相等的段寄存器(微型内存模型)。

祝好运。


26
该问题明确提到“不使用C库”
Mehrdad Afshari,2009年

25
错误。C库本身显然可以,所以有可能。实际上,这只是稍微困难一点。您只需要使用正确的5个参数调用WriteConsole()。
MSalters

12
尽管第二个示例未调用任何C库函数,但它也不是Windows程序。虚拟DOS计算机将被触发以运行它。
罗慕洛Ceccon

7
@Alex Hart,他的第二个示例是针对DOS的,而不是Windows。在DOS中,小模式下的程序(.COM文件,总代码+数据+堆栈低于64Kb)从0x100h开始,因为该段中的前256个字节由PSP占用(命令行参数等)。看到这个链接:en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov 2012年

7
这不是要求的。第一个示例使用C库,第二个示例使用MS-DOS,而不是Windows。
Paulo Pinto

127

本示例说明了如何直接进入Windows API,而不是在C标准库中链接。

    global _main
    extern  _GetStdHandle@4
    extern  _WriteFile@20
    extern  _ExitProcess@4

    section .text
_main:
    ; DWORD  bytes;    
    mov     ebp, esp
    sub     esp, 4

    ; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
    push    -11
    call    _GetStdHandle@4
    mov     ebx, eax    

    ; WriteFile( hstdOut, message, length(message), &bytes, 0);
    push    0
    lea     eax, [ebp-4]
    push    eax
    push    (message_end - message)
    push    message
    push    ebx
    call    _WriteFile@20

    ; ExitProcess(0)
    push    0
    call    _ExitProcess@4

    ; never here
    hlt
message:
    db      'Hello, World', 10
message_end:

要进行编译,您需要NASM和LINK.EXE(来自Visual Studio标准版)

   nasm -fwin32 hello.asm
   链接/ subsystem:console / nodefaultlib / entry:main hello.obj 

21
您可能需要包含kernel32.lib来链接此链接(我确实这样做了)。链接/ subsystem:console / nodefaultlib / entry:main hello.obj kernel32.lib
Zach Burlingame

5
如何从MinGW将obj与ld.exe链接?
DarrenVortex

4
@DarrenVortexgcc hello.obj
13年

4
使用sourceforge.net/projects/alink中的 Alink或godevtool.com/#linker中的GoLink等免费链接器是否也可以使用?我不想仅为此安装Visual Studio吗?
jj_

21

这些是使用Windows API调用的Win32和Win64示例。它们是针对MASM而非NASM的,但请看一看。你可以找到更多的细节文章。

这使用MessageBox而不是打印到stdout。

Win32 MASM

;---ASM Hello World Win32 MessageBox

.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

.data
title db 'Win32', 0
msg db 'Hello World', 0

.code

Main:
push 0            ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg   ; LPCSTR lpText
push 0            ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax          ; uExitCode = MessageBox(...)
call ExitProcess

End Main

Win64 MASM

;---ASM Hello World Win64 MessageBox

extrn MessageBoxA: PROC
extrn ExitProcess: PROC

.data
title db 'Win64', 0
msg db 'Hello World!', 0

.code
main proc
  sub rsp, 28h  
  mov rcx, 0       ; hWnd = HWND_DESKTOP
  lea rdx, msg     ; LPCSTR lpText
  lea r8,  title   ; LPCSTR lpCaption
  mov r9d, 0       ; uType = MB_OK
  call MessageBoxA
  add rsp, 28h  
  mov ecx, eax     ; uExitCode = MessageBox(...)
  call ExitProcess
main endp

End

要使用MASM汇编和链接这些文件,请将其用于32位可执行文件:

ml.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main

或64位可执行文件:

ml64.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main

为什么x64 Windows需要在a之前保留28h的堆栈空间call 根据调用约定,这是32个字节(0x20)的影子空间(即本地空间)。再用8个字节将堆栈重新对齐16,因为调用约定要求RSP a 之前对齐16个字节call。(我们main的调用者(在CRT启动代码中)做到了。8字节的返回地址意味着RSP在进入函数时与16字节边界相距8字节。)

函数可以使用影子空间将其寄存器args转储到任何堆栈args(如果有)所在的位置旁边。system call除了前面提到的4个寄存器外,A还需要30h(48个字节)为r10和r11保留空间。但是DLL调用只是函数调用,即使它们是syscall指令的包装。

有趣的事实:非Windows,即x86-64 System V调用约定(例如,在Linux上)根本不使用影子空间,并且在XMM寄存器中最多使用6个整数/指针寄存器arg,以及最多8个FP arg。 。


使用MASM的invoke指令(知道调用约定),可以使用一个ifdef来制作该版本的版本,该版本可以构建为32位或64位。

ifdef rax
    extrn MessageBoxA: PROC
    extrn ExitProcess: PROC
else
    .386
    .model flat, stdcall
    include kernel32.inc
    includelib kernel32.lib
    include user32.inc
    includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text    db 'Hello World', 0
.code
main proc
    invoke MessageBoxA, 0, offset text, offset caption, 0
    invoke ExitProcess, eax
main endp
end

两者的宏变体相同,但是您不会以这种方式学习汇编。您将学习C风格的asm。invoke是for stdcallfastcallwhile cinvoke是for cdecl或变量参数fastcall。汇编器知道使用哪个。

您可以反汇编输出以查看如何invoke扩展。


1
+1为您的答案。您也可以在ARM(WOA)上为Windows添加汇编代码吗?
安妮

1
为什么rsp需要0x28字节而不是0x20?关于调用约定的所有引用都说应为32,但实际上似乎需要40。
douggard '17

在您的32位消息框代码中,由于某种原因,当我将其title用作标签名称时,我遇到了错误。但是,当我使用其他名称作为标签名称时mytitle,一切正常。
user3405291

包括在内怎么办?
bluejayke

13

Flat Assembler不需要额外的链接器。这使汇编程序编程变得非常容易。它也可用于Linux。

hello.asm来自Fasm示例:

include 'win32ax.inc'

.code

  start:
    invoke  MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
    invoke  ExitProcess,0

.end start

Fasm创建一个可执行文件:

> fasm hello.asm
平面汇编器版本1.70.03(1048575 KB内存)
4次通过,1536字节。

这是IDA中的程序:

在此处输入图片说明

你可以看到三个电话:GetCommandLineMessageBoxExitProcess


这使用了包含和GUI,我们如何只针对完全没有包含的CMD进行操作?
bluejayke


您能指出我一个没有任何dll写入控制台的部分吗?
bluejayke

11

要使用NASM的编译器和Visual Studio的链接器获取.exe,此代码可以正常运行:

global WinMain
extern ExitProcess  ; external functions in system libraries 
extern MessageBoxA

section .data 
title:  db 'Win64', 0
msg:    db 'Hello world!', 0

section .text
WinMain:
    sub rsp, 28h  
    mov rcx, 0       ; hWnd = HWND_DESKTOP
    lea rdx,[msg]    ; LPCSTR lpText
    lea r8,[title]   ; LPCSTR lpCaption
    mov r9d, 0       ; uType = MB_OK
    call MessageBoxA
    add rsp, 28h  

    mov  ecx,eax
    call ExitProcess

    hlt     ; never here

如果此代码保存在例如“ test64.asm”上,则进行编译:

nasm -f win64 test64.asm

产生“ test64.obj”,然后从命令提示符链接:

path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain  /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no

其中path_to_link可能是C:\ Program Files(x86)\ Microsoft Visual Studio 10.0 \ VC \ bin,或者您的机器中的link.exe程序在哪里, path_to_libs可能是C:\ Program Files(x86)\ Windows Kits \ 8.1 \ Lib \ winv6.3 \ um \ x64或您的库在哪里(在这种情况下,kernel32.lib和user32.lib都位于同一位置,否则对每个所需路径使用一个选项)和/ largeaddressaware:no选项是避免链接程序抱怨地址太长(在这种情况下为user32.lib)是必要的。另外,如此处所做的那样,如果从命令提示符处调用Visual的链接器,则必须事先设置环境(运行vcvarsall.bat和/或参见一次 MS C ++ 2010和mspdb100.dll)。


2
我强烈建议default rel在文件顶部使用,以便那些寻址模式([msg][title])使用RIP相对寻址而不是32位绝对寻址。
彼得·科德斯

5

除非您调用某些函数,否则这并非易事。(而且,严重的是,调用printf和调用win32 api函数之间在复杂性方面没有真正的区别。)

即使DOS int 21h实际上也只是一个函数调用,即使它是不同的API。

如果您想在没有帮助的情况下进行操作,则需要直接与视频硬件对话,可能将“ Hello world”字母的位图写入帧缓冲区。即使这样,视频卡仍在进行将这些内存值转换为VGA / DVI信号的工作。

注意,实际上,在ASM中,所有这些东西一直到硬件都比在C中更有趣。“ hello world”程序可以归结为函数调用。关于ASM的一件好事是,您可以相当轻松地使用任何您想要的ABI。您只需要知道ABI是什么。


这是一个很好的观点--- ASM和C都依赖于操作系统提供的功能(Windows中为_WriteFile)。那么魔术在哪里?它在视频卡的设备驱动程序代码中。
阿萨德·易卜拉欣

2
这完全是重点。张贴者询问在“ Windows下”运行的汇编程序。这意味着可以使用Windows工具(例如kernel32.dll),但不能使用其他工具(例如Cygwin下的libc)。为了大声喊叫,张贴者明确说没有c-libraries。
艾伯特·

5

最好的示例是那些具有fasm的示例,因为fasm不使用链接器,后者通过另一个不透明的复杂性层隐藏了Windows编程的复杂性。如果您对写入gui窗口的程序感到满意,那么fasm的example目录中有一个示例。

如果需要控制台程序,则还可以重定向标准输入和标准输出。有一个(helas非常重要的)示例程序不使用gui,而是严格与控制台一起使用,这本身就是fasm。这可以简化为要点。(我已经编写了第四个编译器,这是另一个非GUI的示例,但它也不是简单的)。

这样的程序具有以下命令来生成适当的可执行头,通常由链接器完成。

FORMAT PE CONSOLE 

名为“ .idata”的节包含一个表,该表可在启动过程中帮助Windows将功能名称与运行时地址耦合。它还包含对Windows操作系统KERNEL.DLL的引用。

 section '.idata' import data readable writeable
    dd 0,0,0,rva kernel_name,rva kernel_table
    dd 0,0,0,0,0

  kernel_table:
    _ExitProcess@4    DD rva _ExitProcess
    CreateFile        DD rva _CreateFileA
        ...
        ...
    _GetStdHandle@4   DD rva _GetStdHandle
                      DD 0

该表格式由Windows强制执行,并且包含在程序启动时在系统文件中查找的名称。FASM隐藏了rva关键字的某些复杂性。因此,_ExitProcess @ 4是fasm标签,而_exitProcess是Windows查找的字符串。

您的程序位于“ .text”部分。如果您声明该部分可读可写且可执行,则这是您唯一需要添加的部分。

    section '.text' code executable readable writable

您可以调用在.idata节中声明的所有设施。对于控制台程序,您需要_GetStdHandle来找到用于标准输入和标准输出的文件描述符(使用符号名,例如STD_INPUT_HANDLE,fasm可以在包含文件win32a.inc中找到它)。一旦有了文件描述符,就可以执行WriteFile和ReadFile。所有功能在kernel32文档中进行了描述。您可能已经意识到这一点,或者您不会尝试汇编程序编程。

总结:有一个带有asci名称的表与Windows OS耦合。在启动过程中,该表将转换为可调用地址表,供您在程序中使用。


FASM可能不使用链接器,但仍必须汇编PE文件。这意味着它实际上不仅是汇编代码,而且还承担了链接程序通常会执行的工作,因此,以我的拙见,这误导了将缺少链接程序称为“隐藏复杂性”,这恰恰相反-汇编器的工作是汇编程序,但将其留给链接器以将程序嵌入到程序映像中,这可能取决于很多事情。这样,我发现链接器和汇编器之间的分隔是一件好事,您对此表示不同意。
AMN

@amn这样考虑。如果使用链接器创建上述程序,它是否使您对程序的用途或组成内容有更深入的了解?如果我看一下fasm的资料,我就会知道程序的完整结构。
Albert van der Horst

有道理。另一方面,将链接与其他所有内容分开也有其好处。通常,您可以访问一个目标文件(这也使一个人也可以独立于程序映像文件格式来检查程序的结构),可以使用不同的选项调用您喜欢的另一个链接器。这与可重用性和可组合性有关。考虑到这一点,FASM做任何事情都是因为它“方便”而违反了这些原则。我原则上并不反对它-我看到他们为此辩护-但我其中一个不需要。
6

在fasm 64位窗口的最上面一行获取非法非法构造的错误
bluejayke,

3

如果要通过anderstornvig的Hello World示例使用NASM和Visual Studio的链接器(link.exe),则必须手动链接包含printf()函数的C运行时库。

nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib

希望这对某人有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.