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

时间:2016-03-13 01:26:22

标签: windows assembly x86 nasm win64

我刚学习x64汇编程序,我刚遇到一个我无法解释的问题。从Kernel32.dll的ReadFile如何在C代码中工作,我期待它停在控制台并等待我在返回调用者之前输入一个完整的行,这对我来说根本不起作用。无论在键盘上按下什么,或者从命令shell上的管道传入的内容,ReadFile过程似乎返回零长度字符串。

;%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。我究竟做错了什么?我错过了哪种API的行为? 从Win32控制台API上的MSDN文章中,ReadFile的行为让我感到惊讶。

此外,如果我从程序集中删除WaitForSingleObject调用,这是C等效项中不存在的东西,整个程序在没有停止等待控制台输入的情况下完成运行,尽管ReadFile应该这样做。 / p>

修改 好吧,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

2 个答案:

答案 0 :(得分:3)

我怀疑这是你的问题:

bNamelim    db      0xff

[...]

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

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

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

答案 1 :(得分:2)

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

...
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参数),控制台始终会设置这些位,因此您无需再次设置它们。