Ас­сем­блер пре­дос­тавля­ет прак­тичес­ки неог­раничен­ную сво­боду для самовы­раже­ния и все­воз­можных извра­щений, что выгод­но отли­чает его от язы­ков высоко­го уров­ня. Вот мы и вос­поль­зуем­ся этой воз­можностью, извра­тив­шись не по‑дет­ски и сот­ворив со сте­ком то, о чем прип­люсну­тый Си толь­ко меч­тает.
 

Турбопередача стековых аргументов

Пе­реда­чу аргу­мен­тов через стек мож­но сущес­твен­но уско­рить, в слу­чае если аргу­мен­ты пред­став­ляют собой кон­стан­ту, извес­тную еще на ста­дии тран­сля­ции. Клас­сичес­кий спо­соб переда­чи выг­лядит так:

Клас­сичес­кий спо­соб переда­чи сте­ковых аргу­мен­тов
00000000: 6869060000 push 000000669
00000005: 6899090000 push 000000999
0000000A: 6896060000 push 000000696
0000000F: E852060000 call 000000666

До­воль­но рас­точитель­ное (в пла­не про­цес­сорных так­тов) решение, осо­бен­но если фун­кция вызыва­ется мно­гок­ратно. При этом опе­ран­ды коман­ды PUSH перего­няют­ся из сек­ции .text (находя­щей­ся в кодовой кэш‑памяти пер­вого уров­ня) в область сте­ка, находя­щуюся в кэш‑памяти дан­ных. Ну и зачем гонять их туда и обратно, ког­да аргу­мен­ты мож­но исполь­зовать непос­редс­твен­но по мес­ту хра­нения?

Усо­вер­шенс­тво­ван­ный при­мер выг­лядит так:

.code
MOV EBP, ESP
MOV ESP, offset func_arg + 4
CALL my_func
MOV ESP, EBP
.data
func_arg DD 00h, 696h, 999h, 669h

И хотя раз­мер кода пос­ле опти­миза­ции не толь­ко не сок­ратил­ся, но даже уве­личил­ся (14h байт до опти­миза­ции и 1Eh - пос­ле), мы сох­ранили нем­ного сте­ковой памяти и сок­ратили вре­мя выпол­нения. При­чем чем боль­ше аргу­мен­тов переда­ется фун­кции, тем в более выиг­рышном положе­нии ока­зыва­ется опти­мизи­рован­ный вари­ант, пос­коль­ку неоп­тимизи­рован­ный вынуж­ден тра­тить на каж­дый аргу­мент один допол­нитель­ный байт!

00000000: 8BEC mov ebp, esp
00000002: BC66000000 mov esp, 000000013
00000007: E80E000000 call 000000666
0000000C: 8BE5 mov esp, ebp
0000000E: 00 00 00 00 96 06 00 00 ? 99 09 00 00 69 06 00 00
0000001E:

Нес­коль­ко замеча­ний по поводу. Пер­вое. Опе­раци­онные сис­темы семей­ства Windows NT (к которым при­над­лежат Windows 2000, Windows XP, Windows Vista, Windows Server 2003 и Windows Server Longhorn) гаран­тиру­ют целос­тность содер­жимого сте­ка выше его вер­шины (для адре­сов мень­ших, чем ESP), поэто­му перено­сят такие извра­щения безо вся­кого ущер­ба для работос­пособ­ности прог­раммы. Опе­раци­онные сис­темы семей­ства Windows 9x ведут себя ина­че, бес­церемон­но исполь­зуя все, что находит­ся выше ESP в целях «про­изводс­твен­ной необ­ходимос­ти», что ведет к иска­жению сек­ции дан­ных и пос­леду­юще­му кра­ху прог­раммы. Поэто­му все, что было ска­зано здесь, рас­простра­няет­ся толь­ко на NT.

Передача стековых аргументов напрямую, без их фактической засылки в стек
Пе­реда­ча сте­ковых аргу­мен­тов нап­рямую, без их фак­тичес­кой засыл­ки в стек

