Почему 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, если такая информация не нужна

Другие вопросы по тегам