Я просто изучаю ассемблер 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
Вы не забыли установить программу как консольную программу? –
Я использую параметр '/ console', когда он отправляется в GoLink. Если мне нужно установить его в другом месте, например, в командной строке NASM или в самой сборке, тогда я не знаю, как это сделать. – liorean
Вы используете различные макросы, скрывающие фактический код. Является ли 'invoke' правильным правилом вызова? Создает ли 'proc' правильный 64-битный стек кадров? Когда вы перешли через программу в отладчике, наблюдали ли вы какие-то необычные коды ошибок? –