尝试使用纯 Win64 API(无 C 运行时)从 x64 汇编器读取控制台输入

Trying to read console input from x64 assembler using pure Win64 APIs (No C runtime)

我刚开始学习 x64 汇编程序,我刚遇到一个我无法解释的问题。从 Kernel32.dll 的 ReadFile 如何从 C 代码工作,我期待它在控制台停止并等待我输入完整的行,然后 returning 到调用者,令人惊讶的是没有为我工作。 ReadFile 过程似乎 return 一个零长度的字符串,无论在键盘上按下什么,或者就此而言,从命令 shell.

上的管道传递给它的是什么
;%USERPROFILE%\nasm\learning\stdio.asm
;
;Basic usage of the standard input/output/error channels.
;
;nasm -f win64 stdio.asm
;golink /console /ni /entry main stdio.obj kernel32.dll

%include "\inc\nasmx.inc"
%include "\inc\win32\windows.inc"
%include "\inc\win32\kernel32.inc"

%ifidn __BITS__, 0x40
;// assert: set call stack for procedure prolog to max
;// invoke param bytes for 64-bit assembly mode
DEFAULT REL
NASMX_PRAGMA CALLSTACK, 0x30
%endif

entry toplevel
;
section .data
errmsg      db      "No errors to report!",0xd,0xa
errmsglen   equ     $-errmsg
query       db      "What is your name?",0xd,0xa
querylen    equ     $-query
greet       db      "Welcome, "
greetlen    equ     $-greet
crlf        db      0xd,0xa
crlflen     equ     $-crlf
bNamelim    db      0xff
minusone    equ     0xffffffffffffffff
zero        equ     0x0

section .bss
    hStdInput   resq    0x1
    hStdOutput  resq    0x1
    hStdError   resq    0x1
    hNum        resq    0x1
    hMode       resq    0x1
    bName       resb    0x100
    bNamelen    resq    0x1

section .text
proc    toplevel, ptrdiff_t argcount, ptrdiff_t cmdline
locals none
    invoke GetStdHandle, STD_INPUT_HANDLE
    mov qword [hStdInput], rax
;    invoke GetConsoleMode, qword [hStdInput],  hMode
;    mov rdx, [hMode]
;    and dl, ENABLE_PROCESSED_INPUT
;    and dl, ENABLE_LINE_INPUT
;    and dl, ENABLE_ECHO_INPUT
;    invoke SetConsoleMode, qword [hStdInput], rdx
    invoke GetStdHandle, STD_OUTPUT_HANDLE
    mov qword [hStdOutput], rax
    invoke GetStdHandle, STD_ERROR_HANDLE
    mov qword [hStdError], rax

    invoke WriteFile, qword [hStdOutput], query, querylen, hNum, zero
    invoke WaitForSingleObject, qword[hStdInput], minusone
    invoke ReadFile, qword [hStdInput], bName, bNamelim, bNamelen, zero
    invoke WriteFile, qword [hStdOutput], greet, greetlen, hNum, zero
    invoke WriteFile, qword [hStdOutput], bName, bNamelen, hNum, zero
    invoke WriteFile, qword [hStdOutput], crlf, crlflen, hNum, zero
    invoke WriteFile, qword [hStdError], errmsg, errmsglen, hNum, zero
    invoke ExitProcess, zero
endproc

我已经使用 C 运行时完成了相同的功能并且它有效,但现在我试图在不使用那个拐杖的情况下获得一个工作版本。我正在使用 NASM(使用 NASMX 包含提供宏的文件)和 GoLink,链接到 kernel32.dll。我究竟做错了什么?我错过了 which API 的什么行为? 从有关 Win32 控制台的 MSDN 文章 APIs,ReadFile 的行为让我感到惊讶。

此外,如果我从程序集中删除 WaitForSingleObject 调用,C 等价物中不存在的东西,整个程序完成 运行 而无需停止等待控制台输入,尽管 ReadFile 被假定为做到这一点。

