在 Windows 下如何在汇编中编写 hello world?

我想写一些基本的汇编在 Windows 下。我正在使用 NASM,但我不能得到任何工作。

如何在 Windows 上编写和编译一个 hello world 程序而不需要 C 函数的帮助?

224702 次浏览

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

也有没有使用 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上加载它。

祝你好运。

此示例演示如何直接转到 WindowsAPI 而不链接到 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:

To compile, you'll need NASM and LINK.EXE (from Visual studio Standard Edition)

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

除非调用 一些函数,否则这一点都不简单。(而且,说真的,调用 printf 和调用 win32api 函数在复杂性上没有什么区别。)

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

If you want to do it without help you need to talk to your video hardware directly, likely writing bitmaps of the letters of "Hello world" into a framebuffer. Even then the video card is doing the work of translating those memory values into DisplayPort/HDMI/DVI/VGA signals.

请注意,实际上,这些东西没有一个在 ASM 中比在 C 中更有趣。“ hello world”程序归结为一个函数调用。关于 ASM 的一个好处是,您可以相当容易地使用任何您想要的 ABI; 您只需要知道 ABI 是什么。

这些是使用 WindowsAPI 调用的 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

or this for 64-bit executable:

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

Why does x64 Windows need to reserve 28h bytes of stack space before a call? That's 32 bytes (0x20) of shadow space aka home space, as required by the calling convention. And another 8 bytes to re-align the stack by 16, because the calling convention requires RSP be 16-byte aligned 之前 a call. (Our main's caller (in the CRT startup code) did that. The 8-byte return address means that RSP is 8 bytes away from a 16-byte boundary on entry to a function.)

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

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


使用 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 式高潮。invoke表示 stdcallfastcall,而 cinvoke表示 cdecl或可变参数 fastcall。汇编程序知道使用哪个。

您可以反汇编输出以查看 invoke是如何展开的。

如果你想在 anderstorvig 的 Hello World 示例中使用 NASM 和 Visual Studio 的链接器(link.exe) ,你必须手动链接包含 printf ()函数的 C Runtime Library。

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

希望这对谁有帮助。

平面汇编程序 不需要额外的链接器,这使得汇编程序编程变得非常容易。

这是来自法斯姆斯的 hello.asm例子:

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 hello.asm
flat assembler  version 1.70.03  (1048575 kilobytes memory)
4 passes, 1536 bytes.

这是 艾达的节目:

enter image description here

You can see the three calls: GetCommandLine, MessageBox and ExitProcess.

要获得一个.exe 文件,并将 NASM 作为汇编程序和 Visual Studio 的链接器,这段代码可以很好地工作:

default rel         ; Use RIP-relative addressing like [rel msg] by default
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      ; reserve shadow space and make RSP%16 == 0
mov rcx, 0       ; hWnd = HWND_DESKTOP
lea rdx,[msg]    ; LPCSTR lpText
lea r8,[title]   ; LPCSTR lpCaption
mov r9d, 0       ; uType = MB_OK
call MessageBoxA


mov  ecx,eax        ; exit status = return value of MessageBoxA
call ExitProcess


add rsp, 28h       ; if you were going to ret, restore RSP


hlt     ; privileged instruction that crashes if ever reached.

如果此代码保存为 test64.asm,则汇编:

nasm -f win64 test64.asm

制作 test64.obj Then to link from command prompt:

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

where Path _ to _ link could be C: Program Files (x86) Microsoft Visual Studio 10.0 VC bin or wherever is your link.exe program in your machine, Path _ to _ libs 可以是 C: Program Files (x86) Windows Kit 8.1 Lib winv6.3 um x64,也可以是库所在的任何地方(在这种情况下,kernel32.lib 和 user32.lib 都在同一个位置,否则对于每个需要的路径都使用一个选项) ,而且必须使用 /大型地址软件: 不选项来避免链接器抱怨地址太长(对于 user32.lib 来说是这种情况)。 此外,如果从命令提示符调用 Visual 的链接器,则需要先设置环境(运行一次 vcvarsall.bat 和/或参见 MS C + + 2010和 mspdb100.dll)。

(Using default rel makes the lea instructions work from anywhere, including outside the low 2GiB of virtual address space. But the call MessageBoxA is still a direct call rel32 that can only reach instructions +-2GiB away from itself.)

The best examples are those with fasm, because fasm doesn't use a linker, which hides the complexity of windows programming by another opaque layer of complexity. If you're content with a program that writes into a gui window, then there is an example for that in fasm's example directory.

如果您想要一个控制台程序,它允许标准输入和标准输出的重定向,这也是可能的。 有一个(helas 非常重要的)示例程序不使用 gui,而是严格使用控制台,即 fasm 本身。这可以精简到最基本的部分。(我已经编写了第四个编译器,这是另一个非 GUI 的例子,但它也是不平凡的)。

这样的程序有以下命令为32位可执行文件生成适当的头,通常由链接器完成。

FORMAT PE CONSOLE

一个叫做’。Idata’包含一个表,帮助窗口在启动时将函数名耦合到运行时地址。它还包含对 KERNEL.DLL 的引用,这是 Windows 操作系统。

 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

表格格式由窗口强加,并包含在程序启动时在系统文件中查找的名称。FASM 隐藏了一些 关键字背后的复杂性。因此,_ ExitProcess@4是一个 fasm 标签,而 _ exitProcess 是一个由 Windows 查找的字符串。

你的节目是在部分。短信。如果您声明该节为可读的、可写的和可执行的,那么它就是您唯一需要添加的部分。

    section '.text' code executable readable writable

中声明的所有设施。资料部分。对于控制台程序,您需要 _ GetStdHandle 来查找标准输入和标准输出的文件描述符(使用像 STD _ INPUT _ HANDLE 这样的符号名称,这些符号名称可以在包含文件 win32a.inc 中找到)。 Once you have the file descriptors you can do WriteFile and ReadFile. 所有的函数都在内核32文档中有描述。您可能已经注意到了这一点,否则不会尝试汇编程序。

总之: 有一个与 windows 操作系统配对的 asci 名称表。 在启动过程中,这会转换成一个可调用地址表,您可以在程序中使用它。

手臂视窗:

AREA    data, DATA


Text    DCB "Hello world(text)", 0x0
Caption DCB "Hello world(caption)", 0x0


EXPORT  WinMainCRTStartup
IMPORT  __imp_MessageBoxA
IMPORT  __imp_ExitProcess


AREA    text, CODE
WinMainCRTStartup   PROC
movs        r3,#0
ldr         r2,Caption_ptr
ldr         r1,Text_ptr
movs        r0,#0
ldr         r4,MessageBoxA_ptr    @ nearby, reachable with PC-relative
ldr         r4,[r4]
blx         r4


movs        r0,#0
ldr         r4,ExitProcess_ptr
ldr         r4,[r4]
blx         r4


MessageBoxA_ptr DCD __imp_MessageBoxA       @ literal pool (constants near code)
ExitProcess_ptr DCD __imp_ExitProcess
Text_ptr    DCD Text
Caption_ptr DCD Caption


ENDP
END