32-бит­ная эпо­ха ухо­дит в прош­лое, сда­ваясь под натис­ком новых идей и плат­форм. Оба флаг­мана рын­ка (Intel и AMD) пред­ста­вили 64-бит­ные архи­тек­туры, откры­вающие дверь в мир боль­ших ско­рос­тей и про­изво­дитель­ных ЦП. Это нас­тоящий про­рыв — новые регис­тры, новые режимы работы… поп­робу­ем с ними разоб­рать­ся? Мы рас­смот­рим архи­тек­туру AMD64 (она же x86-64) и покажем, как с ней бороть­ся.

64-бит­ный лей­бл — зву­чит воз­бужда­юще, но в прак­тичес­ком пла­не это все­го лишь хит­рый мар­кетин­говый трюк, скры­вающий не толь­ко дос­тоинс­тва, но и недос­татки. Нам дарова­ны 64-бит­ные опе­ран­ды и 64-бит­ная адре­сация. Казалось бы, лиш­ние раз­ряды кар­ман не тянут и если не при­годят­ся, то по край­ней мере не помеша­ют. Так ведь нет! С рос­том раз­ряднос­ти уве­личи­вает­ся и дли­на машин­ных команд, а зна­чит, вре­мя их заг­рузки/декоди­рова­ния и раз­меры прог­раммы, поэто­му для дос­тижения не худ­шей про­изво­дитель­нос­ти 64-бит­ный про­цес­сор дол­жен иметь более быс­трую память и более емкий кеш. Это раз.

64-бит­ные целочис­ленные опе­ран­ды ста­новят­ся юза­бель­ны толь­ко при обра­бот­ке чисел поряд­ка 2^33 + (8 589 934 592) и выше. Там, где 32-бит­ному про­цес­сору тре­бует­ся нес­коль­ко так­тов, 64-бит­ный справ­ляет­ся за один. Но где ты видел такие чис­ла в домаш­них и офис­ных при­ложе­ниях? Не зря же инже­неры из Intel пош­ли на сок­ращение раз­ряднос­ти АЛУ (ариф­метико‑логичес­кого устрой­ства), ширина которо­го в Pentium 4 сос­тавля­ет все­го 16 бит, про­тив 32 бит в Pentium III. Это не зна­чит, что Pentium 4 не может обра­баты­вать 32-раз­рядные чис­ла. Может. Толь­ко он тра­тит на них боль­ше вре­мени, чем Pentium III. Но, пос­коль­ку про­цент под­линно 32-раз­рядных чисел (то есть таких, что исполь­зуют свы­ше 16 бит) в домаш­них при­ложе­ниях отно­ситель­но невысок, про­изво­дитель­ность пада­ет нез­начитель­но. Зато ядро содер­жит мень­ше тран­зисто­ров, выделя­ет мень­ше теп­ла и луч­ше работа­ет на повышен­ной так­товой час­тоте — в целом эффект положи­тель­ный.

64-бит­ная раз­рядность… Помилуй! Адре­совать 18 446 744 073 709 551 616 байт памяти не нуж­но даже Microsoft’у со все­ми его гра­фичес­кими заворо­тами! Из 4 Гбайт адресно­го прос­транс­тва Windows Processional и Windows Server толь­ко 2 Гбайт выделя­ют при­ложе­ниям.

3 Гбайт выделя­ет лишь Windows Advanced Server, и не потому, что боль­ше выделить невоз­можно! x86-про­цес­соры с лег­костью адре­суют вплоть до 16 Гбайт (по 4 Гбайт на код, дан­ные, стек и кучу), опять‑таки обхо­дясь минималь­ной перес­трой­кой опе­раци­онной сис­темы! Почему же до сих пор это не было сде­лано? Почему мы сидим на жал­ких 4 Гбайт, из которых реаль­но дос­тупны толь­ко два?! Да потому, что боль­ше никому не нуж­но! Сис­тему, адре­сующую 16 Гбайт, прос­то так не про­дашь, кого эти гигабай­ты инте­ресу­ют? Вот 64 бита — сов­сем дру­гое дело! Это осве­жает! Вот все вок­руг них и тан­цуют.

