世界你好!使用 WinAPI 没有做任何事情

问题描述 投票:0回答:1

我使用 Netwide Assembler 作为我的汇编器。 我正在尝试创建一个打印字符串

Hello World
的程序,但没有成功。

我按照调用约定将所有参数放入堆栈中。 CC 表示第一个参数需要放在

RCX
中,第二个参数放在
RDX
中,第三个参数放在
R8
中,第四个参数放在
R9
中,第五个参数放在
[RSP + 32]
中。

所以我写了这个程序:

section .data
    phrase db 'Hello World'     ; create new var with size of db(Double World)
    charsWritten dd 0           ; set the var up is as things which will be printed out
section .text
    global _start
    extern ExitProcess          ; import the function ExitProcess to return 0
    extern WriteConsoleA        ; import the command of printing out
    extern GetStdHandle         ; descriptor
    _start:
        mov  rcx, -10           ; set 1st argument
        call GetStdHandle       ; call the result was saved in rax
        mov r8, rax             ; save result

        mov rcx, r8             ; set the saved result up as 1st argument
        mov rdx, phrase         ; set the pointer as 2nd argument
        mov r8, 11              ; set r8 as 3rd argument with data 11 (length)
        mov r9, charsWritten    ; set the pointer of printed words out as 4th arg.
        mov qword [rsp + 32], 0 ; set zero into stack as 5th argument
        call WriteConsoleA      ; call printing out

        mov rcx, 0              ; its first argument of returning 0
        call ExitProcess        ; call return with 0 as first argument of it

然而这个程序似乎什么也没做。没有错误。我尝试设置这个

.exe
文件。退出状态为
0
(成功)。

C:\Users\Мила ПК\Desktop>nasm -f win64 test.asm -o test.o

C:\Users\Мила ПК\Desktop>ld -o test.exe test.o -lkernel32
C:\Users\Мила ПК\Desktop>test.exe
C:\Users\Мила ПК\Desktop>echo %ERRORLEVEL%
0

我认为与问题相关的其他细节:

数据 价值
操作系统 Windows 11 专业版
系统 64 位系统
处理器 英特尔i7
winapi nasm win64
1个回答
-1
投票

Kai Burghardt 不建议使用 Microsoft Windows。此答案可能有错误。

您可能会想从 Hello, World 程序开始,但实际上您用汇编语言编写的第一个 W64 程序应该是这样的:

global _start
section .text
_start:
    ret

在类 UNIX 环境中,loader通常

jmp
到入口点。 然而,在 Winblows 中,入口点本质上是
call
ed 将返回地址放置在您可以
ret
的堆栈顶部。


作为侧边栏,一个无限循环程序可以这样写:

global _start
section .text
_start:
    jmp rax   ; rax containing the address of the entry point

退出

现在,来自

ret
_start
无法保证 能够正常工作,因为它显然没有正式记录。 官方的方式是使用ExitProcess
。
让我们用 
ExitProcess
 来重写第一个程序。

堆栈对齐

首先,在进行任何

call

 之前,
ABI 希望您 在 16 字节边界上对齐堆栈。 这意味着以十六进制数编写的堆栈指针应以 0
 结尾。
由于堆栈向下增长(朝向数值较小的地址),实现 16 字节对齐的直接方法是:

and RSP, 0xFFFFFFFFFFFFFFF0
可以公平地假设“操作系统”遵守自己的调用约定。
当我们的程序被

call

ed
时,堆栈也已经对齐了。 但是, call
 将返回地址放在堆栈顶部,从而破坏了对齐(由 push(RIP)
 隐式 
call
)。

因此,当我们的程序从

_start

 之后的第一条指令开始时,堆栈指针(以十六进制数表示)以 
8
 结尾(或算术上表示 
RSP mod 16 = 8
true
)。
因此,上面的 
and
 实际上与 
sub RSP, 8
 具有相同的作用。

由于我们不打算使用返回地址,因此我们可以简单地通过弹出堆栈来

丢弃返回地址:

pop RAX ; discard return address, re‑align stack to 16 B
黑暗空间

Wix64 ABI 要求您(始终)在堆栈上分配4×8 字节的影子空间。 这个空间可能被被调用者(您call的函数)使用来溢出前四个参数

