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