Срав­нивать 32- и 64-бит­ные про­цес­соры бес­смыс­ленно. Если 64-бит­ный про­цес­сор на домаш­нем при­ложе­нии ока­зыва­ется быс­трее, то отнюдь не за счет сво­ей 64-бит­ности, а бла­года­ря совер­шенно незави­симым от нее конс­трук­тивным ухищ­рени­ям, на которых инже­неры едва не разор­вали себе зад­ницы!

Впро­чем, не будем о грус­тном. 64 бита все рав­но вой­дут в нашу жизнь. Для некото­рых задач они очень даже ничего. Вот, нап­ример, крип­тогра­фия. 64 бита — это же 8 байт! 8-сим­воль­ные пароли мож­но пол­ностью умес­тить в один регистр, не обра­щаясь к памяти, что дает неверо­ятный резуль­тат! Ско­рость перебо­ра уве­личи­вает­ся чуть ли не на порядок! Ну так чего же мы ждем? Впе­ред! На штурм 64-бит­ных вер­шин!

AMD Athlon 64 во всей своей красе
AMD Athlon 64 во всей сво­ей кра­се
 

Что нам понадобится?

Нам пот­ребу­ется 64-раз­рядная опе­раци­онная сис­тема. Дотянуть­ся до 64-бит­ных регис­тров и про­чих вкус­ностей x86-64-архи­тек­туры мож­но толь­ко из спе­циаль­ного 64-раз­рядно­го режима (long mode). Ни под реаль­ным, ни под 32-раз­рядным защищен­ным x86-режимом они не дос­тупы. И хотя мы покажем, как перевес­ти про­цес­сор из реаль­ного в 64-раз­рядный режим, соз­дание пол­нофун­кци­ональ­ной опе­раци­онной сис­темы не вхо­дит в наши пла­ны, а без нее никуда!

Те­перь перей­дем к под­готов­ке инс­тру­мен­тария. Как минимум нам понадо­бит­ся ассем­блер и отладчик. Мы будем исполь­зовать FASM. Он бес­пла­тен, работа­ет под Linux, Windows и MS-DOS, под­держи­вает x86-64 и обла­дает удоб­ным син­такси­сом.

Прак­тичес­ки во все x86-64-пор­ты Linux вхо­дит GNU Debugger, которо­го для наших задач впол­не дос­таточ­но. Обла­дате­ли Windows могут вос­поль­зовать­ся Microsoft Debugger.

 

Обзор x86-64

За под­робным опи­сани­ем x86-64-архи­тек­туры луч­ше все­го обра­тить­ся к фир­менной докумен­тации AMD64 Technology — AMD64 Architecture Programmer’s Manual Volume 1:Application Programming. Мы же огра­ничим­ся толь­ко бег­лым обзо­ром основных новов­ведений.

На­конец‑то AMD сжа­лилась над нами и подари­ла прог­раммис­там то, чего все так дол­го жда­ли. К семи регис­трам обще­го наз­начения (вось­ми — с уче­том ESP) добави­лось еще восемь, в резуль­тате чего их общее количес­тво дос­тигло 15 (16) штук.

Ста­рые регис­тры, рас­ширен­ные до 64 бит, получи­ли име­на RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, RIP и RFLAGS. Новые регис­тры оста­лись безымян­ными и прос­то про­нуме­рова­ны от R8 до R15. Для обра­щения к млад­шим 8, 16 и 32 битам новых регис­тров мож­но исполь­зовать суф­фиксы b, w и d. Нап­ример, R9 — это 64-раз­рядный регистр, R9b — его млад­ший байт (по ана­логии с AL), а R9w — млад­шее сло­во (то же самое, что AX в EAX). Пря­мых нас­ледни­ков AH, к сожале­нию, не наб­люда­ется, и для манипу­ляции со сред­ней частью регис­тров при­ходит­ся извра­щать­ся со сдви­гами и матема­тичес­кими опе­раци­ями.

Регистры, доступные в режиме x86-64
Ре­гис­тры, дос­тупные в режиме x86-64

