2016-03-13 2 views
3

Я просто изучаю ассемблер x64, и я только что столкнулся с проблемой, которую я не могу объяснить. Из того, как ReadFile Kernel32.dll работает с кодом C, я ожидал, что он остановится на консоли и дождитесь, пока я войду в полную строку, прежде чем вернуться к вызывающему, что-то неожиданно для меня совсем не работает. Кажется, что процедура ReadFile возвращает строку нулевой длины, независимо от того, что нажата на клавиатуре, или, что то, что передается в нее из канала в командной оболочке.Попытка прочитать консольный ввод с ассемблера x64 с использованием чистых API-интерфейсов Win64 (нет времени выполнения C)

;%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 и что работает, но теперь я пытаюсь получить рабочую Verion без использования этого костыля. Я использую NASM (с NASMX включают файлы, обеспечивающие макросы) и GoLink, ссылающиеся на kernel32.dll. Что я делаю не так? Какое поведение API я пропустил? Из статей MSDN на консольных API Win32 поведение ReadFile меня удивляет.

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

EDIT Ну, Рэймонд Чен спросил о макро-расширениях, и если они были правы в соответствии с заходящими конвенциями, так:

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, кажется, все в порядке.

EDIT Хорошо, благодаря ответу Гарри Джонстона я получил код работы:

;%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 или нет, поэтому я должен буду изучить это еще.

EDIT Версия без макросов, которые, как я считаю, полностью соответствуют 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 
+0

Вы не забыли установить программу как консольную программу? –

+0

Я использую параметр '/ console', когда он отправляется в GoLink. Если мне нужно установить его в другом месте, например, в командной строке NASM или в самой сборке, тогда я не знаю, как это сделать. – liorean

+0

Вы используете различные макросы, скрывающие фактический код. Является ли 'invoke' правильным правилом вызова? Создает ли 'proc' правильный 64-битный стек кадров? Когда вы перешли через программу в отладчике, наблюдали ли вы какие-то необычные коды ошибок? –

ответ

3

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

bNamelim db  0xff 

[...]

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

Вы пропускание адреса, а не значение bNamelim.

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

+0

Спасибо, да, это решает мою проблему с 'ReadFile', не останавливаясь ждать на консольном вводе (так что больше не нужно для' WaitForSingleObject'). Аналогично, «WriteFile», где я пытаюсь повторить эту строку, нужно изменить, чтобы использовать значение 'bNamelen' вместо его адреса. – liorean

+0

@liorean: или измените 'bNamelim' на символическую константу, используя' equ' вместо 'db'. У вас нет кода, который изменяет его значение, поэтому вы можете получить значение, закодированное как непосредственное в потоке команд, вместо того, чтобы писать код, который его загружает. –

+0

Peter: если вы посмотрите на последнее редактирование, которое я сделал (добавленный в конце, не меняя исходный код проблемы), вы можете увидеть исправленный и запущенный код. Это изменение именно то, что я сделал. Хотя мне также пришлось изменить 'bNamelen' на' [bNamelen] 'при вызове' WriteFile', чтобы снова вывести эту строку в консоль для работы. – liorean

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) консоли начинают с тех бит, которые установлены в любом случае, поэтому вам не нужно их снова устанавливать.

+0

Изменения режима консоли были добавлением отчаяния из некоторого кода, который я нашел. Весь 'вызвать GetConsoleMode, QWORD [hStdInput], hMode мов RDX, [hMode] и дл, ENABLE_PROCESSED_INPUT и дл, ENABLE_LINE_INPUT и дл, ENABLE_ECHO_INPUT Invoke SetConsoleMode, QWORD [hStdInput], RDX ' может быть и поведение программы остается неизменным. – liorean

+0

Приобретено для того, чтобы заметить последовательность инструкций, которые, вероятно, не делают то, что автор намеревался, даже если это не настоящая проблема. –