ассемблерный фрагмент
Попытка загнать текст программы в ассемблерную вставку ни к чему хорошему не приводит. Компилятор кроет нас матом, но не компилит. Редиска! Приходится действовать стратегически. То есть свирепо и радикально. Убираем директивы .code и .data вместе с ненужной инструкций "ret" (в оригинале она завершает программу, пользуясь тем фактом, что при запуске PE-файла на вершине стека лежит адрес на термирующую процедуру, однако, в стековой фрейме нашей ассемблерной вставки ничего подобного нет!).
Метку start можно, в принципе, и не убирать, но на фиг она будет торчать?! А вот к метке s0 подобный диалектический подход уже не приемлем и с ней надо кончать. В смысле — избавляться от этой твари любой ценой. Ну не поддерживает встроенный ассемблер директивы "db", что тут поделать?! Приходится объявлять строковую константу средствами самого Си. Она может быть размещена как в стеке (т. е. объявлена как локальная переменная), так и в секции данных (т. е. объявлена как глобальная переменная). В действительности, строки всегда размещаются в секции данных, а стек заносится лишь их копия, а копия — это оверхид, то есть накладные расходы и прочий маст-дай (примечание: некоторые оптимизирующие компиляторы, в том числе и ms vc, могут "загонять" строковые переменные в стек при помощи инструкций PUSH XXYYZZ, и тогда в секции данных их уже не оказывается).
Если переменная объявлена как глобальная, то ключевое слово offset сохраняет свою силу и компилятор не матерится. С локальными переменными все сложнее. Конструкция "push offset s0" в этом случае разворачивается компилятором в "push offset [ebp+x]", что с точки зрения синтаксиса совершенно бессмысленно! Но убирать "offset" нельзя, поскольку "push [ebp+x]" заталкивает в стек отнюдь не указатель на s0, а… значения первых четырех байт, то есть ведет себя как "*((DWORD*)s0)". Правильный вариант выглядит так: "lea eax,s0/push eax" (разумеется, вместо eax можно использовтаь любой другой регистр общего назначения).
Еще один нюанс — конструкция "call MessageBoxA" выполняется совсем не так, как задумывалось, поскольку вместо MessageBoxA коварный компилятор подставляет отнюдь не адрес самой MessageBoxA, а указатель на двойное слово, хранящее адрес MessageBoxA! Следовательно, чтобы программа не развалилась и не умерла, необходимо использовать префикс ds и тогда вызывающий код будет выглядеть так: "call ds:MessageBoxA"
Обобщив все вышесказанное, мы получаем следующую программу. Даже две! С объявлением строки как глобальной и локальной переменной:
#include <windows.h>
// глобальная переменная выводимая на экран
char s0[]="hello,wordl\n";
main()
{
__asm
{
push 0
push 0
push offset s0
push 0
call ds:MessageBoxA ; добавляем
ds:
}
}