Ре­гистр, ука­затель команд RIP, теперь адре­сует­ся точ­но так же, как и все осталь­ные регис­тры обще­го наз­начения. Прог­раммис­ты, зас­тавшие живую PDP-11 (или ее оте­чес­твен­ный клон — «Элек­тро­нику БК», или УКНЦ), толь­ко през­ритель­но хмык­нут: наконец‑то до раз­работ­чиков ста­ли доходить оче­вид­ные исти­ны, которые на всех нор­маль­ных плат­формах были реали­зова­ны еще неиз­вес­тно ког­да.

Возь­мем прос­тей­ший при­мер: заг­рузим в регистр AL опкод сле­дующей машин­ной коман­ды. На x86 при­ходит­ся пос­тупать так.

Заг­рузка опко­да сле­дующей машин­ной коман­ды в клас­сичес­ком x86
call $ + 5 ; Запихнуть в стек адрес следующей команды и передать на нее управление
pop ebx ; Вытолкнуть из стека адрес возврата
add ebx, 6 ; Скорректировать адрес на размер команд pop/add/mov
mov al, [ebx] ; Теперь AL содержит опкод команды NOP
NOP ; Команда, чей опкод мы хотим загрузить в AL

Это же умом поехать мож­но, пока все это напишешь! И еще здесь очень лег­ко оши­бить­ся в раз­мере команд — при­ходит­ся вычис­лять его вруч­ную либо заг­ромож­дать лис­тинг никому не нуж­ными мет­ками. К тому же неиз­бежно зат­рагива­ется стек, что в ряде слу­чаев нежела­тель­но или недопус­тимо (осо­бен­но в защит­ных механиз­мах, наш­пигован­ных анти­отла­доч­ными при­ема­ми).

А теперь перепи­шем тот же самый при­мер на x86-64.

Заг­рузка опко­да сле­дующей машин­ной коман­ды на x86-64
mov al,[rip] ; Загружаем опкод следующей машинной команды
NOP ; Команда, чей опкод мы хотим загрузить в AL

Кра­сота! Толь­ко сле­дует пом­нить, что RIP всег­да ука­зыва­ет на сле­дующую, а отнюдь не текущую инс­трук­цию! К сожале­нию, ни Jx RIP, ни CALL RIP не работа­ют. Таких команд в лек­сиконе x86-64 прос­то нет.

Но это еще что! Исчезла абсо­лют­ная адре­сация! Если нам надо изме­нить содер­жимое ячей­ки памяти по кон­крет­ному адре­су, на x86 мы пос­тупа­ем приб­лизитель­но так:

dec byte ptr [666h] ; Уменьшить содержимое байта по адресу 666h на единицу

Под x86-64 тран­сля­тор выда­ет ошиб­ку ассем­бли­рова­ния, вынуж­дая нас при­бегать к фик­тивно­му базиро­ванию:

xor r9, r9 ; Обнулить регистр r9
dec byte ptr [r9+666h] ; Уменьшить содержимое байта по адресу 0+666h на единицу

Есть и дру­гие отли­чия от x86, но они не столь прин­ципи­аль­ны. Важ­но то, что в режиме сов­мести­мос­ти с x86 (Legacy Mode) ни 64-бит­ные регис­тры, ни новые методы адре­сации не дос­тупны! Никаки­ми средс­тва­ми (вклю­чая чер­ную и белую магию) дотянуть­ся до них нель­зя, и, преж­де чем что‑то сде­лать, необ­ходимо перевес­ти про­цес­сор в длин­ный режим (long mode), который делит­ся на два под­режима: режим сов­мести­мос­ти с x86 (compatibility mode) и 64-бит­ный режим (64-bit mode). Режим сов­мести­мос­ти пре­дус­мотрен толь­ко для того, что­бы 64-раз­рядная опе­раци­онная сис­тема мог­ла выпол­нять ста­рые 32-бит­ные при­ложе­ния. Никакие 64-бит­ные регис­тры здесь и не ночева­ли!

Ре­аль­ная 64-бит­ность оби­тает толь­ко в 64-bit long mode, о котором мы и будем говорить.

 

Hello, world на x86-64

Прог­рамми­рова­ние под 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. Так­же советую заг­лянуть на стра­нич­ку бес­плат­ного ком­пилято­ра Free PASCAL и под­нять докумен­тацию по спо­собам вызова API.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


Подписаться
Уведомить о
1 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии