Попытка прочитать консольный ввод из ассемблера x64 с использованием чистых Win64 API (без времени выполнения C)

Я только изучаю ассемблер x64, и я только что столкнулся с проблемой, которую я не могу объяснить. Исходя из того, как ReadFile Kernel32.dll работает с кодом C, я ожидал, что он остановится на консоли и подождет, пока я введу полную строку, прежде чем вернуться к вызывающей стороне, что удивительно для меня не сработало. Процедура 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 я пропустил? Из статей MSDN, посвященных консольным API Win32, поведение ReadFile меня удивляет.

Кроме того, если я удаляю вызов WaitForSingleObject из сборки, чего нет в эквиваленте C, вся программа завершает работу, не останавливаясь, ожидая ввода с консоли, несмотря на то, что ReadFile должен был это сделать.

РЕДАКТИРОВАТЬ Ну, Раймонд Чен спросил о макро-расширениях и были ли они правильными в соответствии с соглашениями о вызовах, так

    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, кажется нормальным.

РЕДАКТИРОВАТЬ Хорошо, благодаря ответу Гарри Джонстона, я получил работающий код:

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

Тем не менее, этот код по-прежнему не отвечает на проблемы Рэймонда Чена с макросами и тем, нарушают ли они 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 ответа

Решение

Я подозреваю, что это ваша проблема:

bNamelim    db      0xff

[...]

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

Вы передаете адрес, а не значение bNamelim,

Я не уверен, как именно ReadFile должен реагировать на значение, превышающее 32 бита, но это определенно не то, что вы хотели сделать.

Проблема на самом деле, когда вы меняете режим консоли:

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

Поскольку ENABLE_* макросы - это одиночные биты, andих объединение приводит к нулю, что означает, что вы передаете ноль в SetConsoleMode, Если вы хотите установить биты, используйте or вместо and, Если вы пытаетесь очистить биты, вам нужно инвертировать биты, добавив ~ (двоичное НЕ).

Кроме того, в соответствии с MSDN дляGetConsoleInput (в частности, dwMode параметр), консоли начинаются с установленных битов, так что вам не нужно устанавливать их заново.

Другие вопросы по тегам