assembly 如何在Windows下编写hello world程序集?

xoshrz7s  于 2022-11-13  发布在  Windows
关注(0)|答案(9)|浏览(186)

我想在Windows下用汇编语言写一些基本的东西。我用的是NASM,但是我不能让任何东西工作。
我如何在没有Windows上的C函数的帮助下编写和编译hello world程序?

ldfqzlk8

ldfqzlk81#

此示例说明如何直接转到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
   link /subsystem:console /nodefaultlib /entry:main hello.obj
5anewei6

5anewei62#

NASM examples
正在调用libc标准输入输出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

也有The Clueless Newbies Guide to Hello World in Nasm没有使用C库,那么代码看起来就像这样。

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

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

将其构建到.com可执行文件中,以便在cs:100h处加载,所有段寄存器彼此相等(微型内存模型)。
祝你好运

wd2eg0qa

wd2eg0qa3#

这些是使用Windows API调用的Win32和Win 64示例。它们是针对MASM而不是NASM的,但请查看它们。您可以在this文章中找到更多详细信息。
这将使用MessageBox而不是打印到标准输出。

Win32多媒体子系统

;---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

Win 64 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需要在call之前保留28 h字节的堆栈空间?**这是32字节(0x 20)的阴影空间(也称为主空间),这是调用约定所要求的。另外8个字节用于按16重新对齐堆栈,因为调用约定要求RSP在call * 之前 * 对齐16字节。(我们的main的调用程序(在CRT启动代码中)就是这样做的。8字节的返回地址意味着在进入函数时,RSP距离16字节的边界有8个字节。)

函数可以使用Shadow space将其寄存器参数转储到堆栈参数(如果有)所在的位置。system call需要30 h(48字节)来为r10和r11预留空间,此外还有前面提到的4个寄存器。但是DLL调用只是函数调用,即使它们是syscall指令的 Package 器。
有趣的事实:非Windows,即x86-64 System V调用约定(例如在Linux上)根本不使用影子空间,并使用多达6个整数/指针寄存器参数,* 和 * 多达8个FP参数在XMM寄存器中。
通过使用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。invokestdcallfastcall,而cinvokecdecl或变量参数fastcall。汇编程序知道该使用哪一个。

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

9rbhqvlz

9rbhqvlz4#

要获取一个.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然后从命令提示符链接:

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位于同一位置,否则为所需的每个路径使用一个选项),/largedressaware:no 选项是必要的,以避免链接器抱怨地址太长(在本例中为user32.lib)。此外,正如此处所做的那样,如果Visual的链接器是从命令提示符调用的,则必须预先设置环境(
(使用default rel使得lea指令可以在任何地方工作,包括在2GiB的虚拟地址空间之外。但是call MessageBoxA仍然是一个直接的call rel32,它只能到达距离它自己+-2GiB的指令。)

yptwkmov

yptwkmov5#

Flat Assembler不需要额外的连接器。这使得汇编程序的编程非常容易。它也适用于Linux。
这是Fasm示例中的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创建一个可执行文件:

>fasm hello.asm
flat assembler  version 1.70.03  (1048575 kilobytes memory)
4 passes, 1536 bytes.

这是IDA中的程序:

您可以看到三个调用:GetCommandLineMessageBoxExitProcess中的一个或多个。

t30tvxxf

t30tvxxf6#

除非调用 some 函数,否则这一点都不简单(而且,说真的,调用printf和调用win32 api函数在复杂性上没有真实的的区别)。
即使DOS int 21 h实际上只是一个函数调用,即使它是一个不同的API。
如果你想在没有帮助的情况下完成这件事,你需要直接和你的视频硬件对话,比如把“Hello world”的位图写入帧缓冲区。即使这样,显卡也会把这些内存值转换成DisplayPort/HDMI/DVI/VGA信号。
请注意,实际上,在ASM中,从硬件到软件的所有内容都不如在C中有趣。“hello world”程序可以归结为一个函数调用。ASM的一个优点是,您可以相当容易地使用任何您想要的ABI;你只需要知道ABI是多少。

z6psavjg

z6psavjg7#

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

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

希望这能帮助到一些人。

li9yvcax

li9yvcax8#

最好的例子是fasm,因为fasm不使用链接器,这通过另一层不透明的复杂性隐藏了windows编程的复杂性。如果你满足于一个写入gui窗口的程序,那么在fasm的example目录中有一个例子。
如果你想要一个控制台程序,它允许标准输入和标准输出的重定向,这也是可能的。有一个(helas高度重要的)例子程序,它不使用GUI,并且严格地与控制台一起工作,那就是fasm本身。这可以被精简到本质。(我已经写了一个forth编译器,这是另一个非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

表格格式是由Windows强制的,并且包含了程序启动时在系统文件中查找的名称。FASM隐藏了rva关键字背后的一些复杂性。因此_ExitProcess@4是一个fasm标签,而_exitProcess是一个由Windows查找的字符串。
您的程式位于区段'. text'中。如果您将该区段宣告为可读取、可写入且可执行,则它是您唯一需要加入的区段。

section '.text' code executable readable writable

您可以调用在.idata节中声明的所有功能。对于控制台程序,您需要_GetStdHandle来找到标准输入和标准输出的文件描述符(使用像STD_INPUT_HANDLE这样的符号名称,它可以在包含文件www.example.com中找到win32a.inc)。一旦您有了文件描述符,您就可以执行WriteFile和ReadFile。所有函数都在kernel32文档中进行了描述。您可能已经意识到了这一点,否则您不会尝试汇编程序编程。
总之:有一个表与asci名称耦合到windows操作系统。在启动过程中,这被转换为一个可调用地址表,您可以在程序中使用。

iklwldmw

iklwldmw9#

对于ARMWindows:

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

相关问题