За­меча­ние номер два. Перед аргу­мен­тами необ­ходимо оста­вить двой­ное сло­во (а в 64-бит­ном режиме — чет­вер­тное) для сох­ранения адре­са воз­вра­та. При этом сек­ция дан­ных, где находит­ся это сло­во, дол­жна быть дос­тупна на запись. Если же фун­кция вызыва­ется из одно­го единс­твен­ного мес­та и адрес воз­вра­та известен заранее, ничего не меша­ет положить его рядом с аргу­мен­тами. Но тог­да фун­кцию при­дет­ся пус­кать коман­дой jump, а не call, что еще боль­ше уве­личи­вает про­изво­дитель­ность:

Вы­зов фун­кции с пре­доп­ределен­ным адре­сом воз­вра­та коман­дой JMP
.code
MOV EBP, ESP
MOV ESP, offset func_arg + 4
JMP my_func
here:
MOV ESP, EBP
.data
func_arg DD offset here, 696h, 999h, 669h

Кста­ти говоря, ни адрес воз­вра­та, ни аргу­мен­ты фун­кции вов­се не обя­заны быть кон­стан­тами, извес­тны­ми на ста­дии ком­пиляции, и они могут сво­бод­но модифи­циро­вать­ся в любой момент коман­дами MOV и STOS. Так­же если аргу­мен­ты хра­нят­ся в локаль­ных перемен­ных, то засылать их в стек не обя­затель­но! Дос­таточ­но лишь скор­ректи­ровать регистр ESP таким обра­зом, что­бы перемен­ные‑аргу­мен­ты ока­зались на вер­шине. Естес­твен­но, порядок раз­мещения аргу­мен­тов в памяти дол­жен сов­падать с поряд­ком переда­чи аргу­мен­тов, но на ассем­бле­ре, в отли­чие от язы­ков высоко­го уров­ня, мы можем самос­тоятель­но выбирать нуж­ную схе­му раз­мещения перемен­ных, так что это не проб­лема.

Еще одна тон­кость: «опти­мизи­рован­ный» вари­ант обла­дает все­ми фор­маль­ными атри­бута­ми «переда­чи по зна­чению», но де‑фак­то аргу­мен­ты переда­ются по ссыл­ке. То есть сов­сем наобо­рот! Аргу­мен­ты переда­ются по зна­чению, но это зна­чение пос­ле выхода из фун­кции сох­раня­ет свое сос­тояние, ведет себя так, как буд­то бы оно было переда­но по ссыл­ке. Иног­да это эко­номит так­ты про­цес­сора и сок­раща­ет пот­ребнос­ти в памяти, но иног­да ведет к труд­ноуло­вимым ошиб­кам, лиш­ний раз под­тверждая тезис, что нет в мире совер­шенс­тва.

И пос­леднее: при всех этих играх со сте­ком сле­дует пом­нить, что целый ряд API-фун­кций тре­бует, что­бы ука­затель сте­ка был выров­нен на гра­ницу четырех бай­тов. Наруше­ние это­го пра­вила ведет к неп­ред­ска­зуемым пос­ледс­тви­ям.

 

Повторное использование кадра стека

При вхо­де внутрь фун­кции боль­шое количес­тво локаль­ных перемен­ных ини­циали­зиру­ется кон­стан­тами или зна­чени­ями, инва­риан­тны­ми по отно­шению к самой фун­кции (то есть дру­гими перемен­ными, чаще все­го гло­баль­ными). При­чем ини­циали­зация обыч­но осу­щест­вля­ется коман­дой MOV, а для обслу­жива­ния стро­ковых перемен­ных при­ходит­ся при­бегать к REP MOVSB. Все это мед­ленно, гро­моз­дко и неп­роиз­водитель­но.

А почему бы не под­готовить кадр сте­ка еще на ста­дии тран­сля­ции?! В гру­бом приб­лижении это будет выг­лядеть так:

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

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

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

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

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


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