C-pushl сборки не работает для функции WriteFile - PullRequest
1 голос
/ 02 июля 2019

Я использовал C-сборку для вызова функции (_GetStdHandle @ 4) для получения (вывода) дескриптора, а затем использовал функцию (_WriteFile @ 20) для записи моей строки в консоли, используя дескриптор, который я получил (_GetStdHandle @ 4). я использовал (pushl) в своем исходном коде для каждой функции для передачи параметров, но что-то не так, потому что (WriteFile)) функция возвращает ошибку (6), которая является недействительным дескриптором, но дескриптор действителен ... поэтому что-то не так с передачей аргумента. .. да ... моя проблема заключается в передаче аргумента в функцию (_WriteFile) с использованием (pushl) ... в этом коде я использовал (g) для каждого аргумента, потому что нет причин перемещать параметры в регистр, а затем выдвигать регистры ... так что я не использовал (r), но если я использую (r), программа работает без каких-либо проблем (которые сначала перемещают параметры в регистры, а затем передают регистры (в которые я хочу передать параметры, не перемещая их в регистры) этот код ничего не показывает, и проблема в функции (WriteFile), и если я использую (r) для параметров (WriteFile), печать будет выполнена, но почему я не могу использовать «g», чтобы не перемещать параметры в регистры?

    typedef void * HANDLE;

#define GetStdHandle(result, handle)                                    \
    __asm (                                                             \
        "pushl  %1\n\t"                                                 \
        "call   _GetStdHandle@4"                                        \
            : "=a" (result)                                             \
            : "g" (handle))

#define WriteFile(result, handle, buf, buf_size, written_bytes)         \
    __asm (                                                             \
        "pushl  $0\n\t"                                                 \
        "pushl  %1\n\t"                                                 \
        "pushl  %2\n\t"                                                 \
        "pushl  %3\n\t"                                                 \
        "pushl  %4\n\t"                                                 \
        "call   _WriteFile@20"                                          \
            : "=a" (result)                                             \
            : "g" (written_bytes), "g" (buf_size), "g" (buf), "g" (handle))

int main()
{
    HANDLE handle;
    int write_result;
    unsigned long written_bytes;

    GetStdHandle(handle, -11);
    if(handle != INVALID_HANDLE_VALUE)
    {
        WriteFile(write_result, handle, "Hello", 5, & written_bytes);
    }

    return 0;
}

Код сборки для этой программы:

.file   "main.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "Hello\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB25:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
/APP
    pushl  $-11
    call   _GetStdHandle@4
 # 0 "" 2
/NO_APP
    movl    %eax, 12(%esp)
    cmpl    $-1, 12(%esp)
    je  L2
    leal    4(%esp), %eax
/APP
    pushl  $0
    pushl  %eax
    pushl  $5
    pushl  $LC0
    pushl  12(%esp)
    call   _WriteFile@20
 # 0 "" 2
/NO_APP
    movl    %eax, 8(%esp)
L2:
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE25:
    .ident  "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"

в чем проблема?

1 Ответ

3 голосов
/ 02 июля 2019

Я бы поставил под сомнение необходимость вызова WINAPI через такие оболочки, а не вызывать их напрямую. Вы можете объявить прототипы для соглашения о вызовах stdcall с помощью
__attribute__((stdcall))

Если вам не нужно использовать встроенную сборку, вам не следует. Встроенную сборку GCC трудно понять правильно. Неправильное понимание может привести к тому, что код будет работать до тех пор, пока однажды не сработает, особенно если включена оптимизация. У Дэвида Уолферда есть хорошая статья о том, почему вам не следует использовать встроенную сборку , если вам это не нужно.


Основная проблема видна в этом разделе сгенерированного кода:

pushl  $0
pushl  %eax
pushl  $5
pushl  $LC0
pushl  12(%esp)
call   _WriteFile@20

GCC вычислил операнд памяти (дескриптор) для первого параметра как 12(%esp). Проблема в том, что вы изменили ESP с предыдущими нажатиями, и теперь смещение 12(%esp) больше не там, где handle.

Чтобы обойти эту проблему, вы можете передавать адреса памяти через регистры или сразу же (если это возможно). Вместо использования ограничения g, которое включает m (ограничения памяти), просто используйте ri для регистров и немедленных операций. Это предотвращает генерацию операндов памяти. Если вы передаете указатели через регистры, вам также нужно добавить "memory" clobber.

Соглашение о вызовах STDCALL (WINAPI) позволяет функции уничтожать EAX , ECX и EDX (AKA * 1034) * энергозависимые регистры ). Возможно, что GetStdHandle и WriteFile будут перекрывать ECX и EDX , а также возвращать значение в EAX . Вы должны убедиться, что ECX и EDX также перечислены как клобберы (или имеют ограничение, помечающее его как выходной), иначе компилятор может принять значения в этих регистрах как то же самое до и после того, как встроенные блоки сборки завершены. Если они отличаются, это может привести к незначительным ошибкам.

С этими изменениями ваш код может выглядеть примерно так:

#define INVALID_HANDLE_VALUE (void *)-1    
typedef void *HANDLE;

#define GetStdHandle(result, handle)                                    \
    __asm (                                                             \
        "pushl  %1\n\t"                                                 \
        "call   _GetStdHandle@4"                                        \
            : "=a" (result)                                             \
            : "g" (handle)                                              \
            : "ecx", "edx")

#define WriteFile(result, handle, buf, buf_size, written_bytes)         \
    __asm __volatile (                                                  \
        "pushl  $0\n\t"                                                 \
        "pushl  %1\n\t"                                                 \
        "pushl  %2\n\t"                                                 \
        "pushl  %3\n\t"                                                 \
        "pushl  %4\n\t"                                                 \
        "call   _WriteFile@20"                                          \
            : "=a" (result)                                             \
            : "ri" (written_bytes), "ri" (buf_size), "ri" (buf), "ri" (handle) \
            : "memory", "ecx", "edx")

int main()
{
    HANDLE handle;
    int write_result;
    unsigned long written_bytes;

    GetStdHandle(handle, -11);
    if(handle != INVALID_HANDLE_VALUE)
    {
        WriteFile(write_result, handle, "Hello", 5, &written_bytes);
    }

    return 0;
}

Примечания :

  • Я пометил WriteFile встроенную сборку как __volatile, так что оптимизатор не может удалить всю встроенную сборку, если считает, что result не используется. Компилятор не знает, что побочным эффектом функции является обновление дисплея. Отметьте функцию как энергозависимую, чтобы предотвратить полное удаление встроенного узла.

  • GetStdHandle не имеет проблем с потенциальными операндами памяти, поскольку после начального push %1 больше нет ограничений. Проблема, с которой вы сталкиваетесь, возникает только тогда, когда ESP был изменен (через PUSH / POP или изменен на ESP напрямую), и в этом случае возможно использование ограничения памяти впоследствии встроенная сборка.

...