2016-03-13 58 views
3

我剛學習x64彙編程序,剛剛遇到一個我無法解釋的問題。從Kernel32.dll的ReadFile如何從C代碼運行,我期待它停止在控制檯,並等待我輸入一個完整的行,然後再返回給調用者,這令人驚訝的是根本無法爲我工作。 ReadFile過程似乎返回一個零長度的字符串,不管鍵盤上按下什麼,或者從那個命令外殼上的管道傳遞給它。嘗試從使用純Win64 API(無C運行時)的x64彙編程序讀取控制檯輸入

;%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應該這樣做。

編輯 嗯,雷蒙德陳問起宏展開,如果他們根據調用約定是正確的,那麼:

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 

然而,該代碼仍然沒有回答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 
+0

您是否記得將您的程序設置爲控制檯程序? –

+0

當它發送到GoLink時,我使用'/ console'參數。如果我需要將它設置在其他地方,比如在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:或者使用'equ'而不是'db'將'bNamelim'更改爲一個符號常量。您沒有任何修改其值的代碼,因此您可以將值編碼爲立即指令流,而不必編寫加載它的代碼。 –

+0

Peter:如果你看看我做的最後一個編輯(最後添加,不改變最初的問題代碼),你可以看到固定的和正在運行的代碼。這一變化正是我所做的。雖然我也不得不在'WriteFile'的調用中將'bNamelen'更改爲'[bNamelen]',以便將該字符串輸出到控制檯再次工作。 – 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 MOV RDX,[HMODE] 和DL,ENABLE_PROCESSED_INPUT 和DL,ENABLE_LINE_INPUT 和DL,ENABLE_ECHO_INPUT 調用SetConsoleMode,QWORD [hStdInput],RDX ' 可以是刪除和程序行爲保持不變。 – liorean

+0

Upvoted注意到可能不符合作者意圖的一系列指令,即使這不是實際問題。 –

相關問題