Главная Случайная страница Контакты | Мы поможем в написании вашей работы! | ||
|
Программирование под 64-битную версию Windows мало чем отличается от традиционного, только все операнды и адреса по умолчанию 64-разярные, а параметры API-функций передаются через регистры, а не через стек. Первые четыре аргумента всех API-функций передаются в регистрах RCX, RDX, R8 и R9 (регистры перечислены в порядке следования аргументов, крайний левый аргумент помещается в RCX). Остальные параметры кладутся на стек. Все это называется x86-64 fast calling conversion (соглашение о быстрой передаче параметров для x86-64), подробное описание которой можно найти в статье "The history of calling conventions, part 5 amd64" (http://blogs.msdn.com/oldnewthing/archive/2004/01/14/58579.aspx). Так же нелишне заглянуть на страничку бесплатного компилятора Free PASCAL и поднять документацию по способам вызова API: http://www.freepascal.org/wiki/index.php/Win64/AMD64_API.
В частности, вызов функции с пятью аргументами API_func(1,2,3,4,5) выглядит так:
mov dword ptr [rsp+20h], 5; кладем на стек пятый слева аргумент
mov r9d, 4; передаем четвертый слева аргумент
mov r8d, 3; передаем третий слева аргумент
mov edx, 2; передаем второй слева аргумент
mov ecx, 1; передаем первый слева аргумент
call API_func
Листинг 8 пример вызова API-функции с пятью параметрами по соглашению x86-64
Смещение пятого аргумента относительно верхушки стека требует пояснений. Почему оно равно 20h? Ведь адрес возврата занимает только 8 байт. Какая су… сущность съела все остальные? Оказывается, они "резервируются" для первых четырех аргументов, переданных через регистры. "Зарезервированные" ячейки содержат неинициализированный мусор и по-буржуйски называются "spill", что переводится как "затычка" или "потеря".
Вот минимум знаний, необходимых для выживания в мире 64-битной Windows при программировании на ассемблере. Остается разобрать самую малость. Как эти самые 64-бита заполучить?! Для перевода FASM'а в x86-64 режим достаточно указать директиву "use64" и дальше шпрехать как обычно.
Ниже идет пример простейшей x86-64 программы, которая не делает ничего, только возвращает в регистре RAX значение "ноль".
; сообщаем FASM'у, что мы хотим программировать на x86-64
Use64
xor r9,r9; обнуляем регистр r9
mov rax,r9; пересылаем в rax,r9 (можно сразу mov rax,0, но неинтересно)
ret; выходим туда откуда пришли
Листинг 9 простейшая 64-битная программа
Никаких дополнительных аргументов командной строки указывать не надо, просто сказать "fasm file-name.asm" и все! Через несколько секунд образуется файл file-name.bin, который в hex-представлении выглядит следующим образом:
4D 31 C9 xor r9, r9
4C 89 C8 mov rax, r9
C3 retn
Листинг 10 дизассемблерный листинг простейшей 64-битной программы
Формально, это типичный com-файл, вот только запустить его не удастся (во всяком случае, ни одна популярная ось его не "съест") и необходимо замутить законченный ELF или PE, в заголовке которого будет явно прописана нужна разрядность.
Начиная с версии 1.64 ассемблер FASM поддерживает специальную директиву "format PE64", автоматически формирующую 64-разрядный PE-файл (директиву "use64" в этом случае указывать уже не нужно), а в каталоге EXAMPLES можно найти готовый пример PE64DEMO в котором показано как ее использовать на практике.
Ниже приведен пример x86-64 программы "hello, world" с комментариями:
; пример 64-битного PE файла
; для его выполнения необходимо иметь Windows XP 64-bit edition
; указываем формат
format PE64 GUI
; указываем точку входа
entry start
; создать кодовую секцию с атрибутами на чтение и исполнение
section '.code' code readable executable
start:
mov r9d,0; uType == MB_OK (кнопка по умолчанию)
; аргументы по соглашению x86-64
; передаются через регистры, не через стек!
; префикс d задает регистр размером в слово,
; можно использовать и mov r9,0, но тогда
; машинный код будет на байт длиннее
lea r8,[_caption]; lpCaption передаем смещение
; команда lea занимает всего 7 байт,
; а mov reg, offset - целых 11, так что
; lea намного более предпочтительна
lea rdx,[_message]; lpText передаем смещение выводимой строки
mov rcx,0; hWnd передам дескриптор окна-владельца
; (можно так же использовать xor rcx,rcx
; что на три байта короче)
call [MessageBox]; вызываем функцию MessageBox
mov ecx,eax; заносим в ecx результат возврата
; (Функция ExitProcess ожидает 32-битный параметр
; можно использовать и mov rcx,rax, но это будет
; на байт длиннее)
call [ExitProcess]; вызываем функцию ExitProcess
; создать секцию данных с атрибутами на чтение и запись
; (вообще-то в данном случае атрибут на запись необязателен,
; поскольку мы ничего не пишем, а только читаем)
section '.data' data readable writeable
_caption db 'PENUMBRA is awesome!',0; ASCIIZ-строка заголовка окна
_message db 'Hello World!',0; ASCIIZ-строка выводимая на экран
; создать секцию импорта с атрибутами на чтение и запись
; (здесь атрибут на запись обязателен, поскольку при загрузке PE-Файла
; в секцию импорта; будут записываться фактические адреса API-функций)
section '.idata' import data readable writeable
dd 0,0,0,RVA kernel_name,RVA kernel_table
dd 0,0,0,RVA user_name,RVA user_table
dd 0,0,0,0,0; завершаем список двумя 64-разряными нулеми!!!
kernel_table:
ExitProcess dq RVA _ExitProcess
dq 0; завершаем список 64-разряным нулем!!!
user_table:
MessageBox dq RVA _MessageBoxA
dq 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
db 'ExitProcess',0
_MessageBoxA dw 0
db 'MessageBoxA',0
Листинг 11 64-битное приложение "hello, world" под Windows на FASM'е
Рисунок 8 64-битный файл — первый полет
Ассемблируем файл (fasm PE64DEMO.ASM) и запустим образовавшийся EXE на выполнение. Под 32-разрядной Windows он, естественно, не запустится и она скажет "мяу":
Рисунок 9 реакция 32-битной Windows на попытку запуска 64-битного PE-файла
Вдоволь наигравшись нашем первым x86-64 файлом, загрузим его в дизассемблер (например, в IDA Pro 4.7. Она хоть и материться, предлагая использовать специальную 64-битную версию, но при нажатии на "yes" все конкретно дизассемблирует, во всяком случае до тех пор пока не столкнется с подлинным 64-битным адресом или операндом, с которым произойдет обрезание, в частности mov r9,1234567890h дизассемблируется как mov r9, 34567890h, так что переход на 64-битную версию IDA все же очень желателен, тем более, что начиная с IDA 4.9 она входит в базовую поставку). Посмотрим, что у него внутри?
А внутри у него вот что:
.code:0000000000401000 41 B9 00 00 00 00 mov r9d, 0
.code:0000000000401006 4C 8D 05 F3 0F 00 00 lea r8, aPENUMBRA
.code:000000000040100D 48 8D 15 03 10 00 00 lea rdx, aHelloWorld; "Hello World!"
.code:0000000000401014 48 C7 C1 00 00 00 00 mov rcx, 0
.code:000000000040101B FF 15 2B 20 00 00 call cs:MessageBoxA
.code:0000000000401021 89 C1 mov ecx, eax
.code:0000000000401023 FF 15 13 20 00 00 call cs:ExitProcess
Листинг 12 дизассемблерный листинг 64-битного приложения "hello, world!"
Что ж… довольно громоздко, объемно и концептуально. Для сравнения, дизассемблированный листинг аналогичного 32-разрядного файла приведен ниже. Старый x86 код в 1,6 раз короче! А ведь это только демонстрационная программа из нескольких строк! На полновесных приложениях разрыв будет только нарастать! Так что не стоит злоупотреблять 64-разрядным кодом без необходимости. Его следует использовать только там, где 64-битная арифметика и 8 дополнительных регистров действительно дают ощутимый выигрыш. Например, в математических задачах или программах для вскрытия паролей.
Рисунок 10 дизассемблирование 64-битного PE-файла 32-битной версий IDA Pro
code:00401000 6A 00 push 0
code:00401002 68 00 20 40 00 push offset aPENUMBRA
code:00401007 68 17 20 40 00 push offset aHelloWorld
code:0040100C 6A 00 push 0
code:0040100E FF 15 44 30 40 00 call ds:MessageBoxA
code:00401014 6A 00 push 0
code:00401016 FF 15 3C 30 40 00 call ds:ExitProcess
Листинг 13 дизассемблерный листинг 32-битного приложения "hello, world!"
В качестве заключительно упражнения перепишем наше приложение в стиле MASM, поклонников которого нужно не бить, а уважать (как ни крути, а все-таки патриарх). Никаких радикальных отличий не наблюдается:
; объявляем внешние API-функции, которые мы будем вызывать
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
; секция данных с атрибутами по умолчанию (чтение и запись)
.data
mytit db 'PENUMBRA is awesome!', 0
mymsg db 'Hello World!', 0
; секция кода с атрибутами по умолчанию (чтение и исполнение)
.code
Main:
mov r9d, 0; uType = MB_OK
lea r8, mytit; LPCSTR lpCaption
lea rdx, mymsg; LPCSTR lpText
mov rcx, 0; hWnd = HWND_DESKTOP
call MessageBoxA
mov ecx, eax; uExitCode = MessageBox(...)
call ExitProcess
End Main
Листинг 14 64-битное приложение "hello, world" под Windows на MASM'е
Ассемблирование и линковка проходит так: "ml64 XXX.asm /link /subsystem:windows /defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main" в результате чего образуется готовый к употреблению exe-файл с румяной поджаренной корочкой нашего ЦП (FASM ассемблирует намного быстрее).
Примеры более сложных программ легко найти в сети. Как показывает практика, запросы типа "x86-64 [AMD64] assembler example" катастрофически неэффективны и гораздо лучше использовать "mov rax" (без кавычек) или вроде того.
заключение
Вот мы и познакомились с архитектурой x86-64! Здесь действительно есть место где развернутся и чему поучиться! Насколько эти знания окажутся востребованы на практике — так сразу и не скажешь. У AMD есть хорошие шансы пошатнуть рынок, но ведь и Intel не дремлет, активно продвигая собственные 64-разрядные платформы, известные под общем именем IA64, но о них как ни будь в другой раз…
Дата публикования: 2014-12-08; Прочитано: 1978 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!