RCX
RDX
R8
R9
。 溢出意味着将数据存储在其他地方,以便释放寄存器以供其他用途;稍后可以从所述影子空间检索原始参数值。
global _start                   ; export _start
extern ExitProcess              ; tell NASM the reference is resolved later

section .text
_start:
    pop RAX                     ; discard return address, re‑align stack to 16 B
    
exit:
    xor ECX, ECX                ; ECX ≔ EXIT_SUCCESS
    sub RSP, 4 * 8              ; reserve 32 bytes of shadow space
    call ExitProcess            ; 32 mod 16 = 0, so stack pointer still aligned

获取输出

您有责任在

call

完成后清理堆栈。 虽然

ExitProcess
(理想情况下)不会返回,但
GetStdHandle
 会返回,因此我们反转 
sub RSP, 32
的效果:
global _start
extern GetStdHandle, ExitProcess

STD_OUTPUT_HANDLE   equ  -11    ; a more meaningful constant instead of −11

section .text
_start:
    pop RAX                     ; discard return address, re‑align stack to 16 B

get_output: 
    mov ECX, STD_OUTPUT_HANDLE  ; retrieve HANDLE for standard output
    sub RSP, 4 * 8              ; reserve 32 bytes of shadow space
    call GetStdHandle           ; RAX ≔ GetStdHandle(STD_OUTPUT_HANDLE)
    add RSP, 4 * 8              ; unreserve 32 bytes of shadow space
    
exit:
    xor ECX, ECX                ; ECX ≔ EXIT_SUCCESS
    sub RSP, 4 * 8              ; reserve 32 bytes of shadow space
    call ExitProcess

您可能会注意到一半的指令只是来回移动堆栈指针。
您可以减少杂耍,但我建议推迟该步骤,直到您的算法按预期工作,直到您不想再插入或删除任何内容。

写入输出

正如您所知,前四个参数是在寄存器中传递的。 任何附加参数都从右到左压入堆栈。 这样做时,您必须

考虑阴影空间和对齐

此外,最好让

汇编器计算字符串长度

,而不是自己计算(→ hello_world_length)。

global _start
extern GetStdHandle, WriteConsoleA, ExitProcess
default rel                     ; if not specified use RIP‑relative addressing

STD_OUTPUT_HANDLE   equ  -11    ; a more meaningful constant instead of −11

; --- initialized data ---------------------------------------------------------
section .data
hello_world:
    db 'Hello, world!', `\r\n`
hello_world_length  equ  $ - hello_world

; --- uninitialized data -------------------------------------------------------
section .bss
result:
    resd 1

; --- executable text ----------------------------------------------------------
section .text
_start:
    pop RAX                     ; discard return address, re‑align stack to 16 B
    
get_output:
    mov ECX, STD_OUTPUT_HANDLE  ; retrieve HANDLE for standard output
    sub RSP, 4 * 8              ; reserve 32 bytes of shadow space
    call GetStdHandle           ; RAX ≔ GetStdHandle(STD_OUTPUT_HANDLE)
    add RSP, 4 * 8              ; unreserve 32 bytes of shadow space

write_hello_world:
    add RSP, 1 * 8              ; keep alignment after push
    
    mov ECX, EAX                ; write destination
    LEA RDX, [hello_world]      ; source string start address
    mov R8, hello_world_length  ; length of source string
    LEA R9, [result]            ; destination for number of bytes written
    push 0                      ; reserved, must be zero
    
    add RSP, 4 * 8              ; reserve 32 bytes of shadow space
    call WriteConsoleA
    sub RSP, 6 * 8              ; reclaim 48 bytes of stack space
    
exit:
    xor ECX, ECX                ; ECX ≔ EXIT_SUCCESS
    sub RSP, 4 * 8              ; reserve 32 bytes of shadow space
    call ExitProcess

; vim: set filetype=nasm:

显然,即使在 Win64 上,
HANDLE

大小也是 32 位

,因此使用 32 位寄存器就足够了。
错误

使用的 I/O 函数可能会失败,因此您应该检查返回值。

GetStdHandle

可能会返回

INVALID_HANDLE_VALUE
,您不应将其传递给
WriteConsoleA

:出于解释目的,我们假设它已被call编辑。中间的存根

jmp
_start
可以忽略。

    
	

© www.soinside.com 2019 - 2024. All rights reserved.