编辑 好吧,Raymond Chen 询问了宏扩展以及根据调用约定它们是否正确,所以:

    invoke GetStdHandle, STD_INPUT_HANDLE
    mov qword [hStdInput], rax

这变成了

    sub rsp,byte +0x20
    mov rcx,0xfffffffffffffff6
    call qword 0x2000
    add rsp,byte +0x20
    mov [0x402038],rax

这似乎遵循 Win64 的 0-4 整数参数调用的调用约定就好了。五参数形式怎么样?

    invoke WriteFile, qword [hStdOutput], query, querylen, hNum, zero

这变成了

    sub rsp,byte +0x30
    mov rcx,[0x402040]
    mov rdx,0x402016
    mov r8d,0x14
    mov r9,0x402050
    mov qword [rsp+0x20],0x0
    call qword 0x2006
    add rsp,byte +0x30

在我看来,至少 invoke 宏是正确的。 proc-locals-endproc 宏更难,因为它分散了,我相信 invoke 宏以某种方式依赖它。无论如何,序言最终扩展为:

    push rbp
    mov rbp,rsp
    mov rax,rsp
    and rax,byte +0xf
    jz 0x15
    sub rsp,byte +0x10
    and spl,0xf0
    mov [rbp+0x10],rcx
    mov [rbp+0x18],rdx

结语最终扩展为:

    mov rsp,rbp
    pop rbp
    ret

根据我对 Win64 的无可否认的微薄知识,两者似乎都可以。

编辑 好的,多亏了 Harry Johnston 的回答,我的代码才开始工作:

;%USERPROFILE%\nasm\learning\stdio.asm
;
;Basic usage of the standard input/output/error channels.
;
;nasm -f win64 stdio.asm
;golink /console /ni /entry main stdio.obj kernel32.dll

%include "\inc\nasmx.inc"
%include "\inc\win32\windows.inc"
%include "\inc\win32\kernel32.inc"

%ifidn __BITS__, 0x40
;// assert: set call stack for procedure prolog to max
;// invoke param bytes for 64-bit assembly mode
DEFAULT REL
NASMX_PRAGMA CALLSTACK, 0x30
%endif

entry toplevel

section .data
errmsg      db      "No errors to report!",0xd,0xa
errmsglen   equ     $-errmsg
query       db      "What is your name?",0xd,0xa
querylen    equ     $-query
greet       db      "Welcome, "
greetlen    equ     $-greet
crlf        db      0xd,0xa
crlflen     equ     $-crlf
bNamelim    equ     0xff
minusone    equ     0xffffffffffffffff
zero        equ     0x0

section .bss
    hStdInput   resq    0x1
    hStdOutput  resq    0x1
    hStdError   resq    0x1
    hNum        resq    0x1
    hMode       resq    0x1
    bName       resb    0x100
    bNamelen    resq    0x1

section .text
proc    toplevel, ptrdiff_t argcount, ptrdiff_t cmdline
locals none
    invoke GetStdHandle, STD_INPUT_HANDLE
    mov qword [hStdInput], rax
    invoke GetStdHandle, STD_OUTPUT_HANDLE
    mov qword [hStdOutput], rax
    invoke GetStdHandle, STD_ERROR_HANDLE
    mov qword [hStdError], rax

    invoke WriteFile, qword [hStdOutput], query, querylen, hNum, zero
    invoke ReadFile, qword [hStdInput], bName, bNamelim, bNamelen, zero
    invoke WriteFile, qword [hStdOutput], greet, greetlen, hNum, zero
    invoke WriteFile, qword [hStdOutput], bName, [bNamelen], hNum, zero
    invoke WriteFile, qword [hStdOutput], crlf, crlflen, hNum, zero
    invoke WriteFile, qword [hStdError], errmsg, errmsglen, hNum, zero
    invoke ExitProcess, zero
endproc

然而,该代码仍然没有回答 Raymond Chen 关于宏的问题以及它们是否违反 Win64 ABI,所以我将不得不再研究一下。

