Я бы поставил под сомнение необходимость вызова 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 напрямую), и в этом случае возможно использование ограничения памяти впоследствии встроенная сборка.