【问题标题】:Repeated call of WriteConsole (NASM x64 on Win64)重复调用 WriteConsole(Win64 上的 NASM x64)
【发布时间】:2021-01-06 18:23:45
【问题描述】:

我最近开始学习组装,为了练习,我想制作一个小游戏。 为了制作游戏的边框图形,我需要打印 n 次块字符。 为了测试这一点,我编写了以下代码:

bits 64

global main
extern ExitProcess
extern GetStdHandle
extern WriteConsoleA

section .text
    
main:
    mov rcx, -11
    call GetStdHandle   
    mov rbx, rax
drawFrame:
    mov r12, [sze]
    l:
    mov rcx, rbx
    mov rdx, msg
    mov r8, 1
    sub rsp, 48
    mov r9, [rsp+40]
    mov qword [rsp+32], 0
    call WriteConsoleA
    dec r12
    jnz l
    
    xor rcx, rcx
    call ExitProcess

section .data
    score dd 0
    sze dq 20
    msg db 0xdb

我想用 WinAPI 函数来做这个输出。 有趣的是,这段代码在使用 WriteConsoleA 打印一个字符后停止,但是当我使用 C 的 putchar 时,它可以正常工作。我还可以设法使用 WriteConsoleA 函数制作一个 C 等效项,该函数也可以正常工作。 C代码的反汇编并没有让我更进一步。

我怀疑我在使用我看不到的堆栈时出现了问题。希望有人能解释或指出。

【问题讨论】:

    标签: winapi assembly 64-bit x86-64 nasm


    【解决方案1】:

    您不想在每个循环中不断地从 RSP 中减去 48。您只需在循环之前和调用 C 库函数或 WinAPI 之前分配该空间一次。

    主要问题在于 R9 中的第四个参数。 WriteConsole 函数定义为:

    BOOL WINAPI WriteConsole(
      _In_             HANDLE  hConsoleOutput,
      _In_       const VOID    *lpBuffer,
      _In_             DWORD   nNumberOfCharsToWrite,
      _Out_opt_        LPDWORD lpNumberOfCharsWritten,
      _Reserved_       LPVOID  lpReserved
    );
    

    R9 应该是一个指向内存位置的指针,它返回一个带有写入字符数的DWORD,但你这样做:

    mov r9, [rsp+40]
    

    这会将内存地址RSP+40 开始的8 个字节移动到R9。你想要的是[rsp+40] 的地址,可以使用LEA 指令完成:

    lea r9, [rsp+40]
    

    您的代码可能如下所示:

    bits 64
    
    global main
    extern ExitProcess
    extern GetStdHandle
    extern WriteConsoleA
    
    section .text
        
    main:
        sub rsp, 56          ; Allocate space for local variable(s)
                             ; Allocate 32 bytes of space for shadow store
                             ; Maintain 16 byte stack alignment for WinAPI/C library calls
                             ; 56+8=64 . 64 is evenly divisible by 16.
        mov rcx, -11
        call GetStdHandle   
        mov rbx, rax
    drawFrame:
        mov r12, [sze]
    l:
        mov rcx, rbx
        mov rdx, msg
        mov r8, 1
        lea r9, [rsp+40]
        mov qword [rsp+32], 0
        call WriteConsoleA
        dec r12
        jnz l
        
        xor rcx, rcx
        call ExitProcess
    
    section .data
        score dd 0
        sze dq 20
        msg db 0xdb
    

    重要提示:为了符合64-bit Microsoft ABI,您必须在调用 WinAPI 或 C 库函数之前保持堆栈指针的 16 字节对齐.在调用 main 函数时,堆栈指针 (RSP) 是 16 字节对齐的。在main 函数开始执行时,堆栈未对齐 8,因为 8 字节的返回地址被压入堆栈。 48+8=56 不会让您回到 16 字节对齐的堆栈地址(56 不能被 16 整除),但 56+8=64 可以。 64 可以被 16 整除。

    【讨论】:

    • 非常感谢!我不知何故认为该函数会在返回后弹出影子空间和分配的堆栈空间,从而将堆栈指针向前移动 48 个字节。 WinAPI 文档也对阴影空间非常模糊。 :)
    猜你喜欢
    • 2013-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-29
    • 2012-10-17
    • 2010-09-05
    相关资源
    最近更新 更多