编辑 我认为没有宏的版本完全遵循 x64 ABI,包括展开数据。

;%USERPROFILE%\nasm\learning\stdio.asm
;
;Basic usage of the standard input/output/error channels.
;
;nasm -f win64 stdio.asm
;golink /console /ni /entry main stdio.obj kernel32.dll

;Image setup
bits 64
default rel
global main

;Linkage
extern GetStdHandle
extern WriteFile
extern ReadFile
extern ExitProcess

;Read only data
section .rdata use64
    zero:                   equ     0x0
    query:                  db      "What is your name?",0xd,0xa
    querylen:               equ     $-query
    greet:                  db      "Welcome, "
    greetlen:               equ     $-greet
    errmsg:                 db      "No errors to report!",0xd,0xa
    errmsglen:              equ     $-errmsg
    crlf:                   db      0xd,0xa
    crlflen:                equ     $-crlf
    bNamelim:               equ     0xff
    STD_INPUT_HANDLE:       equ     -10
    STD_OUTPUT_HANDLE:      equ     -11
    STD_ERROR_HANDLE:       equ     -12
    UNW_VERSION:            equ     0x1
    UNW_FLAG_NHANDLER:      equ     0x0
    UNW_FLAG_EHANDLER:      equ     0x1
    UNW_FLAG_UHANDLER:      equ     0x2
    UNW_FLAG_CHAININFO:     equ     0x4
    UWOP_PUSH_NONVOL:       equ     0x0
    UWOP_ALLOC_LARGE:       equ     0x1
    UWOP_ALLOC_SMALL:       equ     0x2
    UWOP_SET_FPREG:         equ     0x3
    UWOP_SAVE_NONVOL:       equ     0x4
    UWOP_SAVE_NONVOL_FAR:   equ     0x5
    UWOP_SAVE_XMM128:       equ     0x8
    UWOP_SAVE_XMM128_FAR:   equ     0x9
    UWOP_PUSH_MACHFRAME:    equ     0xa

;Uninitialised data
section .bss use64
    argc:       resq    0x1
    argv:       resq    0x1
    envp:       resq    0x1
    hStdInput:  resq    0x1
    hStdOutput: resq    0x1
    hStdError:  resq    0x1
    hNum:       resq    0x1
    hMode:      resq    0x1
    bName:      resb    0x100
    bNamelen:   resq    0x1

;Program code
section .text use64
main:
.prolog:
.argc:    mov qword [argc], rcx
.argv:    mov qword [argv], rdx
.envp:    mov qword [envp], r8
.rsp:     sub rsp, 0x8*0x4+0x8

.body:
        ; hStdInput = GetStdHandle (STD_INPUT_HANDLE)
        mov rcx, qword STD_INPUT_HANDLE
        call GetStdHandle
        mov qword [hStdInput], rax

        ; hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE)
        mov rcx, qword STD_OUTPUT_HANDLE
        call GetStdHandle
        mov qword [hStdOutput], rax

        ; hStdError = GetStdHandle (STD_ERROR_HANDLE)
        mov rcx, qword STD_ERROR_HANDLE
        call GetStdHandle
        mov qword [hStdError], rax

        ; WriteFile (*hStdOutput, &query, querylen, &hNum, NULL)
        mov rcx, qword [hStdOutput]
        mov rdx, qword query
        mov r8d, dword querylen
        mov r9, qword hNum
        mov qword [rsp+0x20], zero
        call WriteFile

        ; ReadFile (*hStdInput, &bName, bNamelim, &bNameLen, NULL)
        mov rcx, qword [hStdInput]
        mov rdx, qword bName
        mov r8d, dword bNamelim
        mov r9, qword bNamelen
        mov qword [rsp+0x20], zero
        call ReadFile

        ; WriteFile (*hStdOutput, &crlf, crlflen, &hNum, NULL)
        mov rcx, qword [hStdOutput]
        mov rdx, qword crlf
        mov r8d, dword crlflen
        mov r9, qword hNum
        mov qword [rsp+0x20], zero
        call WriteFile

        ; WriteFile (*hStdOutput, &greet, greetlen, &hNum, NULL)
        mov rcx, qword [hStdOutput]
        mov rdx, qword greet
        mov r8d, dword greetlen
        mov r9, qword hNum
        mov qword [rsp+0x20], zero
        call WriteFile

        ; WriteFile (*hStdOutput, &bName, *bNamelen, &hNum, NULL)
        mov rcx, qword [hStdOutput]
        mov rdx, qword bName
        mov r8d, dword [bNamelen]
        mov r9, qword hNum
        mov qword [rsp+0x20], zero
        call WriteFile

        ; WriteFile (*hStdOutput, &crlf, crlflen, &hNum, NULL)
        mov rcx, qword [hStdOutput]
        mov rdx, qword crlf
        mov r8d, dword crlflen
        mov r9, qword hNum
        mov qword [rsp+0x20], zero
        call WriteFile

        ; WriteFile (*hStdError, &errmsg, errmsglen, &hNum, NULL)
        mov rcx, qword [hStdError]
        mov rdx, qword errmsg
        mov r8d, dword errmsglen
        mov r9, qword hNum
        mov qword [rsp+0x20], zero
        call WriteFile

        ; ExitProcess(0)
