Почему WriteConsoleW прерывается после вызова CoInitialize с использованием ml64
Я пытаюсь выполнить некоторую автоматизацию Office с помощью 64-разрядной сборки, используя ml64.exe из Visual Studio 2019. Прежде чем я смогу вызвать COM-интерфейсы Office, мне нужно вызвать CoInitialize. В настоящее время я просто тестирую инициализацию COM и запись в консоль (обычно я не пишу ассемблерный код). Если я закомментирую строку
call CoInitialize
Вызов API WriteConsoleW работает должным образом и выводит на экран сообщение "COM Failed to Initialize". Однако, как только я добавляю вызов CoInitialize обратно, на экран консоли ничего не выводится, и происходит сбой.
; *************************************************************************
; Proto types for API functions and structures
; *************************************************************************
EXTRN GetStdHandle:PROC
EXTRN WriteConsoleW:PROC
EXTRN CoCreateInstance:PROC
EXTRN CoInitialize:PROC
EXTRN SysFreeString:PROC
EXTRN SysStringByteLen:PROC
EXTRN SysAllocStringByteLen:PROC
EXTRN OleRun:PROC
EXTRN ExitProcess:PROC
.const
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
; *************************************************************************
; Object libraries
; *************************************************************************
includelib user32.lib
includelib kernel32.lib
includelib ole32.lib
includelib oleaut32.lib
; *************************************************************************
; Our data section.
; *************************************************************************
.data
strErrComFailed dw 'C','O','M',' ','F','a','i','l','e','d',' ','t','o',' ','i','n','i','t','i','a','l','i','z','e',0,0
strErrOutlookFailed dw 'F','a','i','l','e','d',' ','t','o',' ','i','n','i','t','i','a','l','i','z','e',' ','O','u','t','l','o','o','k',0,0
; {0006F03A-0000-0000-C000-000000000046}
CLSID_OutlookApplication dd 0006f03ah
dw 0000h
dw 0000h
dw 0C000h
db 00h
db 00h
db 00h
db 00h
db 00h
db 46h
; {00063001-0000-0000-C000-000000000046}
IID_OutlookApplication dd 00063001h
dw 0000h
dw 0000h
dw 0C000h
db 00h
db 00h
db 00h
db 00h
db 00h
db 46h
; {00000000-0000-0000-C000-000000000046}
IID_IUnknown dd 00000000h
dw 0000h
dw 0000h
dw 0C000h
db 00h
db 00h
db 00h
db 00h
db 00h
db 46h
; *************************************************************************
; Our executable assembly code starts here in the .code section
; *************************************************************************
.code
wcslen PROC inputString:QWORD
LOCAL stringLength:QWORD
mov QWORD PTR inputString, rcx
mov QWORD PTR stringLength, 0h
continue:
mov rax, QWORD PTR inputString
mov rcx, QWORD PTR stringLength
movzx eax, word ptr [rax+rcx*2]
test eax, eax
je finished
mov rax, QWORD PTR stringLength
inc rax
mov QWORD PTR stringLength, rax
jmp continue
finished:
mov rax, QWORD PTR stringLength
ret
wcslen ENDP
main PROC
LOCAL hStdOutput:QWORD
LOCAL hErrOutput:QWORD
LOCAL hResult:DWORD
xor ecx,ecx
call CoInitialize
mov DWORD PTR hResult, eax
mov ecx, STD_OUTPUT_HANDLE
call GetStdHandle
mov QWORD PTR hStdOutput, rax
mov ecx, STD_ERROR_HANDLE
call GetStdHandle
mov QWORD PTR hErrOutput, rax
lea rcx,strErrComFailed
call wcslen
mov QWORD PTR [rsp+32], 0
xor r9d, r9d ; lpNumberOfCharsWritten
mov r8d, eax ; nNumberOfCharsToWrite
lea rdx,QWORD PTR strErrComFailed
mov rcx,QWORD PTR hStdOutput
call WriteConsoleW
; When the message box has been closed, exit the app with exit code eax
mov ecx, eax
call ExitProcess
ret
main ENDP
End
Перед вызовом CoInitialize WinDbg показывает следующее состояние регистра:
00007ff7`563e1041 e865000000 call Win64App+0x10ab (00007ff7`563e10ab)
0:000> r
rax=00007ff7563e1037 rbx=0000000000000000 rcx=0000000000000000
rdx=00007ff7563e1037 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7563e1041 rsp=000000a905affa58 rbp=000000a905affa70
r8=000000a9058d6000 r9=00007ff7563e1037 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
Win64App+0x1041:
00007ff7`563e1041 e865000000 call Win64App+0x10ab (00007ff7`563e10ab)
0:000> r ecx
ecx=0
После вызова CoInitialize происходит следующее состояние регистра:
0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=8aa77f80a0990000
rdx=0000000000000015 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7563e1046 rsp=000000a905affa58 rbp=000000a905affa70
r8=0000029af97e2620 r9=0000029af97e1440 r10=0000000000000005
r11=000000a905aff9d8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
Win64App+0x1046:
00007ff7`563e1046 8945ec mov dword ptr [rbp-14h],eax ss:000000a9`05affa5c=00000000
0:000> r eax
eax=0
После вызова GetStdHandle:
0:000> r
rax=0000000000000074 rbx=0000000000000000 rcx=0000029af97d2840
rdx=0000000000000015 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7563e1053 rsp=000000a905affa58 rbp=000000a905affa70
r8=0000029af97e2620 r9=0000029af97e1440 r10=0000000000000005
r11=000000a905aff9d8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
При вызове WriteConsoleW кажется, что параметры все еще верны, но на экран ничего не выводится:
KERNEL32!WriteConsoleW:
00007ffb`a97028f0 ff258a4c0500 jmp qword ptr [KERNEL32!_imp_WriteConsoleW (00007ffb`a9757580)] ds:00007ffb`a9757580={KERNELBASE!WriteConsoleW (00007ffb`a697b750)}
0:000> du rdx
00007ff7`563e3000 "COM Failed to initialize"
0:000> r
rax=0000000000000018 rbx=0000000000000000 rcx=0000000000000074
rdx=00007ff7563e3000 rsi=0000000000000000 rdi=0000000000000000
rip=00007ffba97028f0 rsp=000000a905affa50 rbp=000000a905affa70
r8=0000000000000018 r9=0000000000000000 r10=0000000000000005
r11=000000a905aff9d8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
KERNEL32!WriteConsoleW:
00007ffb`a97028f0 ff258a4c0500 jmp qword ptr [KERNEL32!_imp_WriteConsoleW (00007ffb`a9757580)] ds:00007ffb`a9757580={KERNELBASE!WriteConsoleW (00007ffb`a697b750)}
Я попытался использовать CoInitializeEx вместо этого и имел ту же проблему:
mov edx, COINIT_APARTMENTTHREADED ; dwCoInit (COINIT_APARTMENTTHREADED = 2)
xor ecx, ecx ; pvReserved
call CoInitializeEx
1 ответ
X 64 ABI require . Стек всегда выровнен по 16 байтов, когда выполняется инструкция вызова, а также зарезервировано 32 байта. поэтому в каждой точке входа в функцию мы будем иметь:
RSP == 16*N + 8
так что мы вообще должны делать SUB RSP,40 + N*16
в теле функции, если мы будем вызывать другие функции. но когда мы объявляем LOCAL
переменные в функции - компилятор (masm64) выполняют некоторое выделение стека, но не переносят выравнивание стека и 32-байтовое зарезервированное пространство. так что нужно сделать это самому. также когда вы используетеLOCAL
переменные - использование masm64 RBP
зарегистрироваться для сохранения старых RSP
значение и восстановить его в конце (используйте leave
инструкция). и получить доступ к местным жителям черезRBP
так что ты не можешь изменить RBP
в функции себя.
так что код может быть следующим
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
EXTRN __imp_GetStdHandle:QWORD
EXTRN __imp_WriteConsoleW:QWORD
EXTRN __imp_CoInitialize:QWORD
EXTRN __imp_ExitProcess:QWORD
EXTRN __imp__getch:QWORD
EXTRN __imp_wcslen:QWORD
WSTRING macro text:VARARG
FOR arg, <text>
if @InStr( , arg, @ )
f = 0
FORC c, <arg>
IF f
DW '&c'
ENDIF
f = 1
ENDM
else
DW &arg
endif
ENDM
DW 0
ENDM
.const
strErrComFailed: WSTRING @----, 13, 10, @Press any key:, 13, 10
.code
maina PROC
LOCAL hStdOutput:QWORD
LOCAL hErrOutput:QWORD
LOCAL hResult:DWORD
sub rsp,32
and rsp,not 15
xor ecx,ecx
call __imp_CoInitialize
mov hResult, eax
mov ecx, STD_OUTPUT_HANDLE
call __imp_GetStdHandle
mov hStdOutput, rax
mov ecx, STD_ERROR_HANDLE
call __imp_GetStdHandle
mov hErrOutput, rax
lea rcx,strErrComFailed
call __imp_wcslen
mov QWORD PTR [rsp+32], 0
xor r9d, r9d ; lpNumberOfCharsWritten
mov r8d, eax ; nNumberOfCharsToWrite
lea rdx,strErrComFailed
mov rcx,hStdOutput
call __imp_WriteConsoleW
call __imp__getch
mov ecx, eax
call __imp_ExitProcess
ret
maina ENDP
END
также некоторые примечания:
функции импорта всегда вызываются через указатель. все эти имена указателей начинаются с__imp_
префикс. поэтому нам нужно объявить импортированный apiXxx
в качестве EXTRN __imp_Xxx:QWORD
- это более эффективное сравнение PROC
декларация - в этом случае компоновщику нужно построить заглушку с единственной инструкцией jmp:
Xxx proc
jmp __imp_Xxx
Xxx endp
конечно, лучше сделать прямой call __imp_Xxx
и не иметь Xxx
заглушка, вместо call Xxx
- код будет меньше и быстрее
вам не нужно реализовывать wcslen
сами - вы можете импортировать его реализацию из ntdllp.lib
(всегда) или msvcrt.lib
(очень зависит от конкретной реализации библиотеки, но msvcrt.dll, конечно, экспортируетwcslen
- вы можете собрать msvcrt.lib самостоятельно) и использоватьcall __imp_wcslen
использовать объявление как
strErrComFailed dw 'C','O','M',' '...
очень неудобно. вы можете использовать, например, такой макрос
WSTRING macro text:VARARG
FOR arg, <text>
if @InStr( , arg, @ )
f = 0
FORC c, <arg>
IF f
DW '&c'
ENDIF
f = 1
ENDM
else
DW &arg
endif
ENDM
DW 0
ENDM
;strErrComFailed: WSTRING @----, 13, 10, @Press any key:, 13, 10
наконец lpNumberOfCharsWritten
параметр в функции WriteConsoleW
не является обязательным. если вы ищете объявление, это sdk - оно объявлено с__out_opt
или _Out_opt_
. поэтому вы можете передать здесь 0, если такая информация не нужна