.exit:  xor ecx, ecx
        call ExitProcess

.rval:  xor eax, eax ; return 0
.epilog:
        add rsp, 0x8*0x4+0x8
        ret
.end:

; Win64 Windows API x64 Structured Exception Handling (SEH) - procedure data
section .pdata  rdata align=4 use64
    pmain:
    .start: dd      main     wrt ..imagebase 
    .end:   dd      main.end wrt ..imagebase 
    .info:  dd      xmain    wrt ..imagebase 

; Win64 Windows API x64 Structured Exception Handling (SEH) - unwind information
section .xdata  rdata align=8 use64
    xmain:
    .versionandflags:
            db      UNW_VERSION + (UNW_FLAG_NHANDLER << 0x3) ; Version = 1
    ; Version is low 3 bits. Handler flags are high 5 bits.
    .size:  db      main.body-main.prolog ; size of prolog that is
    .count: db      0x1 ; Only one unwind code
    .frame: db      0x0 + (0x0 << 0x4) ; Zero if no frame pointer taken
    ; Frame register is low 4 bits, Frame register offset is high 4 bits,
    ; rsp + 16 * offset at time of establishing
    .codes: db      main.body-main.prolog ; offset of next instruction
            db      UWOP_ALLOC_SMALL + (0x4 << 0x4) ; UWOP_INFO: 4*8+8 bytes
    ; Low 4 bytes UWOP, high 4 bytes op info.
    ; Some ops use one or two 16 bit slots more for addressing here
            db      0x0,0x0 ; Unused record to bring the number to be even
    .handl: ; 32 bit image relative address to entry of exception handler
    .einfo: ; implementation defined structure exception info

问题实际上是在您更改控制台模式时:

...
and dl, ENABLE_PROCESSED_INPUT
and dl, ENABLE_LINE_INPUT
and dl, ENABLE_ECHO_INPUT
...

因为 ENABLE_* 宏是单个位,and将它们组合在一起会得到零,这意味着您将零传递给 SetConsoleMode。如果要设置位,请使用 or 而不是 and。如果您要清除这些位,则需要通过在前面添加 ~(二进制 NOT)来反转这些位。

此外,根据 MSDN for GetConsoleInput(具体来说,dwMode 参数),控制台在启动时无论如何都会设置这些位,因此您无需再次设置它们。

我怀疑这是你的问题:

bNamelim    db      0xff

[...]

invoke ReadFile, qword [hStdInput], bName, bNamelim, bNamelen, zero

您传递的是地址而不是 bNamelim 的值。

我不确定 ReadFile 应该如何响应大于 32 位的值,但这肯定不是您想要的。