Понимание языка ассемблера (Reverse Engineering для начинающих)

Citation preview

Понимание языка ассемблера (Reverse Engineering для начинающих)

Денис Юричев

i

Понимание языка ассемблера (Reverse Engineering для начинающих)

Почему два названия? Читайте здесь: (стр. xiii).

Денис Юричев

Ѳ®@ ©2013-2019, Денис Юричев. Это произведение доступно по лицензии Creative Commons «Attribution-ShareAlike 4.0 International» (CC BY-SA 4.0). Чтобы увидеть копию этой лицензии, посетите h t t p s : / / c r e a t iv e c o m m o n s . o r g / li c e n s e s / b y - s a / 4 . 0 / . Версия этого текста (3 1 м а я 2 0 1 9 г.). Самая новая версия текста (а т а кж е англоязычная версия) доступна на сайте beginners.re. Обложка нарисована Андреем Нечаевским: facebook.

Нужны переводчики! Возможно, вы захотите мне помочь с переводом этой работы на другие языки, кроме английского и русского. Просто пришлите мне любой фрагмент переведенного текста (не важно, насколько короткий), и я добавлю его в исходный код на LaTeX. Читайте здесь. Уже есть кое-что на немецком, ф ранцузском, и чуть-чуть на итальянском, португальском и поль­ ско м . Скорость не важна, потому что это опен-сорсный проект все-таки. Ваше имя будет указано в чис­ ле участников проекта. Корейский, китайский и персидский языки зарезервированы издателями. Английскую и русскую версии я делаю сам, но английский у меня все еще ужасный, т а к что я буду очень признателен за коррективы, итд. Даже мой русский несовершенный, т а к что я благодарен за коррективы и русского текста! Не стесняйтесь писать мне: dennis@ yurichev.com .

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

Краткое оглавление 1 О бразцы ко д а

1

2 В а ж н ы е ф ундам ентальны е вещ и

447

3 Более сл о ж н ы е прим еры

470

4 Java

652

5 П о и с к в к о д е т о г о ч то н у ж н о

691

6 С п е ц и ф и ч н о е д л я ОС

726

7 И нструменты

781

8 П р и м е р ы из п р а к т и к и

7 84

9 П р и м е р ы р а з б о р а з а к р ы т ы х ( p r o p r ie t a r y ) ф о р м а т о в ф а й л о в

896

10 П р о ч е е

959

11 Ч то с т о и т п о ч и т а т ь

973

12 С о о б щ е с т в а

976

Послесловие

978

Приложение

980

Список принятых сокращений

1009

Гл о сса р и й

101 4

Предметны й ука за те л ь

1016

iii

Оглавление 1 О бразцы ко д а 1 1.1 М е т о д ........................................................................................................................................................... 1 1.2 Некоторые базовые п о н я т и я .............................................................................................................. 2 1.2.1 Краткое введение в C P U ............................................................................................................ 2 1.2.2 Представление ч и с е л ................................................................................................................. 3 1.3 Пустая ф у н к ц и я ....................................................................................................................................... 5 1.3.1x86 .................................................................................................................................................. 6 1.3.2 A R M .................................................................................................................................................. 6 1.3.3 MIPS.................................................................................................................................................. 6 1.3.4 Пустые ф ункции на п р а к т и к е ................................................................................................. 7 1.4 Возврат з н а ч е н и я .................................................................................................................................... 7 1.4.1 x86 .................................................................................................................................................. 7 1.4.2 ARM .................................................................................................................................................. 8 1.4.3 MIPS .................................................................................................................................................. 8 1.4.4 На практике .................................................................................................................................. 8 1.5 Hello, w o r l d ! ............................................................................................................................................... 9 1.5.1x86 .................................................................................................................................................. 9 1.5.2 x86-64 ............................................................................................................................................. 14 1.5.3 ARM .................................................................................................................................................. 18 1.5.4 MIPS .................................................................................................................................................. 24 1.5.5 Вывод ............................................................................................................................................. 28 1.5.6 У п р а ж н е н и я .................................................................................................................................. 29 1.6 Пролог и эпилог ф у н к ц и й .................................................................................................................... 29 1.6.1 Рекурсия ....................................................................................................................................... 29 1.7 С т е к ............................................................................................................................................................. 29 1.7.1 Почему стек растет в обратную с т о р о н у ? .......................................................................... 30 1.7.2 Для чего используется с т е к ? ................................................................................................... 30 1.7.3 Разметка типичного стека ...................................................................................................... 37 1.7.4 Мусор в с т е к е ............................................................................................................................... 37 1.7.5 Упражнения .................................................................................................................................. 41 1.8 printfO с несколькими а р г у м е н т а м и ................................................................................................. 41 1.8.1 x86 .................................................................................................................................................. 41 1.8.2 ARM .................................................................................................................................................. 52 1.8.3 MIPS .................................................................................................................................................. 58 1.8.4 Вывод ............................................................................................................................................. 64 1.8.5 Кстати ............................................................................................................................................. 65 1 .9 s c a n f()........................................................................................................................................................... 65 1.9.1 Простой пример ......................................................................................................................... 65 1.9.2 Классическая о ш и б к а ................................................................................................................. 74 1.9.3 Глобальные переменные ......................................................................................................... 75 1.9.4 Проверка результата s c a n f ( ) ................................................................................................... 84 1.9.5 Упражнение .................................................................................................................................. 96 1.10 Доступ к переданным аргументам ................................................................................................. 97 1.10.1 x86 ............................................................................................................................................... 97 1.10.2 x64 ............................................................................................................................................... 99 1.10.3 ARM ............................................................................................................................................... 102 1.10.4 MIPS ............................................................................................................................................... 105 1.11 Ещё о возвращаемых результатах .................................................................................................106 1.11.1 Попытка использовать результат ф ункции возвращающей v o id .............................. 106 1.11.2 Что если не использовать результат ф у н к ц и и ? .............................................................107 1.11.3 Возврат с т р у к т у р ы ....................................................................................................................108 1.12 У к а з а т е л и ............................................................................................................................................... 109 1.12.1 Возврат значений .................................................................................................................... 109 (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

iv

1.12.2 Обменять входные значения друг с д р у г о м ..................................................................... 119 1.13 Оператор G O T O .................................................................................................................................... 120 1.13.1 Мертвый к о д ............................................................................................................................... 123 1.13.2 Упражнение ............................................................................................................................... 124 1.14 Условные переходы ............................................................................................................................ 124 1.14.1 Простой пример ....................................................................................................................... 124 1.14.2 Вычисление абсолютной величины ................................................................................... 141 1.14.3 Тернарный условный о п е р а т о р ........................................................................................... 143 1.14.4 Поиск минимального и максимального з н а ч е н и я .......................................................... 146 1.14.5 В ы во д .............................................................................................................................................151 1.14.6 Упражнение ............................................................................................................................... 152 1.15 Взлом П О .................................................................................................................................................. 152 1.16 switch()/case/default ............................................................................................................................ 154 1.16.1 Если вариантов м а л о .............................................................................................................. 154 1.16.2 И если м н о г о ............................................................................................................................... 167 1.16.3 Когда много case в одном блоке ........................................................................................ 179 1.16.4 F a ll-th ro u g h ..................................................................................................................................183 1.16.5 Упражнения ............................................................................................................................... 185 1.17 Ц и к л ы ........................................................................................................................................................185 1.17.1 Простой п р и м е р ......................................................................................................................... 185 1.17.2 Функция копирования блоков п а м я т и ................................................................................ 196 1.17.3 Проверка условия .................................................................................................................... 199 1.17.4 В ы во д .............................................................................................................................................200 1.17.5 Упражнения ............................................................................................................................... 201 1.18 Еще кое-что о с т р о к а х ......................................................................................................................... 202 1.18.1 s t r l e n ( ) .......................................................................................................................................... 202 1.19 Замена одних арифметических инструкций на другие .......................................................... 213 1.19.1 У м н о ж е н и е ..................................................................................................................................213 1.19.2 Д е л е н и е ....................................................................................................................................... 218 1.19.3 Упражнение ............................................................................................................................... 219 1.20 Работа с FPU .......................................................................................................................................... 219 1.20.1 IEEE 754 ....................................................................................................................................... 219 1.20.2 x86 ............................................................................................................................................... 219 1.20.3 ARM, MIPS, x86/x64 S I M D .........................................................................................................220 1.20.4 С и / С и + + ....................................................................................................................................... 220 1.20.5 Простой пример ......................................................................................................................... 220 1.20.6 Передача чисел с плавающей запятой в аргументах .................................................. 230 1.20.7 Пример со сравнением ............................................................................................................ 232 1.20.8 Некоторые константы ............................................................................................................266 1.20.9 К о п и р о в а н и е ............................................................................................................................... 266 1.20.10 Стек, калькуляторы и обратная польская з а п и с ь ....................................................... 266 1.20.11 80 б и т ? ....................................................................................................................................... 266 1.20.12 x64 ............................................................................................................................................... 266 1.20.13 Упражнения ............................................................................................................................ 267 1.21 Массивы .................................................................................................................................................. 267 1.21.1 Простой пример ......................................................................................................................... 267 1.21.2 Переполнение буфера ............................................................................................................ 274 1.21.3 Защита от переполнения б у ф е р а ........................................................................................ 282 1.21.4 Еще немного о м а с с и в а х .........................................................................................................285 1.21.5 Массив указателей на строки ..............................................................................................286 1.21.6 Многомерные м а с с и в ы ............................................................................................................293 1.21.7 Набор строк ка к двухмерный массив ................................................................................ 301 1.21.8 В ы во д .............................................................................................................................................304 1.21.9 Упражнения ............................................................................................................................... 304 1.22 Пример: ошибка в Angband .............................................................................................................. 305 1.23 Работа с отдельными битами ............................................................................................................ 307 1.23.1 Проверка какого-либо бита ................................................................................................... 307 1.23.2 Установка и сброс отдельного бита ................................................................................... 311 1.23.3 С д в и г и .......................................................................................................................................... 319 1.23.4 Установка и сброс отдельного бита: пример с FPU1 .................................................... 319 1.23.5 Подсчет выставленных бит ................................................................................................... 323 1.23.6 В ы во д .............................................................................................................................................338 1.23.7 Упражнения ............................................................................................................................... 340 1Floating-Point Unit

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

v

1.24 Линейный конгруэнтный г е н е р а т о р ..............................................................................................340 1.24.1 x86 ............................................................................................................................................... 341 1.24.2 x64 ............................................................................................................................................... 342 1.24.3 32-bit A R M .................................................................................................................................... 343 1.24.4 M IP S ............................................................................................................................................... 343 1.24.5 Версия этого примера для многопоточной с р е д ы .......................................................... 346 1.25 С т р у к т у р ы ............................................................................................................................................... 346 1.25.1 MSVC: Пример SYSTEMTIME................................................................................................... 346 1.25.2 Выделяем место для структуры через m a llo c ()............................................................... 350 1.25.3 UNIX: struct t m ............................................................................................................................ 351 1.25.4 Упаковка полей в с т р у к т у р е .................................................................................................361 1.25.5 Вложенные с т р у к т у р ы ............................................................................................................368 1.25.6 Работа с битовыми полями в структуре .......................................................................... 371 1.25.7 Упражнения ............................................................................................................................... 378 1.26 Объединения (u n io n )............................................................................................................................ 378 1.26.1 Пример генератора случайных ч и с е л ................................................................................ 378 1.26.2 Вычисление машинного эпсилона ..................................................................................... 381 1.26.3 Замена инструкции FSCALE................................................................................................... 383 1.26.4 Быстрое вычисление квадратного к о р н я .......................................................................... 384 1.27 Указатели на ф ункции .......................................................................................................................385 1.27.1 M S V C .............................................................................................................................................386 1.27.2 G C C ............................................................................................................................................... 392 1.27.3 Опасность указателей на ф -ц и и ........................................................................................... 396 1.28 64-битные значения в 32-битной с р е д е ........................................................................................ 396 1.28.1 Возврат 64-битного значения ..............................................................................................396 1.28.2 Передача аргументов, сложение, в ы ч и т а н и е .................................................................. 397 1.28.3 Умножение, деление .............................................................................................................. 401 1.28.4 Сдвиг в п р а в о ............................................................................................................................... 404 1.28.5 Конвертирование 32-битного значения в 6 4 - б и т н о е .................................................... 405 1.29 S IM D ...........................................................................................................................................................406 1.29.1 В е к т о р и з а ц и я ............................................................................................................................ 407 1.29.2 Реализация s t r l e n ( ) при помощи SIMD .......................................................................... 417 1.30 64 бита ..................................................................................................................................................... 420 1.30.1 x86-64 .......................................................................................................................................... 420 1.30.2 ARM ............................................................................................................................................... 427 1.30.3 Числа с плавающей з а п я т о й .................................................................................................427 1.30.4 Критика 64-битной архитектуры ........................................................................................ 427 1.31 Работа с числами с плавающей запятой (SIMD) .......................................................................... 428 1.31.1 Простой пример ......................................................................................................................... 428 1.31.2 Передача чисел с плавающей запятой в аргументах .................................................. 435 1.31.3 Пример со сравнением ............................................................................................................ 436 1.31.4 Вычисление машинного эпсилона: x64 и SIMD ............................................................... 438 1.31.5 И снова пример генератора случайных чисел ............................................................... 439 1.31.6 И т о г ............................................................................................................................................... 440 1.32 Кое-что специфичное для ARM ......................................................................................................... 440 1.32.1 Знак номера (#) перед ч и с л о м ..............................................................................................440 1.32.2 Режимы адресации ................................................................................................................. 440 1.32.3 Загрузка констант в р е г и с т р .................................................................................................441 1.32.4 Релоки в ARM64 ......................................................................................................................... 443 1.33 Кое-что специфичное для MIPS ......................................................................................................... 444 1.33.1 Загрузка 32-битной константы в регистр ........................................................................ 444 1.33.2 Книги и прочие материалы о MIPS ..................................................................................... 446 2 В а ж н ы е ф ундам ентальны е вещ и 447 2.1 Целочисленные типы данных ............................................................................................................448 2.1.1 Бит .................................................................................................................................................. 448 2.1.2 Ниббл AKA nibble AKA n y b b l e ................................................................................................... 448 2.1.3 Байт ............................................................................................................................................... 449 2.1.4 Wide c h a r ....................................................................................................................................... 450 2.1.5 Знаковые целочисленные и беззнаковые .......................................................................... 450 2.1.6 Слово ( w o r d ) ..................................................................................................................................450 2.1.7 Регистр а д р е с а ............................................................................................................................ 451 2.1.8 Числа ............................................................................................................................................... 452 2.2 Представление знака в числах ............................................................................................................ 454

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

vi

2.2.1 Использование IMUL вместо M U L ........................................................................................... 455 2.2.2 Еще кое-что о дополнительном коде ................................................................................... 456 2.3 Целочисленное переполнение (integer o v e rflo w ).......................................................................... 457 2.4 A N D ............................................................................................................................................................. 458 2.4.1 Проверка того, находится ли значение на границе 2п .................................................... 458 2.4.2 Кирилличная кодировка K O I-8R ..............................................................................................458 2.5 И и ИЛИ ка к вычитание и с л о ж е н и е .................................................................................................459 2.5.1 Текстовые строки в ПЗУ2 ZX Spectrum ................................................................................ 459 2.6 XOR (исключающее И Л И ) .......................................................................................................................462 2.6.1 Логическая разница (logical d iffe r e n c e ) ................................................................................ 462 2.6.2 Бытовая речь ............................................................................................................................... 462 2.6.3 Ш и ф р о ва н и е ..................................................................................................................................462 2.6.4 RAID34 ............................................................................................................................................. 462 2.6.5 Алгоритм обмена значений при помощи исключающего ИЛИ ....................................463 2.6.6 Список связанный при помощи X O R ..................................................................................... 463 2.6.7 Трюк с переключением з н а ч е н и й ........................................................................................... 464 2.6.8 Хэширование Зобриста / табуляционное х э ш и р о в а н и е ..................................................464 2.6.9 Кстати ............................................................................................................................................. 465 2.6.10 AND/OR/XOR ка к M O V .............................................................................................................. 465 2.7 Подсчет бит ............................................................................................................................................. 465 2.8 Endianness (порядок б а й т ) ....................................................................................................................466 2.8.1 Big-endian (от старшего к м л а д ш е м у )...................................................................................466 2.8.2 Little-endian (от младшего к ста р ш е м у )................................................................................ 466 2.8.3 Пример .......................................................................................................................................... 466 2.8.4 Bi-endian (переключаемый порядок) ..................................................................................... 467 2.8.5 Конвертирование .......................................................................................................................467 2.9 П а м я т ь ........................................................................................................................................................467 2.10 CPU ............................................................................................................................................................. 467 2.10.1 Предсказатели пе р е хо д о в...................................................................................................... 467 2.10.2 Зависимости между д а н н ы м и ..............................................................................................468 2.11 Х е ш -ф у н кц и и .......................................................................................................................................... 468 2.11.1 Как работает односторонняя функция? .......................................................................... 468 3 Более сл о ж н ы е прим еры 470 3.1 Двойное отрицание ............................................................................................................................... 470 3.2 Использование const (const correctness) ........................................................................................ 471 3.2.1.Пересекающиеся const-строки ..............................................................................................472 3.3 Пример s t r s t r ( ) .......................................................................................................................................... 473 3.4 Конвертирование температуры ......................................................................................................... 473 3.4.1 Целочисленные значения ......................................................................................................... 474 3.4.2 Числа с плавающей запятой ................................................................................................... 475 3.5 Числа Ф и б о н а ч ч и .................................................................................................................................... 478 3.5.1 Пример # 1 .................................................................................................................................... 478 3.5.2 Пример # 2 .................................................................................................................................... 481 3.5.3 Итог .................................................................................................................................................. 484 3.6 Пример вычисления CRC32 ................................................................................................................. 485 3.7 Пример вычисления адреса с е т и ...................................................................................................... 488 3.7.1 calc_network_address() .............................................................................................................. 489 3.7.2 fo rm _ IP ().......................................................................................................................................... 489 3.7.3 print_as_IP().................................................................................................................................... 491 3.7.4 form_netmask() и set_bit() .........................................................................................................492 3.7.5 Итог .................................................................................................................................................. 493 3.8 Циклы: несколько и т е р а т о р о в ............................................................................................................493 3.8.1 Три итератора ............................................................................................................................... 494 3.8.2 Два итератора ............................................................................................................................ 494 3.8.3 Случай Intel C + + 2011 .............................................................................................................. 496 3.9 Duff's device .............................................................................................................................................497 3.9.1 Нужно ли использовать развернутые ц и к л ы ? .................................................................. 500 3.10 Деление используя умножение ...................................................................................................... 500 3.10.1 x86 ............................................................................................................................................... 500 3.10.2 Как это р а б о т а е т .......................................................................................................................502 3.10.3 A R M ............................................................................................................................................... 502 2Постоянное запоминающее устройство 3Redundant Array of Independent Disks

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

vii

3.10.4 M IP S ............................................................................................................................................... 503 3.10.5 Упражнение ............................................................................................................................... 504 3.11 Конверсия строки в число ( a t o i ( ) ) ................................................................................................... 504 3.11.1 Простой п р и м е р ......................................................................................................................... 504 3.11.2 Немного расширенный пример ........................................................................................... 507 3.11.3 Упражнение ............................................................................................................................... 510 3.12 Inline- ф у н к ц и и ....................................................................................................................................... 510 3.12.1 Ф ункции работы со строками и п а м я т ь ю .......................................................................... 511 3.13 C99 re s tric t............................................................................................................................................... 519 3.14 Функция abs() без п е р е х о д о в ............................................................................................................522 3.14.1 ОптимизирующийGCC 4.9.1 x64 ........................................................................................... 522 3.14.2 ОптимизирующийGCC 4.9 ARM64 ........................................................................................ 522 3.15 Ф ункции с переменным количеством аргументов ..................................................................... 523 3.15.1 Вычисление среднего арифметического .......................................................................... 523 3.15.2 Случай с функцией vprintf() ................................................................................................... 527 3.15.3 Случай с Pin ............................................................................................................................... 528 3.15.4 Эксплуатация строки формата ........................................................................................... 528 3.16 Обрезка строк ....................................................................................................................................... 529 3.16.1 x64: ОптимизирующийMSVC 2013 ........................................................................................ 530 3.16.2 x64: НеоптимизирующийGCC 4 . 9 . 1 ..................................................................................... 532 3.16.3 x64: ОптимизирующийGCC 4.9.1 ........................................................................................ 533 3.16.4 ARM64: НеоптимизирующийGCC (Linaro) 4.9 .................................................................. 534 3.16.5 ARM64: ОптимизирующийGCC (Linaro) 4 . 9 ........................................................................535 3.16.6 ARM: О п т и м и з и р у ю щ и й ^ ! 6/2013 (Режим A R M ) .............................................................536 3.16.7 ARM: О п т и м и з и р у ю щ и й ^ ! 6/2013 (Режим T h u m b ) ....................................................... 536 3.16.8 MIPS ............................................................................................................................................... 537 3.17 Функция to u p p e r()..................................................................................................................................538 3.17.1 x64 ............................................................................................................................................... 539 3.17.2 ARM ............................................................................................................................................... 540 3.17.3 Используя битовые операции .............................................................................................. 541 3.17.4 Итог ............................................................................................................................................... 542 3.18 Обфускация ............................................................................................................................................. 542 3.18.1 Текстовые строки ....................................................................................................................543 3.18.2 Исполняемый код .................................................................................................................... 543 3.18.3 Виртуальная машина / псевдо-код ..................................................................................... 545 3.18.4 Еще кое-что ............................................................................................................................... 545 3.18.5 Упражнение ............................................................................................................................... 545 3.19 С и+ + ........................................................................................................................................................545 3.19.1 Классы .......................................................................................................................................... 545 3.19.2 ostream ....................................................................................................................................... 562 3.19.3 References.................................................................................................................................... 563 3.19.4 STL.................................................................................................................................................. 564 3.19.5 П а м я т ь .......................................................................................................................................... 597 3.20 Отрицательные индексы массивов ................................................................................................. 597 3.20.1 Адресация строки с к о н ц а ...................................................................................................... 598 3.20.2 Адресация некоторого блока с к о н ц а ................................................................................ 598 3.20.3 Массивы начинающиеся с 1 ................................................................................................... 598 3.21 Больше об у к а з а т е л я х ......................................................................................................................... 600 3.21.1 Работа с адресами вместо указателей ............................................................................. 601 3.21.2 Передача значений ка к указателей; тэггированные о б ъ е д и н е н и я .........................603 3.21.3 Издевательство над указателями в ядре Windows ....................................................... 604 3.21.4 Нулевые указатели ................................................................................................................. 609 3.21.5 Массив ка к аргумент ф ункции .............................................................................................. 612 3.21.6 Указатель на ф ункцию ............................................................................................................ 613 3.21.7 Указатель ка к идентиф икатор объекта .......................................................................... 614 3.22 Оптимизации циклов ......................................................................................................................... 615 3.22.1 Странная оптимизация циклов ........................................................................................... 615 3.22.2 Еще одна оптимизация циклов ........................................................................................... 616 3.23 Еще о структурах .................................................................................................................................. 618 3.23.1 Иногда вместо массива можно использовать структуру в Си .................................... 618 3.23.2 Безразмерный массив в структуре Си ................................................................................ 619 3.23.3 Версия структуры в Си ............................................................................................................ 620 3.23.4 Файл с рекордами в игре «Block out» и примитивная с е р и а л и з а ц и я ...................... 622 3.24 memmove() и m e m c p y () .......................................................................................................................626 (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

v iii

3.24.1 Анти-отладочный п р и е м .........................................................................................................627 3.25 s e tjm p /lo n g jm p ....................................................................................................................................... 628 3.26 Другие нездоровые хаки связанные со с т е к о м .......................................................................... 630 3.26.1 Доступ к аргументам и локальным переменным вызывающей ф - ц и и ................... 630 3.26.2 Возврат строки ......................................................................................................................... 632 3.27 OpenMP ..................................................................................................................................................... 634 3.27.1 M S V C .............................................................................................................................................635 3.27.2 GCC ............................................................................................................................................... 637 3.28 Еще одна heisenbug- а ......................................................................................................................... 639 3.29 Windows 1 6 - b i t ....................................................................................................................................... 640 3.29.1 П р и м е р # 1 .................................................................................................................................... 640 3.29.2 Пример # 2 ..................................................................................................................................641 3.29.3 Пример # 3 ..................................................................................................................................641 3.29.4 Пример # 4 .................................................................................................................................. 642 3.29.5 Пример # 5 .................................................................................................................................. 645 3.29.6 Пример # 6 .................................................................................................................................. 648 4 Java 652 4.1 Java ............................................................................................................................................................. 652 4.1.1 Введение ....................................................................................................................................... 652 4.1.2 Возврат значения ....................................................................................................................... 652 4.1.3 Простая вычисляющая функция ........................................................................................... 657 4.1.4 Модель памяти в JVM4 .............................................................................................................. 659 4.1.5 Простой вызов ф ункций ............................................................................................................ 660 4.1.6 Вызов beep() .................................................................................................................................. 661 4.1.7 Линейный конгруэнтный ГПСЧ5 ..............................................................................................662 4.1.8 Условные переходы .................................................................................................................... 663 4.1.9 Передача аргументов ................................................................................................................. 665 4.1.10 Битовые поля ............................................................................................................................ 666 4.1.11 Циклы .......................................................................................................................................... 667 4.1.12 s w itc h ( ) .......................................................................................................................................... 669 4.1.13 Массивы ....................................................................................................................................... 670 4.1.14 С т р о к и .......................................................................................................................................... 678 4.1.15 Исключения ............................................................................................................................... 680 4.1.16 Классы .......................................................................................................................................... 683 4.1.17 Простейшая модификация ................................................................................................... 685 4.1.18 Итоги ............................................................................................................................................. 690 5 П о и с к в к о д е т о г о ч то н у ж н о 691 5.1 Идентификация исполняемых файлов ........................................................................................... 692 5.1.1 Microsoft Visual C + + ....................................................................................................................692 5.1.2 GCC .................................................................................................................................................. 692 5.1.3 Intel Fortran .................................................................................................................................... 692 5.1.4 Watcom, O penW atcom ................................................................................................................. 693 5.1.5 Borland ............................................................................................................................................. 693 5.1.6 Другие известные DLL .............................................................................................................. 694 5.2 Связь с внешним миром (на уровне функции) ............................................................................. 694 5.3 Связь с внешним миром ( w in 3 2 ) ......................................................................................................... 694 5.3.1 Часто используемые ф ункции Windows A P I ........................................................................695 5.3.2 Расширение триального п е р и о д а ........................................................................................... 695 5.3.3 Удаление nag-окна ....................................................................................................................695 5.3.4 tracer: Перехват всех ф ункций в отдельном м о д у л е ....................................................... 696 5.4 С т р о к и ........................................................................................................................................................696 5.4.1 Текстовые строки .......................................................................................................................696 5.4.2 Поиск строк в бинарном файле .............................................................................................. 701 5.4.3 Сообщения об ошибках и отладочные сообщения .......................................................... 702 5.4.4 Подозрительные магические строки ................................................................................... 702 5.5 Вызовы assert() ....................................................................................................................................... 703 5.6 Константы ............................................................................................................................................... 704 5.6.1 Магические числа ....................................................................................................................... 704 5.6.2 Специфические константы ...................................................................................................... 706 5.6.3 Поиск констант ............................................................................................................................ 706 4Java Virtual Machine 5Генератор псевдослучайных чисел

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

ix

5.7 Поиск нуж ны х инструкций ................................................................................................................. 706 5.8 Подозрительные паттерны кода ...................................................................................................... 708 5.8.1 Инструкции X O R ......................................................................................................................... 708 5.8.2 Вручную написанный код на ассемблере .......................................................................... 708 5.9 Использование magic numbers для трассировки .......................................................................... 709 5.10 Циклы ........................................................................................................................................................ 709 5.10.1 Некоторые паттерны в бинарных ф а й л а х ........................................................................710 5.10.2 Сравнение «снимков» п а м я т и ..............................................................................................717 5.11 Определение ISA6 ..................................................................................................................................719 5.11.1 Неверно дизассемблированный к о д ...................................................................................719 5.11.2 Корректино дизассемблированный код .......................................................................... 724 5.12 Прочее ..................................................................................................................................................... 724 5.12.1 Общая идея ............................................................................................................................... 724 5.12.2 Порядок ф ункций в бинарном коде ...................................................................................724 5.12.3 Крохотные ф у н к ц и и ................................................................................................................. 724 5.12.4 С и + + .............................................................................................................................................725 6 С п е ц и ф и ч н о е д л я ОС 726 6.1 Способы передачи аргументов при вызове ф у н к ц и й .................................................................. 726 6.1.1 cdecl ............................................................................................................................................... 726 6.1.2 stdcall .............................................................................................................................................726 6.1.3 f a s t c a l l .............................................................................................................................................727 6.1.4 t h i s c a l l .............................................................................................................................................729 6.1.5 x86-64 ............................................................................................................................................. 729 6.1.6 Возвращение переменных типа float, d o u b le ..................................................................... 732 6.1.7 Модификация аргументов ...................................................................................................... 732 6.1.8 Указатель на аргумент ф ункции ........................................................................................... 733 6.2 Thread Local S t o r a g e ............................................................................................................................... 734 6.2.1 Вернемся к линейному конгруэнтному генератору ....................................................... 735 6.3 Системные вызовы (syscall-ы) ............................................................................................................739 6.3.1 Linux ............................................................................................................................................... 740 6.3.2 W in d o w s .......................................................................................................................................... 740 6.4 Linux ........................................................................................................................................................... 740 6.4.1 Адресно-независимый код ...................................................................................................... 740 6.4.2 Трюк с LD_PRELOAD в L i n u x ...................................................................................................... 743 6.5 Windows NT ............................................................................................................................................... 745 6.5.1 CRT ( w in 3 2 ) .................................................................................................................................... 745 6.5.2 Win32 PE .......................................................................................................................................... 749 6.5.3 Windows SEH .................................................................................................................................. 757 6.5.4 Windows NT: Критические секции ........................................................................................ 779 7 И нструменты 781 7.1 Дизассемблеры ....................................................................................................................................... 781 7.1.1 IDA .................................................................................................................................................. 781 7.2 Отладчики ............................................................................................................................................... 781 7.2.1 O llyD b g .............................................................................................................................................781 7.2.2 G D B .................................................................................................................................................. 781 7.2.3 t r a c e r ............................................................................................................................................... 781 7.3 Трассировка системных вызовов ...................................................................................................... 782 7.4 Декомпиляторы ....................................................................................................................................... 782 7.5 Прочие инструменты ............................................................................................................................ 782 7.5.1 Калькуляторы ............................................................................................................................... 782 7.6 Чего-то здесь недостает? .................................................................................................................... 783 8 П р и м е р ы из п р а к т и к и 7 84 8.1 Ш утка с task manager (Windows Vista) .............................................................................................. 785 8.1.1 Использование LEA для загрузки з н а ч е н и й ........................................................................788 8.2 Ш утка с игрой Color Lines .................................................................................................................... 790 8.3 Сапёр (Windows XP) ............................................................................................................................... 793 8.3.1 Автоматический поиск массива .............................................................................................. 798 8.3.2 Упражнения .................................................................................................................................. 799 8.4 Хакаем часы в W in d o w s ......................................................................................................................... 799 8.5 Д о н г л ы ........................................................................................................................................................806 in s tru c tio n Set Architecture (Архитектура набора команд)

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

x

8.5.1 Пример #1: MacOS Classic и PowerPC ...................................................................................806 8.5.2 Пример #2: SCO O p e n S e r v e r ................................................................................................... 813 8.5.3 Пример #3: M S-D O S....................................................................................................................823 8.6 Случай с зашифрованной БД # 1 .........................................................................................................828 8.6.1 Base64 и э н т р о п и я .......................................................................................................................828 8.6.2 Данные с ж а т ы ? ............................................................................................................................ 830 8.6.3 Данные зашифрованы? ............................................................................................................831 8.6.4 C ryptoP P .......................................................................................................................................... 832 8.6.5 Режим обратной связи по ш и ф р о т е к с т у ............................................................................. 833 8.6.6 Инициализирующий вектор ................................................................................................... 835 8.6.7 Структура б у ф е р а .......................................................................................................................836 8.6.8 Шум в к о н ц е ..................................................................................................................................838 8.6.9 Вывод ............................................................................................................................................. 839 8.6.10 Post Scriptum: перебор всех IV7 ........................................................................................... 839 8.7 Разгон майнера биткоинов C o in te rra .................................................................................................839 8.8 SAP................................................................................................................................................................ 844 8.8.1 Касательно сжимания сетевого траффика в клиенте S A P ............................................ 844 8.8.2 Ф ункции проверки пароля в SAP 6 . 0 ..................................................................................... 855 8.9 Oracle RDBMS.............................................................................................................................................859 8.9.1 Таблица V$VERSION в Oracle RDBMS ..................................................................................... 859 8.9.2 Таблица X$KSMLRU в Oracle R D B M S ........................................................................................ 866 8.9.3 Таблица V$TIMER в Oracle R D B M S ........................................................................................... 868 8.10 Вручную написанный на ассемблере к о д ..................................................................................... 871 8.10.1 Тестовый файл E IC A R .............................................................................................................. 871 8.11 Демо ........................................................................................................................................................... 872 8.11.1 10 PRINT CHR$(205.5+RND(1)); : GOTO 1 0 .......................................................................... 873 8.11.2 Множество Мандельброта ...................................................................................................... 876 8.12 "П рикуп" в игре " М а р ь я ж " ................................................................................................................. 886 8.12.1 Упражнение ............................................................................................................................... 895 8.13 Другие примеры .................................................................................................................................... 895 9 П р и м е р ы р а з б о р а з а к р ы т ы х ( p r o p r ie t a r y ) ф о р м а т о в ф а й л о в 896 9.1 Примитивное XOR- ш и ф р о в а н и е .........................................................................................................896 9.1.1.Norton Guide: простейшее однобайтное XOR-ш и ф р ован ие............................................ 897 9.1.2 Простейшее четырехбайтное XOR-ш и ф р ован ие ............................................................... 900 9.1.3 Простое шифрование используя XOR-маску ..................................................................... 904 9.1.4 Простое шифрование используя XOR-маску, второй с л у ч а й ......................................... 911 9.1.5 Домашнее задание .................................................................................................................... 916 9.2 Информационная энтропия ................................................................................................................. 917 9.2.1 Анализирование энтропии в M a th e m a tic a .......................................................................... 917 9.2.2 Вывод ............................................................................................................................................. 926 9.2.3 Инструменты ............................................................................................................................... 926 9.2.4 Кое-что о примитивном шифровании ка к X O R .................................................................. 927 9.2.5 Еще об энтропии исполняемого кода ................................................................................... 927 9.2.6 Г П С Ч ............................................................................................................................................... 927 9.2.7 Еще примеры ............................................................................................................................... 928 9.2.8 Энтропия различных файлов ................................................................................................... 928 9.2.9 Понижение уровня энтропии ................................................................................................... 929 9.3 Файл сохранения состояния в игре Millenium ................................................................................ 929 9.4 Файл с индексами в программе fo rtu n e ........................................................................................... 936 9.4.1 Х а к и н г .............................................................................................................................................941 9.4.2 Ф а й л ы .............................................................................................................................................941 9.5 Oracle RDBMS: .SYM-файлы ................................................................................................................. 942 9.6 Oracle RDBMS: .MSB-файлы ................................................................................................................. 951 9.6.1 Вывод ............................................................................................................................................. 958 9.7 Упражнения ............................................................................................................................................. 958 9.8 Дальнейшее чтение ............................................................................................................................... 958 10 П р о ч е е 959 10.1 Модификация исполняемых файлов .............................................................................................. 959 10.1.1 Текстовые строки .................................................................................................................... 959 10.1.2 х 8 6 - к о д .......................................................................................................................................... 959 10.2 Статистика количества аргументов ф ункций ............................................................................. 960 in itia liz a tio n Vector

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

xi

10.3 Compiler in trin s ic .................................................................................................................................... 960 10.4 Аномалии компиляторов .................................................................................................................... 961 10.4.1 Oracle RDBMS 11.2 and Intel C + + 10.1 ................................................................................ 961 10.4.2 MSVC 6 . 0 ....................................................................................................................................... 961 10.4.3 Итог ............................................................................................................................................... 962 10.5 I t a n i u m ..................................................................................................................................................... 962 10.6 Модель памяти в 8086 ......................................................................................................................... 964 10.7 Перестановка basic block- о в .............................................................................................................. 965 10.7.1 Profile-guided optimization ...................................................................................................... 965 10.8 Мой опыт с Hex-Rays 2 . 2 . 0 ................................................................................................................. 966 10.8.1 Ошибки ....................................................................................................................................... 966 10.8.2 Странности .................................................................................................................................. 968 10.8.3 Безмолвие .................................................................................................................................. 969 10.8.4 Запятая ....................................................................................................................................... 971 10.8.5 Типы данных ............................................................................................................................... 972 10.8.6 Длинные и запутанные выражения ................................................................................... 972 10.8.7 Мой план .................................................................................................................................... 972 10.8.8 Итог ............................................................................................................................................... 972 11 Ч то с т о и т п о ч и т а т ь 973 11.1 Книги и прочие материалы .............................................................................................................. 973 11.1.1 Reverse Engineering ................................................................................................................. 973 11.1.2 Windows ....................................................................................................................................... 973 11.1.3 С и / С и + + ....................................................................................................................................... 973 11.1.4 x 8 6 /x 8 6 - 6 4 .................................................................................................................................. 974 11.1.5 A R M ............................................................................................................................................... 974 11.1.6 Язык а с с е м б л е р а .......................................................................................................................974 11.1.7 J a v a ............................................................................................................................................... 974 11.1.8 UNIX ............................................................................................................................................... 974 11.1.9 Программирование ................................................................................................................. 975 11.1.10 Криптография ......................................................................................................................... 975 12 С о о б щ е с т в а

976

Послесловие

978

12.1 Вопросы? .................................................................................................................................................. 978

Приложение

980

.1 x86 ................................................................................................................................................................ 980 .1.1 Терминология .................................................................................................................................. 980 .1.2 Регистры общего пользования ................................................................................................. 980 .1.3 FPU р е г и с т р ы ..................................................................................................................................984 .1.4 SIMD регистры ............................................................................................................................... 986 .1.5 Отладочные р е г и с т р ы ................................................................................................................. 986 .1.6 Инструкции .................................................................................................................................... 987 .1.7 npad .................................................................................................................................................. 999 .2 A R M ................................................................................................................................................................ 1001 .2.1 Т е р м и н о л о ги я ..................................................................................................................................1001 .2.2 Версии ............................................................................................................................................... 1001 .2.3 32-битный ARM ( A A rc h 3 2 )............................................................................................................1001 .2.4 64-битный ARM (AArch64) ............................................................................................................1002 .2.5 Инструкции .................................................................................................................................... 1003 .3 MIPS ................................................................................................................................................................ 1003 .3.1 Регистры .......................................................................................................................................... 1003 .3.2 Инструкции .................................................................................................................................... 1004 .4 Некоторые библиотечные ф ункции GCC ........................................................................................... 1005 .5 Некоторые библиотечные ф ункции M S V C ........................................................................................ 1005 .6 Cheatsheets .................................................................................................................................................. 1005 .6.1 IDA ..................................................................................................................................................... 1005 .6.2 OllyDbg .............................................................................................................................................1006 .6.3 M S V C .................................................................................................................................................. 1006 .6.4 GCC ..................................................................................................................................................... 1007 .6.5 GDB ..................................................................................................................................................... 1007 (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

xii

Список принятых сокращений

1009

Гл о сса р и й

101 4

Предметны й ука за те л ь

1016

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

x iii

Предисловие Почему два названия? В 2014-2018 книга называлась "Reverse Engineering для начинающих", но я всегда подозревал что это слишком суж ает аудиторию. Люди от инфобезопасности знают о "reverse engineering", но я от них редко слышу слово "ассем­ блер". Точно такж е, термин "reverse engineering" слишком незнакомый для общей аудитории программи­ стов, но они знают про "ассемблер". В июле 2018, для эксперимента, я заменил название на "Assembly Language for Beginners" и запо­ стил ссылку на сайт Hacker News8, и кн и гу приняли, в общем, хорошо. Так что, пусть т а к и будет, у книги будет два названия. Хотя, я поменял второе название на "Understanding Assembly Language" ("Понимание языка ассем­ блера"), потому что кто-то уж е написал кн и гу "Assembly Language for Beginners". Также, люди гово­ рят что "для начинающих" уж е звучит немного саркастично для книги объемом в ~1000 страниц. Книги отличаются только названием, именем файла (UAL-XX.pdf и RE4B-XX.pdf), URL-ом и парой первых страниц.

О reverse engineering У термина «reverse engineering» несколько популярных значений: 1) исследование скомпилирован­ ных программ; 2) сканирование трехмерной модели для последующего копирования; 3) восстанов­ ление структуры СУБД. Настоящая книга связана с первым значением.

Желательные знания перед началом чтения Очень желательно базовое знание ЯП9 Си. Рекомендуемые материалы: 11.1.3 (стр. 973).

Упражнения и задачи ...все перемещены на отдельный сайт: h t t p : / / c h a l l e n g e s . r e . 8h ttp s ://n e w s .y c o m b in a to r.c o m /ite m ? id = 1 7 5 4 9 0 5 0 9Язык Программирования

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

x iv

Об авторе Денис Юричев — опытный reverse engineer и программист. С ним можно контактировать по емейлу: d e n n is @ y u ric h e v .c o m .

Отзывы об этой книге • «Now that Dennis Yurichev has made this book free (libre), it is a contribution to the world of free knowledge and free education.» Richard M. Stallman, Основатель GNU, активист в области сво­ бодного ПО. • «It's very well done .. and for free .. amazing.»10 Daniel Bilar, Siege Technologies, LLC. • «... excellent and free»11 Pete Finnigan,гуру по безопасности

Oracle RDBMS.

• «... [the] book is interesting, great job!» Michael Sikorski, автор книги

Practical Malware Analysis:

The Hands-On Guide to Dissecting Malicious Software.

• «... my compliments for the very nice tutorial!» Herbert Bos, профессор университета Universiteit Amsterdam, соавтор Modern Operating Systems (4th Edition).

Vrije

• «... It is amazing and unbelievable.» Luis Rocha, CISSP/ ISSAP, Technical Manager, Network & Information Security at Verizon Business. • «Thanks for the great work and your book.» Joris van de Vis, специалист по Security .

SAP Netweaver &

• «... [a] reasonable intro to some of the techniques.»12 Mike Stay, преподаватель в Enforcement Training Center, Georgia, US.

Federal Law

• «I love this book! I have several students reading it at the moment, [and] plan to use it in graduate course.»13 Сергей Братусь , Research Assistant Professor в отделе Computer Science в Dartmouth College • «Dennis @Yurichev has published an impressive (and free!) book on reverse engineering»14 Tanel Poder, эксперт по настройке производительности Oracle RDBMS . • «This book is a kind of Wikipedia to beginners...» Archer, Chinese Translator, IT Security Researcher. 10twitter.com/daniel_bilar/status/436578617221742593 11twitter.com/petefinnigan/status/400551705797869568 12reddit 13twitter.com/sergeybratus/status/505590326560833536 14twitter.com/TanelPoder/status/524668104065159169

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

xv

• «Прочел Вашу кн и гу — отличная работа, рекомендую на своих курсах студентам в качестве учебного пособия». Николай Ильин, преподаватель в ФТИ НТУУ «КПИ» и DefCon-UA • «[A] first-class reference for people wanting to learn reverse engineering. And it's free for all.» Mikko Hypponen, F-Secure.

Благодарности Тем, кто много помогал мне отвечая на массу вопросов: Слава «Avid» Казаков, SkullC0DEr. Тем, кто присылал замечания об ошибках и неточностях: Станислав «Beaver» Бобрицкий, Алек­ сандр Лысенко, Александр «Solar Designer» Песляк, Федерико Рамондино, Марк Уилсон, Ксения Галинская, Разихова Мейрамгуль Кайратовна, Анатолий Прокофьев, Костя Бегунец, Валентин "netch" Нечаев, Александр Плахов, Артем Метла, Александр Ястребов, Влад Головкин15, Евгений Прошин, Александр Мясников, Zhu Ruijin, Changmin Heo, Vitor Vidal, Stijn Crevits, Jean-Gregoire Foulon16, Ben L., Etienne Khan, Norbert Szetei17, Marc Remy, Michael Hansen, Derk Barten, The Renaissance18, Hugo Chan, Emil Mursalimov, Tanner Hoke, Tan90909090@GitHub, Ole Petter Orhagen, Sourav Punoriyar.. Просто помогали разными способами: Андрей Зубинский, Arnaud Patard (rtp на #debian-arm IRC), noshadow на #gcc IRC, Александр Автаев, Mohsen Mostafa Jokar, Пётр Советов, Миша "tiphareth" Вербицкий. Переводчикам на китайский язык: Antiy Labs (antiy.cn), Archer. Переводчику на корейский язык: Byungho Min. Переводчику на голландский язык: Cedric Sambre (AKA Midas). Переводчикам на испанский язык: Diego Boy, Luis Alberto Espinosa Calvo, Fernando Guida, Diogo Mussi, Patricio Galdames. Переводчикам на португальский язык: Thales Stevan de A. Gois, Diogo Mussi, Luiz Filipe. Переводчикам на итальянский язык: Federico Ramondino19, Paolo Stivanin20, twyK, Fabrizio Bertone. Переводчикам на французский язык: Florent Besnard21, Marc Remy22, Baudouin Landais, Teo Dacquet23, BlueSkeye@GitHub24. Переводчикам на немецкий язык: Dennis Siekmeier25, Julius Angres26, Dirk Loser27, Clemens Tamme. Переводчикам на польский язык: Kateryna Rozanova, Aleksander Mistewicz, Wiktoria Lewicka. Переводчикам на японский язык: shmz@github28. Корректорам: Александр «Lstar» Черненький, Владимир Ботов, Андрей Бражук, Марк "Logxen" Ку­ пер, Yuan Jochen Kang, Mal Malakov, Lewis Porter, Jarle Thorsen, Hong Xie. Васил Колев29 сделал очень много исправлений и указал на многие ошибки. За иллюстрации и обложку: Андрей Нечаевский. И ещё всем тем на github.com кто присылал замечания и исправления30. Было использовано множество пакетов LTeX. Их авторов я т а кж е хотел бы поблагодарить. goto-vlad@github 16h tt p s / / g it h u b . c o m / p i x ju a n 17h tt p s //g ith u b .c o m /7 3 6 9 6 e 6 5 18h tt p s //g ith u b .c o m /T h e R e n a is s a n c e 19h tt p s / / g it h u b . c o m / p i n k r a b 20h tt p s / / g i t h u b . c o m / p a o l o s t i v a n i n 21h tt p s // g it h u b . c o m / b e s n a r d f 22h tt p s //g ith u b .c o m /m re m y 23h tt p s / / g it h u b . c o m / T 3 0 r i x 24h tt p s // g ith u b .c o m /B lu e S k e y e 25h tt p s // g ith u b .c o m /D S ie k m e ie r 26h tt p s // g ith u b .c o m /J A n g re s 27h tt p s //g ith u b .c o m /P o ly m a th M o n k e y 28h tt p s //g ith u b .c o m /s h m z 29h tt p s / / v a s i l . l u d o s t . n e t / 30h tt p s // g ith u b .c o m /D e n n is Y u ric h e v /R E - f o r - b e g i n n e r s / g r a p h s / c o n t r ib u t o r s

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

xvi

Ж ертвователи Тем, кто поддерживал меня во время написания этой книги: 2 * Oleg Vygovsky (50+100 UAH), Daniel Bilar ($50), James Truscott ($4.5), Luis Rocha ($63), Joris van de Vis ($127), Richard S Shultz ($20), Jang Minchang ($20), Shade Atlas (5 AUD), Yao Xiao ($10), Pawel Szczur (40 CHF), Justin Simms ($20), Shawn the R0ck ($27), Ki Chan Ahn ($50), Triop AB (100 SEK), Ange Albertini (€10+50), Sergey Lukianov (300 RUR), Ludvig Gislason (200 SEK), Gerard Labadie (€40), Sergey Volchkov (10 AUD), Vankayala Vigneswararao ($50), Philippe Teuwen ($4), Martin Haeberli ($10), Victor Cazacov (€5), Tobias Sturzenegger (10 CHF), Sonny Thai ($15), Bayna AlZaabi ($75), Redfive B.V. (€25), Joona Oskari Heikkila (€5), Marshall Bishop ($50), Nicolas Werner (€12), Jeremy Brown ($100), Alexandre Borges ($25), Vladimir Dikovski (€50), Jiarui Hong (100.00 SEK), Jim Di (500 RUR), Tan Vincent ($30), Sri Harsha Kandrakota (10 AUD), Pillay Harish (10 SGD), Timur Valiev (230 RUR), Carlos Garcia Prado (€10), Salikov Alexander (500 RUR), Oliver Whitehouse (30 GBP), Katy Moe ($14), Maxim Dyakonov ($3), Sebastian Aguilera (€20), Hans-Martin Munch (€15), Jarle Thorsen (100 NOK), Vitaly Osipov ($100), Yuri Romanov (1000 RUR), Aliaksandr Autayeu (€10), Tudor Azoitei ($40), Z0vsky (€10), Yu Dai ($10), Anonymous ($15), Vladislav Chelnokov ($25), Nenad Noveljic ($50), Ryan Smith ($25), Andreas Schommer (€5). Огромное спасибо каждому!

mini-ЧаВО Q: Что необходимо знать перед чтением книги? A: Желательно иметь базовое понимание Си/Си+ + . Q: Должен ли я изучать сразу x86/x64/ARM и MIPS? Это не многовато? A: Думаю, для начала, вы можете читать только о x86/x64, пропуская/пролистывая части о ARM/MIPS. Q: Возможно ли купить русскую/английскую бумажную книгу? A: К сожалению нет, пока ни один издатель не заинтересовался в издании русской или англий­ ской версии. А пока вы можете распечатать/переплести её в вашем любимом копи-шопе или копицентре. Q: Существует ли версия epub/mobi? A: Книга очень сильно завязана на специфические для TeX/LaTeX хаки, поэтому преобразование в HTML (epub/mobi это набор HTML) легким не будет. Q: Зачем в наше время нужно изучать язык ассемблера? A: Если вы не разработчик ОС31, вам наверное не нужно писать на ассемблере: современные ком­ пиляторы (2010-ые) оптимизирую т код намного лучше человека 32. К тому же, современные CPU33 это крайне сложные устройства и знание ассемблера вряд ли помо­ ж е т узнать их внутренности. Но все-таки остается по крайней мере две области, где знание ассемблера может хорошо помочь: 1) исследование malware (зловредов) с целью анализа; 2) лучшее понимание вашего скомпили­ рованного кода в процессе отладки. Таким образом, эта книга предназначена для тех, кто хочет скорее понимать ассемблер, нежели писать на нем, и вот почему здесь масса примеров, связанных с результатами работы компиляторов. Q: Я кликнул на ссылку внутри PDF-документа, ка к теперь вернуться назад? A: В Adobe Acrobat Reader нажмите сочетание Alt+LeftArrow. В Evince кликните на "< ". Q: Могу ли я распечатать эту книгу? Использовать её для обучения? A: Конечно, поэтому книга и лицензирована под лицензией Creative Commons (CC BY-SA 4.0). Q: Почему эта книга бесплатная? Вы проделали большую работу. Это подозрительно, ка к и многие другие бесплатные вещи. A: По моему опыту, авторы технической литературы делают это, в основном ради саморекламы. Такой работой заработать приличные деньги невозможно. 31Операционная Система 32Очень хороший те кст на эту тему: [Agner Fog, The m ic ro a rc h ite c tu re o f Intel, AM D an d VIA CPUs, (2016)] 33Central Processing Unit

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

x v ii

Q: Как можно найти работу reverse engineer-а? A: На reddit, посвященному RE34, время от времени бывают hiring thread (2016). Посмотрите там. В смежном субреддите «netsec» имеется похожий тред: 2016. Q: Как научиться программированию вообще? A: Если вы можете хорошо освоить и Си и LISP, это делает ж изнь программиста значительно легче. Я бы порекомендовал решать задачи из [Брайан Керниган, Деннис Ритчи, Язык программирования Си, второе издание, (1988, 2009)] и SICP35. Q: У меня есть вопрос... A: Напишите мне его емейлом ([email protected]).

Как научиться программированию Многие люди спрашивают об этом. Л егких путей нет, но есть пути быстрые и эффективные. Из моего опыта, это просто: решать задачи из: • Брайан Керниган, Деннис Ритчи, Язык программирования Си, второе издание, (1988, 2009) • Харольд Абельсон, Джеральд Сассман - Структура и интерпретация компьютерных программ • Дональд Э. Кнут, Искусство программирования • Книги Никласа Вирта • Brian W. Kernighan, Rob Pike, Practice of Programming, (1999) ... на чистом Си и Лиспе. В будущем, возможно, вы не будете использовать эти языки вообще. Большинство коммерческих программистов не используют. Но опыт программирования на Си и Лиспе помогает очень сильно в долгосрочной перспективе. Также, вы можете не читать сами книги, просто листайте, тогда, когда вы чувствуете, что вы не можете чего-то понять для решения задачи. В лучшем случае, это занимает годы, или всю жизнь, но это все ж е намного быстрее, чем метаться между очередными модными штуками. Успех этих кн и г вероятно связан с тем, что их авторы - преподаватели, и весь материал был в начале отточен на студентах. Насчет Лиспа, я бы рекомендовал Racket (диалект Scheme). Но это дело вкуса, т а к или иначе. Некоторые люди говорят, что понимание ассемблера очень помогает, даж е если вы не будете использовать его. Это верно. Но это уж е путь для наиболее посвященных гиков, и для начала это можно отлож ить на будущее. Также, очень частая проблема самоучек (включая автора этих строк), это то, что они слишком часто хватаются за слишком трудные проблемы, пропуская более легкие. Это большая ошибка. Сравните со спортом или музыкой - никто не начинает с попыток поднимать сразу 100 кг, или же сразу пытаться играть Капризы Паганини. Я бы сказал т а к - браться за задачу можно тогда, когда вы зараннее, в уме, можете примерно прикинуть решение. I think the art of doing research consists largely of asking questions, and sometimes answering them. Learn how to repeatedly pose miniquestions that represent special cases of the big questions you are hoping to solve. When you begin to explore some area, you take baby steps at first, building intuition about that territory. Play with many small examples, trying to get a complete understanding of particular parts of the general situation. In that way you learn many properties that are true and many properties that are false. That gives guidance about directions th a t are fruitful versus directions to avoid. Eventually your brain will have learned how to take larger and larger steps. And shazam, you'll be ready to take some giant steps and solve the big problem. 34reddit.com/r/ReverseEngineering/ 35Структура и интерпретация компьютерных программ (Structure and Interpretation of Computer Programs)

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

xv iii

But don't stop there! At this point you'll be one of very few people in the world who have ever understood your problem area so well. It will therefore be your responsibility to discover what else is true, in the neighborhood of that problem, using the same or similar methods to what your brain can now envision. Take your results to their "natural boundary" (in a sense analogous to the natural boundary where a function of a complex variable ceases to be analytic). My little book Surreal Numbers provides an authentic example of research as it is happening. The characters in that story make false starts and useful discoveries in exactly the same order as I myself made those false starts and useful discoveries, when I first studied John Conway's fascinating axioms about number systems — his amazingly simple axioms that go significantly beyond real-valued numbers. (One of the characters in that book tends to succeed or fail by brute force and patience; the other is more introspective, and able to see a bigger picture. Both of them represent aspects of my own activities while doing research. With that book I hoped to teach research skills "by osmosis", as readers observe a detailed case study.) Surreal Numbers deals with a purely mathematical topic, not especially close to computer science; it features algebra and logic, not algorithms. When algorithms become part of the research, a beautiful new dimension also comes into play: Algorithms can be implemented on computers! I strongly recommend that you look for every opportunity to write programs that carry out all or a part of whatever algorithms relate to your research. In my experience the very act of writing such a program never fails to deepen my understanding of the problem area. ( Дональд Э. Кнут - h t t p s : / / t h e o r y d i s h . b lo g / 2 0 1 8 / 0 2 / 0 1 / d o n a ld - k n u t h - o n - d o i n g - re s e a rc h / ) Удачи!

О переводе на корейский язык В январе 2015, издательство Acorn в Южной Корее сделало много работы в переводе и издании моей книги (по состоянию на август 2014) на корейский язык. Она теперь доступна на их сайте. Переводил Byungho Min (tw itter/tais9 ). Обложку нарисовал мой хороший знакомый х у д о ж н и к Ан­ дрей Нечаевский facebook/andydinka. Они т а кж е имеют права на издание книги на корейском язы­ ке. Так что если вы хотите иметь настоящую кн и гу на полке на корейском языке и хотите поддер­ ж ать мою работу, вы можете купить её.

О переводе на персидский язык (фарси) В 2016 году книга была переведена Mohsen Mostafa Jokar (который т а кж е известен иранскому со­ обществу по переводу руководства Radare36). Книга доступна на сайте издательства37 (Pendare Pars). Первые 40 страниц: h t t p s : / / b e g i n n e r s . r e / f a r s i . p d f . Регистрация книги в Национальной Библиотеке Ирана: h t t p : / / o p a c . n l a i . i r / o p a c - p r o d / b i b l i o g r a p h i c / 4473995.

О переводе на китайский язык В апреле 2017, перевод на китайский был закончен китайским издательством PTPress. Они т а кж е имеют права на издание книги на китайском языке. Она доступна для заказа здесь: h t t p : / / w w w . e p u b it . c o m . c n / b o o k / d e t a ils / 4 1 7 4 . Что-то вроде ре­ цензии и история о переводе: h t t p : / / w w w .c p t o d a y .c n / n e w s /d e t a il/3 1 5 5 . Основным переводчиком был Archer, перед которым я теперь в долгу. Он был крайне дотошным (в хорошем смысле) и сообщил о большинстве известных ошибок и баг, что крайне важно для литературы вроде этой книги. Я буду рекомендовать его услуги всем остальным авторам! 36h t t p : / / r a d a . r e / g e t / r a d a r e 2 b o o k - p e r s ia n .p d f 37h t t p : / / g o o . g l/ 2 T z x 0 H

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

x ix

Ребята из Antiy Labs т а кж е помогли с переводом. Здесь предисловие написанное ими.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

Глава 1

Об ра зцы кода 1.1. Метод Когда автор этой книги учил Си, а затем С и++, он просто писал небольшие фрагменты кода, ком­ пилировал и смотрел, что получилось на ассемблере. Так было намного проще понять1. Он делал это такое количество раз, что связь между кодом на Си/Си+ + и тем, что генерирует компилятор, вбилась в его подсознание достаточно глубоко. После этого не трудно, глядя на код на ассембле­ ре, сразу в общих чертах понимать, что там было написано на Си. Возможно это поможет кому-то ещё. Иногда здесь используются достаточно древние компиляторы, чтобы получить самый короткий (или простой) фрагмент кода. Кстати, есть очень неплохой вебсайт где можно делать всё то же самое, с разными компиляторами, вместо того чтобы инсталлировать их у себя. Вы можете использовать и его: h t t p : / / g o d b o l t . o r g / .

Упражнения Когда автор этой книги учил ассемблер, он т а кж е часто компилировал короткие ф ункции на Си и затем постепенно переписывал их на ассемблер, с целью получить ка к можно более короткий код. Наверное, этим не стоит заниматься в наше время на практике (потому что конкурировать с со­ временными компиляторами в плане эффективности очень трудно), но это очень хороший способ разобраться в ассемблере лучше. Так что вы можете взять любой фрагмент кода на ассемблере в этой книге и постараться сделать его короче. Но не забывайте о тестировании своих результатов.

Уровни оптимизации и отладочная информация Исходный код можно компилировать различными компиляторами с различными уровнями опти ­ мизации. В типичном компиляторе этих уровней около трёх, где нулевой уровень — отключить оптимизацию. Различают т а кж е уровни оптимизации кода по размеру и по скорости. Неоптимизи­ рующий компилятор работает быстрее, генерирует более понятный (хотя и более объемный) код. Оптимизирующий компилятор работает медленнее и старается сгенерировать более быстрый (хо­ тя и не обязательно краткий) код. Наряду с уровнями оптимизации компилятор может включать в конечный файл отладочную информацию, производя таким образом код, который легче о тл аж и ­ вать. Одна очень важная черта отладочного кода в том, что он может содержать связи между к а ж ­ дой строкой в исходном коде и адресом в машинном коде. Оптимизирующие компиляторы обычно генерируют код, где целые строки из исходного кода могут быть оптимизированы и не присутство­ вать в итоговом машинном коде. Практикую щ ий reverse engineer обычно сталкивается с обеими версиями, потому что некоторые разработчики включают оптимизацию, некоторые другие — нет. Вот почему мы постараемся поработать с примерами для обеих версий. 1Честно говоря, он и до сих пор т а к делает, когда не понимает, ка к работает некий код.

1

2

1.2. Некоторые базовые понятия 1.2.1. Краткое введение в CPU CPU это устройство исполняющее все программы. Н е м н о го т е р м и н о л о г и и : И н с т р у к ц и я : примитивная команда CPU. Простейшие примеры: перемещение между регистрами, работа с памятью, примитивные арифметические операции. Как правило, кажды й CPU имеет свой набор инструкций (ISA). М а ш и н н ы й к о д : код понимаемый CPU. Каждая инструкция обычно кодируется несколькими бай­ тами. Я з ы к а с с е м б л е р а : машинный код плюс некоторые расширения, призванные облегчить труд про­ граммиста: макросы, имена, итд. Р е ги стр CPU : Каждый CPU имеет некоторый фиксированный набор регистров общего назначения (GPR2). и 8 в x86, и 16 в x86-64, и 16 в ARM. Проще всего понимать регистр ка к временную переменную без типа. Можно представить, что вы пишете на ЯП высокого уровня и у вас только 8 переменных шириной 32 (или 64) бита. Можно сделать очень много используя только их! Откуда взялась разница между машинным кодом и ЯП высокого уровня? Ответ в том, что люди и CPU-ы отличаются друг от друга — человеку проще писать на ЯП высокого уровня вроде Си/Си+ + , Java, Python, а CPU проще работать с абстракциями куда более низкого уровня. Возможно, можно было бы придумать CPU исполняющий код ЯП высокого уровня, но он был бы значительно слож ­ нее, чем те, что мы имеем сегодня. И наоборот, человеку очень неудобно писать на ассемблере из-за его низкоуровневости, к тому же, крайне трудно обойтись без мелких ошибок. Программа, переводящая код из ЯП высокого уровня в ассемблер называется компилятором 3.

Н е с к о л ь к о сл о в о р а з н и ц е м е ж д у ISA x86 всегда был архитектурой с инструкциями переменной длины, т а к что когда пришла 64-битная эра, расширения x64 не очень сильно повлияли на ISA. ARM это RIS^ -процессор разработанный с учетом инструкций одинаковой длины, что было некоторым преимуществом в прошлом. Так что в самом начале все инструкции ARM кодировались 4-мя байтами5. Это то, что сейчас называется «режим ARM». Потом они подумали, что это не очень экономично. На самом деле, самые исполь­ зуемые инструкц и и 6 процессора на практике могут быть закодированы c использованием мень­ шего количества информации. Так что они добавили другую ISA с названием Thumb, где каждая инструкция кодируется всего лишь 2-мя байтами. Теперь это называется «режим Thumb». Но не все инструкции ARM могут быть закодированы в двух байтах, т а к что набор инструкций Thumb ограниченный. Код, скомпилированный для режима ARM и Thumb может сосуществовать в одной программе. Затем создатели ARM решили, что Thumb можно расширить: т а к появился Thumb-2 (в ARMv7). Thumb-2 это всё ещё двухбайтные инструкции, но некоторые новые инструкции имеют длину 4 байта. Распространено заблуждение, что Thumb-2 — это смесь ARM и Thumb. Это не верно. Режим Thumb-2 был дополнен до более полной поддерж ки возможностей процессора и теперь мо­ ж е т легко конкурировать с режимом ARM. Основное количество приложений для iPod/iPhone/iPad скомпилировано для набора инструкций Thumb-2, потому что Xcode делает т а к по умолчанию. По­ том появился 64-битный ARM. Это ISA снова с 4-байтными инструкциями, без дополнительного режима Thumb. Но 64-битные требования повлияли на ISA, т а к что теперь у нас 3 набора и нструк­ ций ARM: режим ARM, режим Thumb (включая Thumb-2) и ARM64. Эти наборы инструкций частично пересекаются, но можно сказать, это скорее разные наборы, нежели вариации одного. Следова­ тельно, в этой книге постараемся добавлять фрагменты кода на всех трех ARM ISA. Существует ещё много RISC ISA с инструкциями фиксированной 32-битной длины — это ка к минимум MIPS, PowerPC и Alpha AXP. 2General Purpose Registers (регистры общего пользования) 3В более старой русскоязычной литературе т а к ж е часто встречается термин «транслятор». 4Reduced Instruction Set Computing 5Кстати, инструкции фиксированного размера удобны тем, что всегда можно легко узнать адрес следующей (или преды­ дущей) инструкции. Эта особенность будет рассмотрена в секции об операторе switch() (1.16.2 (стр. 174)). 6А это MOV/PUSH/CALL/Jcc

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

3

1.2.2. Представление чисел Nowadays octal numbers seem to be used for exactly one purpose—file permissions on POSIX systems—but hexadecimal numbers are widely used to emphasize the bit pattern of a number over its numeric value. Alan A. A. Donovan, Brian W. Kernighan — The Go Programming Language Люди привыкли к десятичной системе счисления вероятно потому что почти у ка ж д ого есть по 10 пальцев. Тем не менее, число 10 не имеет особого значения в науке и математике. Двоичная система естествена для цифровой электроники: 0 означает отсутствие тока в проводе и 1 — его присутствие. 10 в двоичной системе это 2 в десятичной; 100 в двоичной это 4 в десятичной, итд. Если в системе счисления есть 10 цифр, её основание или radix это 10. Двоичная система имеет основание 2.

Важные вещи, которые полезно вспомнить: 1) число это число, в то время ка к цифра это термин из системы письменности, и это обычно один символ; 2) само число не меняется, когда конверти­ руется из одного основания в другое: меняется способ его записи (или представления в памяти). Как сконвертировать число из одного основания в другое? Позиционная нотация используется почти везде, это означает, что всякая цифра имеет свой вес, в зависимости от её расположения внутри числа. Если 2 расположена в самом последнем месте справа, это 2. Если она расположена в месте перед последним, это 20. Что означает 1234? 103 • 1 + 102 • 2 + 101 • 3 + 1 • 4 = 1234 или 1000 • 1 + 100 • 2 + 10 • 3 + 4 = 1234 Та ж е история и для двоичных чисел, только основание там 2 вместо 10. Что означает 0b101011? 25 • 1 + 24 •0 + 23 • 1 + 22 •0 + 21 • 1 + 20 • 1 = 43 или 32 • 1 + 16 •0 + 8 • 1 + 4 • 0 + 2 • 1 + 1 = 43 Позиционную нотацию можно противопоставить непозиционной нотации, такой ка к римская си­ стема записи чисел 7. Вероятно, человечество перешло на позиционную нотацию, потому что та к проще работать с числами (сложение, умножение, итд) на бумаге, в ручную. Действительно, двоичные числа можно складывать, вычитать, итд, точно такж е, ка к этому обычно обучают в школах, только доступны лишь 2 цифры. Двоичные числа громоздки, когда их используют в исходных кодах и дампах, т а к что в этих слу­ чаях применяется шестнадцатеричная система. Используются цифры 0..9 и еще 6 латинских букв: A..F. Каждая шестнадцатеричная цифра занимает 4 бита или 4 двоичных цифры, та к что конвер­ тировать из двоичной системы в шестнадцатеричную и назад, можно легко вручную, или даже в уме. шестнадцатеричная 0 1 2 3 4 5 6 7 8 9 A B C D E F

двоичная 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

десятичная 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

7Об эволюции способов записи чисел, см.также: [Donald E. Knuth, The A r t o f C o m p u te r Prog ram m in g, Volume 2, 3rd ed., (1997), 195-213.]

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

4

Как понять, какое основание используется в конкретном месте? Десятичные числа обычно записываются ка к есть, т.е., 1234. Но некоторые ассемблеры позволяют подчеркивать этот факт для ясности, и это число может быть дополнено суффиксом "d": 1234d. К двоичным числам иногда спереди добавляют префикс "0b": 0b100110111 (В GCC8 для этого есть нестандартное расширение языка 9). Есть т а кж е еще один способ: суффикс "b", например: 100110111b. В этой книге я буду пытаться придерживаться префикса "0b" для двоичных чисел. Шестнадцатеричные числа имеют префикс "0x" в Си/Си+ + и некоторых других ЯП: 0x1234ABCD. Либо они имеют суффикс "h": 1234ABCDh — обычно т а к они представляются в ассемблерах и от­ ладчиках. Если число начинается с цифры A..F, перед ним добавляется 0: 0ABCDEFh. Во времена 8-битных домашних компьютеров, был та кж е способ записи чисел используя префикс $, например, $ABCD. В книге я попытаюсь придерживаться префикса "0x" для шестнадцатеричных чисел. Нужно ли учиться конвертировать числа в уме? Таблицу шестнадцатеричных чисел из одной циф­ ры легко запомнить. А запоминать большие числа, наверное, не стоит. Наверное, чаще всего шестнадцатеричные числа можно увидеть в URL10-ах. Так кодируются буквы не из числа латинских. Например: h ttp s ://e n .w iktio n a ry.o rg /w iki/n a % C 3 % A F ve t% C 3 % A 9 это URL страницы в Wiktionary о слове «naivete».

В о с ь м е р и ч н а я си сте м а Еще одна система, которая в прошлом много использовалась в программировании это восьмерич­ ная: есть 8 цифр (0..7) и каждая описывает 3 бита, т а к что легко конвертировать числа туда и назад. Она почти везде была заменена шестнадцатеричной, но удивительно, в *NIX имеется у т и ­ лита использующаяся многими людьми, которая принимает на вход восьмеричное число: chmod. Как знают многие пользователи *NIX, аргумент chmod это число из трех цифр. Первая цифра это права владельца файла, вторая это права группы (которой файл принадлежит), третья для всех остальных. И каждая цифра может быть представлена в двоичном виде: десятичная 7 6 5 4 3 2 1 0

двоичная 111 110 101 100 011 010 001 000

значение rw x rw r-x r--w x -w --x —

Так что каж ды й бит привязан к флагу: read/write/execute (чтение/запись/исполнение). И вот почему я вспомнил здесь о chmod, это потому что всё число может быть представлено как число в восьмеричной системе. Для примера возьмем 644. Когда вы запускаете chmod 644 f i l e , вы выставляете права read/write для владельца, права read для группы, и снова, read для всех остальных. Сконвертируем число 644 из восьмеричной системы в двоичную, это будет 110100100, или (в группах по 3 бита) 110 100 100. Теперь мы видим, что каждая тройка описывает права для владельца/группы/остальных: первая это rw-, вторая это r - - и третья это r - - . Восьмеричная система была т а к ж е популярная на старых компьютерах вроде PDP-8, потому что слово там могло содержать 12, 24 или 36 бит, и эти числа делятся на 3, та к что выбор восьмеричной системы в той среде был логичен. Сейчас, все популярные компьютеры имеют размер слова/адреса 16, 32 или 64 бита, и эти числа делятся на 4, та к что шестнадцатеричная система здесь удобнее. Восьмеричная система поддерживается всеми стандартными компиляторами Си/Си+ + . Это ино­ гда источник недоумения, потому что восьмеричные числа кодируются с нулем вперед, например, 8GNU Compiler Collection 9h t t p s : / / g c c . g n u . o r g / o n l i n e d o c s / g c c / B i n a r y - c o n s ta n t s .h t m l 10Uniform Resource Locator

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

5

0377 это 255. И иногда, вы можете сделать опечатку, и написать "09" вместо 9, и компилятор вы­ даст ошибку. GCC может выдать что-то вроде: e r r o r : i n v a l i d d i g i t "9" in o c t a l c o n sta n t. Также, восьмеричная система популярна в Java: когда IDA показывает строку с непечатаемыми символами, они кодируются в восьмеричной системе вместо шестнадцатеричной. Точно т а кж е се­ бя ведет декомпилятор с Java JAD.

Делимость Когда вы видите десятичное число вроде 120, вы можете быстро понять что оно делится на 10, потому что последняя цифра это 0. Точно такж е, 123400 делится на 100, потому что две последних цифры это нули. Точно такж е, шестнадцатеричное число 0x1230 делится на 0x10 (или 16), 0x123000 делится на 0x1000 (или 4096), итд. Двоичное число 0b1000101000 делится на 0b1000 (8), итд. Это свойство можно часто использовать, чтобы быстро понять, что длина какого-либо блока в па­ мяти выровнена по некоторой границе. Например, секции в PE11-файлах почти всегда начинаются с адресов заканчивающихся 3 шестнадцатеричными нулями: 0x41000, 0x10001000, итд. Причина в том, что почти все секции в PE выровнены по границе 0x1000 (4096) байт.

А риф м етика произвольной точности и основание Арифметика произвольной точности (multi-precision arithmetic) может использовать огромные чис­ ла, которые могут храниться в нескольких байтах. Например, ключи RSA, и открытые и закрытые, могут занимать до 4096 бит и даже больше. В [Donald E. Knuth, The Art o f Computer Programming, Volume 2, 3rd ed., (1997), 265] можно най­ ти такую идею: когда вы сохраняете число произвольной точности в нескольких байтах, всё число может быть представлено ка к имеющую систему счисления по основанию 28 = 256, и каждая цифра находится в соответствующем байте. Точно такж е, если вы сохраняете число произвольной точ­ ности в нескольких 32-битных целочисленных значениях, каждая цифра отправляется в кажды й 32-битный слот, и вы можете считать что это число записано в системе с основанием 232.

П роизнош ение Числа в недесятичных системах счислениях обычно произносятся по одной цифре: "один-нольноль-один-один-...". Слова вроде "десять", "тысяча", итд, обычно не произносятся, потому что то­ гда можно спутать с десятичной системой.

Ч и сл а с п л а в а ю щ е й з а п я то й Чтобы отличать числа с плавающей запятой от целочисленных, часто, в конце добавляют ".0", например 0.0, 123.0, итд.

1.3. Пустая функция Простейшая функция из всех возможных, это функция, которая ничего не делает: Листинг 1.1: Код на Си/Си+ + void f ( ) {

re tu rn ;

}; Скомпилируем! 11Portable Executable

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

6

1.3.1. x86 Для x 8 6 и MSVC и GCC делает одинаковый код: Листинг 1.2: ОптимизирующийGCC/MSVC (вывод на ассемблере) f: ret Тут только одна инструкция: RET, которая возвращает управление в вызывающую ф -цию.

1.3.2. ARM Листинг 1.3: О п т и м и з и р у ю щ и й ^ іІ 6/2013 (Режим ARM) ASM Output C P O D RXN PBE

f

lr

Адрес возврата (RA12) в ARM не сохраняется в локальном стеке, а в регистре LR13. Так что ин­ струкция BX LR делает переход по этому адресу, и это то ж е самое что и вернуть управление в вызывающую ф-цию.

1.3.3. MIPS Есть два способа называть регистры в мире MIPS. По номеру (от $0 до $31) или по псевдоимени ($V0, $A0, итд.). Вывод на ассемблере в GCC показывает регистры по номерам: Листинг 1.4: ОптимизирующийGCC 4.4.5 (вывод на ассемблере) j nop

$31

„.а IDA14— по псевдоименам: Листинг 1.5: ОптимизирующийGCC 4.4.5 (IDA) j nop

$ra

Первая инструкция — это инструкция перехода (J или JR), которая возвращает управление в вызы­ вающую ф-цию, переходя по адресу в регистре $31 (или $RA). Это аналог регистра LR в ARM. Вторая инструкция это NOP15, которая ничего не делает. Пока что мы можем её игнорировать.

Ещ е к о е -ч т о об и м е н а х и н с т р у к ц и й и р е ги с т р о в в MIPS Имена регистров и инструкций в мире MIPS традиционно пишутся в нижнем регистре. Но мы будем использовать верхний регистр, потому что имена инструкций и регистров других ISA в этой книге т а к ж е в верхнем регистре. 12Адрес возврата 13Link Register 14 Интерактивный дизассемблер и отладчик, разработан Hex-Rays 15No Operation

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

7

1.3.4. Пустые функции на практике Не смотря на то, что пустые ф ункции бесполезны, они довольно часто встречаются в низкоуровне­ вом коде. Во-первых, популярны функции, выводящие информацию в отладочный лог, например:

void dbg_print (const char *fm t, { # ifd e f _DEBUG / / открыть лог-файл / / записать в лог-файл / / закрыть лог-файл #endif };

Листинг 1.6: Код на Си/Си+ + ...)

void some_function() { dbg_print ("we did something\n"); }; В не-отладочной сборке (например, "release"), _DEBUG не определен, т а к что функция d b g _ p r in t ( ) , не смотря на то, что будет продолжать вызываться в процессе исполнения, будет пустой. Во-вторых, популярный способ защиты ПО это компиляция нескольких сборок: одной для легаль­ ных покупателей, второй — демонстрационной. Демонстрационная сборка может не иметь какихто важных функций, например: Листинг 1.7: Код на Си/Си+ + void s a ve _file () { # ifn d e f DEMO / / код, сохраняющий что-то #endif }; Функция s a v e _ f i l e ( ) может быть вызвана, когда пользователь кликает меню F ile -> S a v e . Демо­ версия может поставляться с отключенным пунктом меню, но даж е если кракер разрешит этот пункт, будет вызываться пустая функция, в которой полезного кода нет. IDA маркирует такие функции именами вроде n u lls u b _ 0 0 , n u lls u b _ 0 1 , итд.

1.4. Возврат значения Еще одна простейшая функция это та, что возвращает некоторую константу: Вот, например: Листинг 1.8: Код на Си/Си+ + in t f() { return 123; }; Скомпилируем её.

1.4.1. x86 И вот что делает оптимизирующ ий GCC: (В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8

Листинг 1.9: ОптимизирующийGCC/MSVC (вывод на ассемблере) f: mov ret

eax, 123

Здесь только две инструкции. Первая помещает значение 123 в регистр EAX, который использу­ ется для передачи возвращаемых значений. Вторая это RET, которая возвращает управление в вызывающую функцию. Вызывающая функция возьмет результат из регистра EAX.

1.4.2. ARM А что насчет ARM? Листинг 1.10: ОптимизирующийКеіІ 6/2013 (Режим ARM) ASM Output f

PROC MOV BX ENDP

r0,#0x7b lr

123

ARM использует регистр R0 для возврата значений, та к что здесь 123 помещается в R0. Нужно отметить, что название инструкции MOV в x86 и ARM сбивает с толку. На самом деле, данные не перемещаются, а скорее копируются.

1.4.3. MIPS Вывод на ассемблере в GCC показывает регистры по номерам: Листинг 1.11: ОптимизирующийGCC 4.4.5 (вывод на ассемблере) 3 2 1 1, 32 $$

j li

# 0x7b

„.а IDA— по псевдоименам: A ) ID

5 . .4 4.

Листинг 1.12: $ra $v0, 0x7B

C C G й и щ ю у р и з и м и т п О

jr li

Так что регистр $2 (или $V0) используется для возврата значений. L I это "Load Immediate", и это эквивалент MOV в MIPS. Другая инструкция это инструкция перехода (J илиJR), которая возвращает управление в вызыва­ ющую ф -цию. Но почему инструкция загрузки (LI) и инструкция перехода (J илиJR) поменяны местами? Это арте­ факт RISC и называется он "branch delay slot". На самом деле, нам не нужно вникать в эти детали. Нужно просто запомнить: в MIPS инструкция после инструкции перехода исполняется перед инструкцией перехода. Таким образом, инструкция перехода всегда поменяна местами с той, которая должна быть испол­ нена перед ней.

1.4.4. На практике На практике крайне часто встречаются ф-ции, которые возвращают 1 (true) или 0 (false). Самые маленькие утилиты UNIX, /bin/true и /bin/false возвращают 0 и 1 соответственно, ка к код возврата (ноль ка к код возврата обычно означает успех, не ноль означает ошибку).

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

9

1.5. Hello, world! Продолжим, используя знаменитый пример из книги [Брайан Керниган, Деннис Ритчи, Язык про­ граммирования Си, второе издание, (1988, 2009)]:

Листинг 1.13: код на Си/Си+ + #include i n t main() { p r i n t f ( " h e l l o , w o rld \n "); return 0; }

1.5.1. x86 MSVC Компилируем в MSVC 2010: c l 1.cpp /Fa1.asm (Ключ /F a означает сгенерировать листинг на ассемблере) _______________________________________ Листинг 1.14: MSVC 2010 CONST SEGMENT $SG3830 DB 'h e llo , w o rld ', 0AH, 00H CONST ENDS PUBLIC _main EXTRN _ p r i n t f : PROC ; Function compile fla g s : /Odtp _TEXT SEGMENT _main PROC push ebp mov ebp, esp push OFFSET $SG3830 c a ll _ p r in t f add esp, 4 xor eax, eax pop ebp ret 0 _main ENDP TEXT ENDS MSVC выдает листинги в синтаксисе Intel. Разница между синтаксисом Intel и AT&T будет рассмот­ рена немного позже: Компилятор сгенерировал файл 1 .o b j, который впоследствии будет слинкован линкером в 1.exe. В нашем случае этот файл состоит из двух сегментов: CONST (для данных-констант) и _TEXT (для кода). Строка h e l l o , w o rld в Си/Си+ + имеет тип const c h a r[][B ja rn e Stroustrup, The C++ Programming Language, 4th Edition, (2013)p176, 7.3.2], однако не имеет имени. Но компилятору нужно как-то с ней работать, поэтому он дает ей внутреннее имя $SG3830. Поэтому пример можно было бы переписать вот так: #include const char $SG3830[]="hello, w o rld\n "; i n t main() { p r i n t f ( $SG3830); return 0; } (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

10

Вернемся к листингу на ассемблере. Как видно, строка заканчивается нулевым байтом — это тре­ бования стандарта Си/Си+ + для строк. Больше о строках в Си/Си+ + : 5.4.1 (стр. 696). В сегменте кода _TEXT находится пока только одна функция: m a in (). Ф ункция m a in (), ка к и прак­ тически все функции, начинается с пролога и заканчивается эпилогом 16. Далее следует вызов функции p r i n t f ( ) : CALL _ p r i n t f . Перед этим вызовом адрес строки (или указатель на неё) с нашим приветствием ("Hello, world!") при помощи инструкции PUSH помещается в стек. После того, ка к функция p r i n t f ( ) возвращает управление в ф ункцию m a in (), адрес строки (или указатель на неё) всё ещё леж и т в стеке. Так ка к он больше не нужен, то указатель стека (регистр ESP) корректируется. ADD ESP, 4 означает прибавить 4 к значению в регистре ESP. Почему 4? Так ка к это 32-битный код, для передачи адреса нуж но 4 байта. В х64-коде это 8 байт. ADD ESP, 4 эквивалентно POP регистр, но без использования какого-либо регистра17. Некоторые компиляторы, например, Intel C ++ Compiler, в этой ж е ситуации могут вместо ADD сге­ нерировать POP ECX (подобное можно встретить, например, в коде Oracle RDBMS, им скомпилиро­ ванном), что почти то ж е самое, только портится значение в регистре ECX. Возможно, компилятор применяет POP ECX, потому что эта инструкция короче (1 байт у POP против 3 у ADD). Вот пример использования POP вместо ADD из Oracle RDBMS: Листинг 1.15: Oracle RDBMS 10.2 Linux (файл app.o) . text:0800029A . te x t :0800029В . text:080002A0

push c a ll pop

ebx qksfroChild ecx

После вызова p r i n t f ( ) в оригинальном коде на Си/Си++указано r e tu r n 0 — вернуть 0 в качестве результата ф ункции m a in ( ) . В сгенерированном коде это обеспечивается инструкцией XOR EAX, EAX. XOR, ка к легко догадаться — «исключающее ИЛИ»18, но компиляторы часто используют его вместо простого MOV EAX, 0 — снова потому, что опкод короче (2 байта у XOR против 5 у mOv ). Некоторые компиляторы генерируют SUB EAX, EAX, что значит отнять значение в EAX от значения в EAX, что в любом случае даст 0 в результате.

Самая последняя инструкция RET возвращает управление в вызывающую функцию. Обычно это код Си/Си+ + CRT19, который, в свою очередь, вернёт управление в ОС.

GCC Теперь скомпилируем то ж е самое компилятором GCC 4.4.1 в Linux: gcc 1 .c -o 1. Затем при по­ мощи IDA посмотрим ка к скомпилировалась функция m a in (). IDA, ка к и MSVC, показывает код в синтаксисе Intel20. Листинг 1.16: код в IDA main

proc near

var_10

= dword p tr -10h push mov and sub mov mov c a ll

ebp ebp, esp esp, OFFFFFFFOh esp, 10h eax, o f fs e t aHelloWorld ; "h e llo , world\n" [esp+10h+var_10], eax _ p r in t f

16Об этом смотрите подробнее в разделе о прологе и эпилоге функции (1.6 (стр. 29)). 17Флаги процессора, впрочем, модифицируются 18wikipedia 19C Runtime library 20Мы т а к ж е можем заставить GCC генерировать листинги в этом формате при помощи ключей -S -masm=intel.

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

11

mov leave retn endp

main

eax, 0

Почти то ж е самое. Адрес строки h e l l o , w o rld , лежащей в сегменте данных, вначале сохраняется в EAX, затем записывается в стек. А ещё в прологе ф ункции мы видим AND ESP, 0FFFFFFF0h — эта инструкция выравнивает значение в ESP по 16-байтной границе, делая все значения в стеке т а кж е выровненными по этой границе (процессор более эффективно работает с переменными, расположенными в памяти по адресам кратным 4 или 16)21. SUB ESP, 10h выделяет в стеке 16 байт. Хотя, ка к будет видно далее, здесь достаточно только 4. Это происходит потому, что количество выделяемого места в локальном стеке тож е выровнено по 16-байтной границе. Адрес строки (или указатель на строку) затем записывается прямо в стек без помощи инструкции PUSH. var_10 одновременно и локальная переменная и аргумент для p r i n t f ( ) . Подробнее об этом будет ниже. Затем вызывается p r i n t f ( ) . В отличие от MSVC, GCC в компиляции без включенной оптимизации генерирует MOV EAX, 0 вместо более короткого опкода. Последняя инструкция LEAVE — это аналог команд MOV ESP, EBP и POP EBP — то есть возврат у к а ­ зателя стека и регистра EBP в первоначальное состояние. Это необходимо, т.к. в начале функции мы модифицировали регистры e Sp и EBP (при помощи MOV EBP, ESP / AND ESP, ...).

GCC: С и н та к с и с A T & T Попробуем посмотреть, ка к выглядит то ж е самое в синтаксисе AT&T языка ассемблера. Этот син­ таксис больше распространен в UNIX-мире. Листинг 1.17: компилируем в GCC 4.7.3 gcc -S 1_1.c Получим такой файл: Листинг 1.18: GCC 4.7.3 .file "1_1.c" .section .rodata . LC0: .s tr in g "h e llo , world\n" . te x t .g lo b l main .type main, @function main: . LFB0: . c fi_ s ta rtp r o c pushl %ebp .c fi_ d e f_ c fa _ o ffs e t 8 . c f i _ o f f s e t 5, -8 movl %esp, %ebp .c fi_ d e f_ c fa _ re g is te r 5 andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) c a ll p r in tf movl $0, %eax leave .c fi_ re s to r e 5 .c fi_ d e f_ c fa 4, 4 ret . cfi_endproc 21Wikipedia: Выравнивание данных

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

12

. LFE0: .size main, .-main .id e n t "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3" . section .note.GNU-stack,"",@progbits Здесь много макросов (начинающихся с точки). Они нам пока не интересны. Пока что, ради упрощения, мы можем их игнорировать (кроме макроса .string, при помощи которого кодируется последовательность символов, оканчивающихся нулем — такие ж е строки ка к в Си). И тогда получится следующее 22: Листинг 1.19: GCC 4.7.3 . LC0: . s trin g "h e llo , world\n" main: pushl movl andl subl movl c a ll movl leave ret

%ebp %esp, %ebp $-16, %esp $16, %esp $.LC0, (%esp) p r in tf $0, %eax

Основные отличия синтаксиса Intel и AT&T следующие: • Операнды записываются наоборот. В Intel-синтаксисе: < и н стр укц и я > < операнд-источник>. В AT&T-синтаксисе: < и н стр укц и я > < операнд-источник> . Чтобы легче понимать разницу, можно запомнить следующее: когда вы работаете с синтакси­ сом Intel — можете в уме ставить знак равенства (=) между операндами, а когда с синтаксисом AT&T — мысленно ставьте стрелку направо (^ ) 23. • AT&T: Перед именами регистров ставится символ процента (%), а перед числами символ дол­ лара ($). Вместо квадратных скобок используются круглые. • AT&T: К каждой инструкции добавляется специальный символ, определяющий тип данных: - q — quad (64 бита) - l — long (32 бита) - w — word (16 бит) - b — byte (8 бит) Возвращаясь к результату компиляции: он идентичен тому, который мы посмотрели в IDA. Одна мелочь: 0FFFFFFF0h записывается ка к $-16. Это то ж е самое: 16 в десятичной системе это 0x10 в шестнадцатеричной. -0x10 будет ка к раз 0xFFFFFFF0 (в рамках 32-битных чисел). Возвращаемый результат устанавливается в 0 обычной инструкцией MOV, а не XOR. MOV просто за­ груж ает значение в регистр. Её название не очень удачное (данные не перемещаются, а копиру­ ются). В других архитектурах подобная инструкция обычно носит название «LOAD» или «STORE» или что-то в этом роде.

К о р р е к ц и я ( п а т ч и н г ) с т р о к и (W in 3 2 ) Мы можем легко найти строку "hello, world" в исполняемом файле при помощи Hiew: 22Кстати, для уменьшения генерации «лишних» макросов, можно использовать такой ключ GCC: -fno-asynch rono us-u nw indtables

23Кстати, в некоторых стандартных функциях библиотеки Си (например, memcpy(), strcpy()) т а к ж е применяется рас­ становка аргументов ка к в синтаксисе Intel: вначале указатель в памяти на блок назначения, затем указатель на блокисточник.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

13

33 Hiew: hw_spanish.exe С :\tmp\hw_spanish.ехе .400025Е0 00 00 00 00-00 .400025F0 00 00 00 00-00 .40003000 [Ш! 65 6С 6C-6F .40003010 0і 00 00 00 -IFЕ .40003020 32 А2 DF 2D-99 .40003030 00 00 00 00-00 .40003040 00 00 00 00-00

E F W 0 ---- --00 00 00-00 00 00 00 00-00 00 2С 20 77-6F 72 FF FF FF-FF FF 2В 00 00-CD 5D 00 00 00-00 00 00 00 00-00 00

00 00 6С FF 20 00 00

РЕ+. 00000001' 400030001Hiew 8.02 00-00 00 00 00 00-00 00 00 00 64-0А 00 00 00 [Jello, worlds FF-00 00 00 00 Е ■ D4 2о"-бчн =] j - f D2-66 FF FF 00-00 00 00 00 00-00 00 00 00

Рис. 1.1: Hiew Можем перевести наше сообщение на испанский язык:

т

Hiew: hw_5panish.exe

C:\tmp\hw_spanish.exe 000011Е0 00 00 00 00-00 000011F0 00 00 00 00-00 00001200 68 6F 6С 61-2С 00001210 01 00 00 00-FE 00001220 32 А2 DF 2D-99 00001230 00 00 00 00-00 00001240 00 00 00 00-00

^ E1FW0 EDITMODE 00 00 00-00 00 00 00 00 00-00 00 00 20 6D 75-бЕ 64 6F FF FF FF-FF FF FF 2В 00 00-CD 5D 20 00 00 00-00 00 00 00 00 00-00 00 00

ш РЕ+00-00 00-00 0Д-00 FF-00 D2-66 00-00 00-00

00000000'0000120D Hiew 8.02 00 00 00 00 00 00 00 00 00 hola, murdoEl 00 00 00 E ■ D4 FF FF 2o®-6+ =] j - f 00 00 00 00 00 00

Рис. 1.2: Hiew Испанский т е кст на 1 байт короче английского, та к что добавляем в конце байт 0x0A (\n) и нулевой байт. Работает. Что если мы хотим вставить более длинное сообщение? После оригинального текста на англий­ ском есть какие-то нулевые байты. Трудно сказать, можно ли их перезаписывать: они могут где-то использоваться в CRT-коде, а может и нет. Так или иначе, вы можете их перезаписывать, только если вы действительно знаете, что делаете.

К о р р е к ц и я с т р о к и (L in u x x6 4 ) Попробуем пропатчить исполняемый файл для Linux x64 используя rada.re: Листинг 1.20: Сессия в rada.re dennis@bigbox ~/tmp % gcc hw.c dennis@bigbox ~/tmp % radare2 a.out - - SHALL WE PLAY A GAME? [0x00400430]> / hello Searching 5 bytes from 0x00400000 to 0x00601040: 68 65 6c 6c 6f Searching 5 bytes in [0x400000-0x601040] h its : 1 0x004005c4 hit0_0 .HHhello, world;0. [0x00400430]> s 0x004005c4 [0x004005c4]> px - o ffs e t 0 1 2 3 4 5 6 7 8 9 A B CD E F 0x004005c4 6865 6c6c 6f2c 2077 6f72 6c64 0000 0000 0x004005d4 011b 033b 3000 0000 0500 0000 1cfe f f f f 0x004005e4 7c00 0000 5cfe f f f f 4c00 0000 5 2 ff f f f f

0123456789ABCDEF h e llo , w o rld ___ . . . ; 0 .................... | . . . \ . . . l ___ R...

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

6 о о

U 1

0 0 51

00

0

О

ffff о

0000 1001 0000 0000 1001 0000 4a0f 0000 0000 0800 0000 0000 0e20 8d04

ffff 0000 0708 ffff 0000

0000 0e10 0000 0e10 288c

c400 0000 9001 2a00 0000 9001 3000 003f 4400 8602 6400 8f02 0548

0000 0000 0710 0000 0000 0000 0000 1a3b 0000 430d 0000 420e 0e30

f f c d

0c01 0178 1c00 0000 0178 1c00 0e18 0000

6 c ff 1400 1b0c 08fe 1400 1b0c 98fd 0b77 1c00 0041 4400 0042 420e

0 00 00

a ■Р* О о 0 о о о

14

0x004005f4 0x00400604 0x00400614 0x00400624 0x00400634 0x00400644 0x00400654 0x00400664 0x00400674 0x00400684 0x00400694 0x004006a4 0x004006b4

017a 1400 0000 017a 2400 000e 2a33 a6fe 0650 a0fe 188e 8606

ffff 5200 0000 0000 5200 0000 1046 2422 ffff 0c07 ffff 0345 480e

__ l . . 7R . x ........ .*... 7R $ . .. .0 ... F ..J ..w . ? ■*3$" .D ... ........A. ...C . P ___ D.. . d . . . e ___ B. ...B . F . ..B .( ..H.0 H . x ........

[0x004005c4]> oo+ F ile a.out reopened in read-write mode [0x004005c4]> w hola, mundo\x00 [0x004005c4]> q dennis@bigbox ~/tmp % ./a .o u t hola, mundo Что я здесь делаю: ищу строку «hello» используя команду / , я затем я выставляю курсор (seek в терминах rada.re) на этот адрес. Потом я хочу удостовериться, что это действительно нужное место: px выводит байты по этому адресу. oo+ переключает rada.re в режим чтения-записи. w за­ писывает ASCII-строку на месте курсора (seek). Нужно отметить \0 0 в конце — это нулевой байт. q заканчивает работу.

Это р е а л ь н а я и с то р и я взлом а ПО Некое ПО обрабатывало изображения, и когда не было зарегистрированно, оно добавляло водяные знаки, вроде "This image was processed by evaluation version of [software name]", поперек картинки. Мы попробовали от балды: нашли эту строку в исполняемом файле и забили пробелами. Водяные знаки пропали. Технически, они продолжали добавляться. При помощи соответствующих ф-ций Qt, надпись продолжала добавляться в итоговое изображение. Но добавление пробелов не меняло само изображение...

Л о к а л и з а ц и я ПО во вре м ен а MS-DOS Описанный способ был очень распространен для перевода ПО под MS-DOS на русский язык в 1980-е и 1990-е. Русские слова и предложения обычно немного длиннее английских, т а к что локализован­ ное ПО содержало массу странных акронимов и труднопонятны х сокращений.

Рис. 1.3: Русифицированный Norton Commander 5.51 Вероятно, т а к было и с другими языками в других странах.

1.5.2. x86-64 MSVC: x 8 6 -6 4 Попробуем т а кж е 64-битный MSVC:

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

15

Листинг 1.21: MSVC 2012 x64 00 CN О СЛ main

main

DB

'h e llo , w o rld ', 0AH, 00H

PROC sub lea c a ll xor add ret ENDP

rsp, 40 rcx, OFFSET FLAT: $SG2989 p r in tf eax, eax rsp, 40 0

В x86-64 все регистры были расширены до 64-х бит и теперь имеют префикс R-. Чтобы поменьше задействовать стек (иными словами, поменьше обращаться к кэшу и внешней памяти), уж е давно имелся довольно популярный метод передачи аргументов функции через регистры (fastcall) 6.1.3 (стр. 727). Т.е. часть аргументов ф ункции передается через регистры и часть —через стек. В Win64 первые 4 аргумента ф ункции передаются через регистры RCX, RDX, R8, R9. Это мы здесь и видим: указатель на строку в p r i n t f ( ) теперь передается не через стек, а через регистр RCX. Указатели теперь 64-битные, т а к что они передаются через 64-битные части регистров (имеющие префикс R-). Но для обратной совместимости можно обращаться и к нижним 32 битам регистров используя префикс E-. Вот ка к выглядит регистр RAX/EAX/AX/AL в x86-64:

-й 0

-й 1

Номер байта: 4-й 3-й RAXx64

-й 2

-й 6

-й 7

5-й

EAX AX AH AL Функция m ain() возвращает значение типа int, который в Си/Си++, надо полагать, для лучшей сов­ местимости и переносимости, оставили 32-битным. Вот почему в конце ф ункции m ain() обнуляет­ ся не RAX, а EAX, т.е. 32-битная часть регистра. Такж е видно, что 40 байт выделяются в локальном стеке. Это «shadow space» которое мы будем рассматривать позже: 1.10.2 (стр. 100).

GCC: x 8 6 -6 4 Попробуем GCC в 64-битном Linux: Листинг 1.22: GCC 4.4.6 x64 . s trin g "h e llo , main: sub mov xor c a ll xor add ret

world\n" rsp, 8 edi, OFFSET FLAT: . LC0 ; "h e llo , world\n" eax, eax ; количество переданных векторных регистров p r in tf eax, eax rsp, 8

В Linux, *BSD и Mac OS X для x86-64 т а кж е принят способ передачи аргументов ф ункции через ре­ гистры [Michael Matz, Jan Hubicka, Andreas Jaeger, Mark Mitchell, System VApplication Binary Interface. AMD64 Architecture Processor Supplement, (2013)] 24. 6 первых аргументов передаются через регистры RDI, RSI, RDX, RCX, R8, R9, а остальные — через стек. Так что указатель на строку передается через EDI (32-битную часть регистра). Но почему не через 64-битную часть, RDI? Важно запомнить, что в 64-битном режиме все инструкции MOV, записывающие что-либо в младшую 32-битную часть регистра, обнуляют старшие 32-бита (это можно найти в документации от Intel: 11.1.4 (стр. 974)). То есть, инструкция MOV EAX, 011223344h корректно запишет это значение в RAX, старшие биты сбросятся в ноль. 24Т акж е доступно здесь: h t t p s : / / s o f t w a r e . i n t e l . c o m / s i t e s / d e f a u l t / f i l e s / a r t i c l e / 4 0 2 1 2 9 / m p x - l i n u x 6 4 - a b i . p d f

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

16

Если посмотреть в IDA скомпилированный объектный файл (.о), увидим т а к ж е опкоды всех инструкций 25 Листинг 1.23: GCC 4.4.6 x64 . te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t

00000000004004D0 00000000004004D0 00000000004004D4 00000000004004D9 00000000004004DB 00000000004004E0 00000000004004E2 00000000004004E6 00000000004004E6

48 BF 31 E8 31 48 C3

83 E8 C0 D8 C0 83

EC 08 05 40 00 FE FF FF C4 08

main sub mov xor c a ll xor add retn main

proc near rsp, 8 edi, o f fs e t format ; "h e llo , world\n" eax, eax _ p r in t f eax, eax rsp, 8 endp

Как видно, инструкция, записывающая в EDI по адресу 0x4004D4, занимает 5 байт. Та ж е и нструк­ ция, записывающая 64-битное значение в RDI, занимает 7 байт. Возможно, GCC решил немного сэкономить. К тому же, вероятно, он уверен, что сегмент данных, где хранится строка, никогда не будет расположен в адресах выше 4GiB. Здесь мы т а кж е видим обнуление регистра EAX перед вызовом p r i n t f ( ) . Это делается потому что по упомянутому выше стандарту передачи аргументов в *NIX для x86-64 в EAX передается количество задействованных векторных регистров.

К о р р е к ц и я ( п а т ч и н г ) а д р е с а (W in 6 4 ) Если наш пример скомпилирован в MSVC 2013 используя опцию \MD (подразумевая меньший ис­ полняемый файл из-за внешнего связывания файла MSVCR*.DLL), ф-ция m ain() идет первой, и её легко найти: Hiew: hw2.exe

BFWO EDITMODE a64 PE+ 00000000'00000404 Hiew 8.02 (c)SEN sub r s p , 028 ; ' ( 1 le a r c x , [000002400] c a ll q,[0000014E8] xor eax,eax add r s p , 028 ; re tn sub r s p , 028 - '( ■ mov eax,000005A4D ZM' cmp [-000000C00],ax 00000042E jz w■ I > l > 1 > 1 > 1 I> > l > 1 > 1 > 1 I> l> > 1 > 1 > 1 > I

C:\tm p\hw2.exe 00000400: 4883EC28 00000404: 488D0DF51F0000 0000040B: FF15D7100000 00000411: 33C0 00000413: 4883C428 00000417: C3 00000418: 4883EC28 0000041C: B84D5A0000 00000421: 663905D8EFFFFF 00000428: 7404 00000

0Ѳ0ѲѲ 00000 00000 00000 0000043F 00000445: 00000447: 0000044С: 00000450: 00000452: 00000454: 0000045В: 0000045D: 1 2f

r c x , [00000000000002401] CommandSelect: O ff 3 ■' E 00000042A ecx,00000020B [ r a x ] [ 0 1 8 ],c x ѲѲ0Ѳ0042А ecx,ecx d , [ r a x ] [00Ѳ000084], 00E 000000466 [ ra x ] [0 0 0 0000F8], ecx 9 10

813850450000 75E3 B90B020000 66394818 75D8 33C9 83B8840000000E 7609 3988F8000000 1 :fl I £

Рис. 1.4: Hiew 25Это нужно задать в O p tio n s ^ D is a s s e m b ly ^ N u m b e r o f o p c o d e b y te s

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

17

В качестве эксперимента, мы можем инкрементировать адрес на 1: - |П |х |

S3 Hiew: hw2.exe

1 > 1 > 1 > 1 > 1 1> > 1 > 1 > 1 > 1 1> > 1 > 1 > 1 > 1 >

ь■

C :\tm p\hw2.exe 0FUO ------- ----- а 64 PE+.00000001'4Ѳ00100В Hiew 8.02 (c)SEN .40001000: 4883ЕС28 sub rsp, 028 i ’ < 1 .40001004: 488D0DF61F0000 lea г с х , [00000001 40003001] ;’e l l o , w .4000100В: H3l5D7100000 c a ll p r in tf .40001011: 33C0 ко г e a x ,e a x .40001013: 4S83C428 add rsp, 028 - C .40001017: СЗ retn .40001018: 4883ЕС28 sub rsp, 028 ; ■ 5 40059c 91192000 add x0, x0, #0x648 6 4005a0 9 7 ffffa 0 bl 400420 7 4005a4 52800000 mov w0, #0x0 / / #0 8 4005a8 a8c17bfd Idp x29, x30, [sp],#16 9 4005ac d65f03c0 ret 10 11 12 13 Contents o f section . rodata: 14 400640 01000200 00000000 48656c6c 6f210a00 ..............H e llo !. . В ARM64 нет режима Thumb и Thumb-2, только ARM, т а к что тут только 32-битные инструкции. Регистров тут в 2 раза больше: .2.4 (стр. 1002). 64-битные регистры теперь имеют префикс X-, а их 32-битные части — W-. Инструкция STP (Store Pair) сохраняет в стеке сразу два регистра: X29 и X30. Конечно, эта и нструк­ ция может сохранять эту пару где угодно в памяти, но здесь указан регистр SP, т а к что пара сохраняется именно в стеке. Регистры в ARM64 64-битные, каж ды й имеет длину в 8 байт, т а к что для хранения двух регистров нужно именно 16 байт. Восклицательный знак ("!") после операнда означает, что сначала от SP будет отнято 16 и только затем значения из пары регистров будут записаны в стек. Это называется pre-index. Больше о разнице между post-index и pre-index описано здесь: 1.32.2 (стр. 440). Таким образом, в терминах более знакомого всем процессора x86, первая инструкция — это просто аналог пары инструкций PUSH X29 и PUSH X30. X29 в ARM64 используется ка к FP41, а X30 ка к LR, поэтому они сохраняются в прологе ф ункции и восстанавливаются в эпилоге. Вторая инструкция копирует SP в X29 (или FP). Это нужно для установки стекового фрейма ф унк­ ции. Инструкции ADRP и ADD нужны для формирования адреса строки «Hello!» в регистре X0, ведь первый аргумент ф ункции передается через этот регистр. Но в ARM нет инструкций, при помощи которых можно записать в регистр длинное число (потому что сама длина инструкции ограничена 4-я бай­ тами. Больше об этом здесь: 1.32.3 (стр. 441)). Так что нуж но использовать несколько инструкций. Первая инструкция (ADRP) записывает в X0 адрес 4-килобайтной страницы где находится строка, а вторая (ADD) просто прибавляет к этому адресу остаток. Читайте больше об этом: 1.32.4 (стр. 443). 0x400000 + 0x648 = 0x400648, и мы видим, что в секции данных .ro d a ta по этому адресу ка к раз находится наша Си-строка «Hello!». Затем при помощи инструкции BL вызывается p u t s ( ) . Это уж е рассматривалось ранее: 1.5.3 (стр. 21). Инструкция MOV записывает 0 в W0. W0 это младшие 32 бита 64-битного регистра X0: Старшие 32 бита

младшие 32 бита X0 W0

41Frame Pointer

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

24

А результат ф ункции возвращается через X0, и m ain() возвращает 0, т а к что вот т а к готовится возвращаемый результат. Почему именно 32-битная часть? Потому что в ARM64, ка к и в x86-64, тип int оставили 32-битным, для лучшей совместимости. Следовательно, раз у ж функция возвращает 32-битный int, то нужно заполнить только 32 младших бита регистра X0. Для того, чтобы удостовериться в этом, немного отредактируем этот пример и перекомпилируем его. Теперь m ain() возвращает 64-битное значение: Листинг 1.29: m ain() возвращающая значение типа u in t 6 4 _ t #include #include < std in t.h > uint64_t main() { p r i n t f ( " H e llo !\n " ) ; return 0; } Результат точно такой же, только MOV в той строке теперь выглядит так: Листинг 1.30: НеоптимизирующийGCC 4.8.1 + objdump 4005a4:

d2800000

mov

x0, #0x0

/ / #0

Далее при помощи инструкции LDP (Load Pair) восстанавливаются регистры X29 и X30. Восклицательного знака после инструкции нет. Это означает, что сначала значения достаются из стека, и только потом SP увеличивается на 16. Это называется post-index. В ARM64 есть новая инструкция: RET. Она работает та к ж е ка к и BX LR, но там добавлен специ­ альный бит, подсказывающий процессору, что это именно выход из функции, а не просто переход, чтобы процессор мог более оптимально исполнять эту инструкцию. Из-за простоты этой ф ункции оптимизирующ ий GCC генерирует точно такой ж е код.

1.5.4. MIPS О « гл о б а л ь н о м у к а з а т е л е » (« g lo b a l p o in te r » ) «Глобальный указатель» («global pointer») — это важная концепция в MIPS. Как мы уж е возможно знаем, каждая инструкция в MIPS имеет размер 32 бита, поэтому невозможно закодировать 32­ битный адрес внутри одной инструкции. Вместо этого нужно использовать пару инструкций (как это сделал GCC для загрузки адреса текстовой строки в нашем примере). С другой стороны, ис­ пользуя только одну инструкцию, возможно загруж ать данные по адресам в пределах register 32768...register + 32767, потому что 16 бит знакового смещения можно закодировать в одной и нструк­ ции). Так мы можем выделить какой-то регистр для этих целей и ещё выделить буфер в 64KiB для самых часто используемых данных. Выделенный регистр называется «глобальный указатель» («global pointer») и он указывает на середину области 64KiB. Эта область обычно содерж ит глобаль­ ные переменные и адреса импортированных ф ункций вроде p r i n t f ( ) , потому что разработчики GCC решили, что получение адреса функции должно быть ка к можно более быстрой операцией, ис­ полняющейся за одну инструкцию вместо двух. В ELF-файле эта 6 4 ^ - о б л а с т ь находится частично в секции .sbss («small BSS42») для неинициализированных данных и в секции .sdata («small data») для инициализированных данных. Это значит что программист может выбирать, к чему нужен ка к можно более быстрый доступ, и затем расположить это в секциях .sdata/.sbss. Некоторые програм­ мисты «старой школы» могут вспомнить модель памяти в MS-DOS 10.6 (стр. 964) или в менеджерах памяти вроде XMS/EMS, где вся память делилась на блоки по 64KiB. Эта концепция применяется не только в MIPS. По крайней мере PowerPC т а кж е использует эту технику. 42Block Started by Symbol

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

25

О п т и м и зи р у ю щ и й G C C Рассмотрим следующий пример, иллюстрирующий концепцию «глобального указателя». Листинг 1.31: ОптимизирующийGCC 4.4.5 (вывод на ассемблере)

0 v$ в or Ne $ зи

1 $LC0: 2 ; \000 это ноль в восьмеричной системе: 3 . a s c ii "Hello, world!\012\000" 4 main: 5 ; пролог функции 6 ; установить GP: 7 lu i $28,%hi(__gnu_local_gp) 8 addiu $sp,$sp,-32 9 addiu $28,$28,%lo(__gnu_local_gp) 10 ; сохранить RA в локальном стеке: 11 sw $31,28($sp) 12 ; загрузить адрес функции puts() из GP в $25: 13 lw $25,%call16(puts)($28) 14 ; загрузить адрес текстовой строки в $4 ($a0): 15 lu i $4,%hi($LC0) 16 ; перейти на p u ts (), сохранив адрес возврата в l i n k -регистре: 17 ja lr $25 18 addiu $4,$4,%lo($LC0) ; branch delay s lo t 19 ; восстановить RA: 20 lw $31,28($sp) 21 ; скопировать 0 22 move $2, $0 23 ; вернуть управление сделав переход по адресу в RA: 24 $31 j 25 ; эпилог функции 26 addiu $sp,$sp,32 ; branch delay s lo t + освободить стек от локальных переменных Как видно, регистр $GP в прологе ф ункции выставляется в середину этой области. Регистр RA со­ храняется в локальном стеке. Здесь т а к ж е используется p u t s ( ) вместо p r i n t f ( ) . Адрес функции p u t s ( ) загружается в $25 инструкцией LW («Load Word»). Затем адрес текстовой строки загр уж а­ ется в $4 парой инструкций LUI («Load Upper Immediate») и ADDIU («Add Immediate Unsigned Word»). LUI устанавливает старшие 16 бит регистра (поэтому в имени инструкции присутствует «upper») и ADDIU прибавляет младшие 16 бит к адресу. ADDIU следует за JALR (помните о branch delay slots?). Регистр $4 т а кж е называется $A0, который используется для передачи первого аргумента ф унк­ ции 43. JALR («Jump and Link Register») делает переход по адресу в регистре $25 (там адрес p u t s ( ) ) при этом сохраняя адрес следующей инструкции (LW) в RA. Это та к ж е ка к и в ARM. И ещё одна важная вещь: адрес сохраняемый в RA это адрес не следующей инструкции (потому что это delay slot и исполняется перед инструкцией перехода), а инструкции после неё (после delay slot). Таким образом во время исполнения JALR в RA записывается P C +8. В нашем случае это адрес инструкции LW следующей после ADDIU. LW («Load Word») в строке 20 восстанавливает RA из локального стека (эта инструкция скорее часть эпилога функции). MOVE в строке 22 копирует значение из регистра $0 ($ZERO) в $2 ($V0). В MIPS есть константный регистр, всегда содержащий ноль. Должно быть, разработчики MIPS ре­ шили, что 0 это самая востребованная константа в программировании, т а к что пусть будет ис­ пользоваться регистр $0, всякий раз, когда будет нужен 0. Другой интересный факт: в MIPS нет инструкции, копирующей значения из регистра в регистр. На самом деле, MOVE DST, SRC это ADD DST, SRC, $ZERO (DST = S R C +0), которая делает тож е самое. Очевидно, разработчики MIPS хотели сделать ка к можно более компактную таблицу опкодов. Это не значит, что сложение происходит во время каж д ой инструкции MOVE. Скорее всего, эти псевдоинструкции оптимизируются в CPU и АЛУ44 никогда не используется. J в строке 24 делает переход по адресу в RA, и это работает ка к выход из функции. ADDIU после J на самом деле исполняется перед J (помните о branch delay slots?) и это часть эпилога функции. Вот листинг сгенерированный IDA. Каждый регистр имеет свой псевдоним: Листинг 1.32: ОптимизирующийGCC 4.4.5 (IDA) 43Таблица регистров в MIPS доступна в приложении .3.1 (стр. 1003) 44Арифметико-логическое устройство

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

26

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

.text:00000000 main: . t e x t :00000000 .text:00000000 var_10 = -0x10 .text:00000000 var_4 = -4 . text:00000000 ; пролог функции ; установить GP: . text:00000000 lu i $gp, ( gnu lo c a l gp >> 16) . text:00000004 addiu $sp, -0x20 . text:00000008 la $gp, (__gnu_local_gp & 0xFFFF) ; сохранить RA в локальном стеке: . text:0000000C sw $ra, 0x20+var_4($sp) ; сохранить GP в локальном стеке: ; по какой-то причине, этой инструкции не было в ассемблерном выводе в GCC: . t e x t :00000010 sw $gp, 0x20+var_10($sp) ; загрузить адрес функции puts () из GP в $t9 . t e x t :00000014 lw $ t9 , (puts & 0xFFFF)($gp) ; сформировать адрес текстовой строки в $a0: . t e x t :00000018 lu i $a0, ( $LC0 >>16) # "Hello, w o rld!" ; перейти на p u ts (), сохранив адрес возврата в l i n k -регистре: . text:0000001C ja lr $t9 . t e x t :00000020 la $a0, ( $LC0 & 0xFFFF) # "Hello, w o rld!" ; восстановить RA: . t e x t :00000024 lw $ra, 0x20+var_4($sp) ; скопировать 0 из $zero в $v0 . t e x t :00000028 move $v0, $zero ; вернуть управление сделав переход по адресу в RA: . text:0000002C $ra jr ; эпилог функции: . t e x t :00000030 addiu $sp, 0x20 Инструкция в строке 15 сохраняет GP в локальном стеке. Эта инструкция мистическим образом отсутствует в листинге от GCC, может быть из-за ошибки в самом GCC45. Значение GP должно быть сохранено, потому что всякая функция может работать со своим собственным окном данных размером 64KiB. Регистр, содержащ ий адрес функции p u t s ( ) называется $T9, потому что регистры с префиксом T- называются «temporaries» и их содержимое можно не сохранять.

Неоптимизирую щ ийG C C НеоптимизирующийGCC более многословный. Листинг 1.33: НеоптимизирующийGCC 4.4.5 (вывод на ассемблере) 1 $LC0: 2 . a s c ii "Hello, world!\012\000" 3 main: 4 ; пролог функции 5 ; сохранить RA ($31) и FP в стеке: 6 addiu $sp,$sp,-32 7 sw $31,28($sp) 8 sw $fp,24($sp) 9 ; установить FP (указатель стекового фрейма): 10 move $fp,$sp 11 ; установить GP: 12 lu i $28,%hi(__gnu_local_gp) 13 addiu $28,$28,%lo(__gnu_local_gp) 14 ; загрузить адрес текстовой строки: 15 lu i $2,%hi($LC0) 16 addiu $4,$2,%lo( $LC0) 17 ; загрузить адрес функции puts() используя GP: 18 lw $2,%call16(puts)($28) 19 nop 20 ; вызвать p u ts(): 21 move $25,$2 22 ja lr $25 23 nop ; branch delay s lo t 45Очевидно, функция вывода листингов не т а к критична для пользователей GCC, поэтому там вполне могут быть неис­ правленные косметические ошибки.

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

27

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

; восстановить GP из локального стека: lw $28,16($fp) ; установить регистр $2 ($V0) в ноль: move $2,$0 ; эпилог функции. ; восстановить SP: move $sp,$fp ; восстановить RA: lw $31,28($sp) ; восстановить FP: lw $fp,24($sp) addiu $sp,$sp,32 ; переход на RA: j $31 nop ; branch delay s lo t Мы видим, что регистр FP используется ка к указатель на фрейм стека. Мы т а кж е видим 3 NOP-а. Второй и третий следуют за инструкциями перехода. Видимо, компилятор GCC всегда добавляет NOP-ы (из-за branch delay slots) после инструкций переходов и затем, если включена оптимизация, от них может избавляться. Так что они остались здесь. Вот т а кж е листинг от IDA: Листинг 1.34: НеоптимизирующийGCC 4.4.5 (IDA)

0 V $

.text:00000000 main: . t e x t :00000000 .text:00000000 var_10 = -0x10 . text:00000000 var_8 = -8 .text:00000000 var_4 = -4 . text:00000000 ; пролог функции ; сохранить RA и FP в стеке: . text:00000000 addiu $sp, -0x20 sw $ra, 0x20+var_4($sp) . text:00000004 . text:00000008 sw $fp, 0x20+var 8($sp) ; установить FP (указатель стекового фрейма ): . text:0000000C move $fp, $sp ; установить GP: . text:00000010 la $gp, __gnu_local_gp . text:00000018 sw $gp, 0x20+var_10($sp) ; загрузить адрес текстовой строки: . text:0000001C lu i $v0, (aHelloWorld >> 16) # "H ello, w o rld!" . text:00000020 addiu $a0, $v0, (aHelloWorld & 0xFFFF) # "H ello, w o rld!" ; загрузить адрес функции puts () используя GP: lw $v0, (puts & 0xFFFF)($gp) . text:00000024 . text:00000028 or $at, $zero ; NOP ; вызвать p u ts(): . text:0000002C move $ t9 , $v0 . text:00000030 ja lr $t9 . text:00000034 or $at, $zero ; NOP ; восстановить GP из локального стека: . text:00000038 lw $gp, 0x20+var_10( $fp) ; установить регистр в ноль: . text:0000003C move $v0, $zero ; эпилог функции. ; восстановить SP: . text:00000040 move $sp, $fp ; восстановить RA: . text:00000044 lw $ra, 0x20+var_4($sp) ; восстановить FP: . text:00000048 lw $fp, 0x20+var 8($sp) . text:0000004C addiu $sp, 0x20 ; переход на RA: . text:00000050 $ra jr . text:00000054 or $at, $zero ; NOP 2 $

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

Интересно что IDA распознала пару инструкций LUI/ADDIU и собрала их в одну псевдоинструкцию LA («Load Address») в строке 15. Мы т а кж е видим, что размер этой псевдоинструкции 8 байт! Это (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

28

псевдоинструкция (или макрос), потому что это не настоящая инструкция MIPS, а скорее просто удобное имя для пары инструкций. Ещё кое что: IDA не распознала NOP-инструкции в строках 22, 26 и 41. Это OR $AT, $ZERO. По своей сути это инструкция, применяющая операцию ИЛИ к содержимому регистра $AT с нулем, что, конечно же, холостая операция. MIPS, ка к и многие другие ISA, не имеет отдельной NOP-инструкции.

Роль с т е к о в о г о ф р е й м а в этом п р и м е р е Адрес текстовой строки передается в регистре. Так зачем устанавливать локальный стек? Причина в том, что значения регистров RA и G p долж ны быть сохранены где-то (потому что вызывается p r i n t f ( ) ) и для этого используется локальный стек. Если бы это была leaf function, тогда можно было бы избавиться от пролога и эпилога функции. Например: 1.4.3 (стр. 8).

О п т и м и зи р у ю щ и й G C C : з а гр у з и м в GDB Листинг 1.35: пример сессии в GDB root@debian-mips:~# gcc hw.c -O3 -o hw root@debian-mips:~# gdb hw GNU gdb (GDB) 7.0.1-debian Reading symbols from /ro o t/h w ...( n o debugging symbols found)...done. (gdb) b main Breakpoint 1 at 0x400654 (gdb) run S ta rtin g program: /root/hw Breakpoint 1, 0x00400654 in main () (gdb) set step-mode on (gdb) disas Dump of assembler code fo r function main: 0x00400640 : lu i gp 0x42 0x00400644 : addiu sp sp, -32 0x00400648 : addiu gp gp, -30624 0x0040064c : sw ra 28(sp) 0x00400650 : sw gp , 16(sp) 0x00400654 : lw t9 ,-32716(gp) 0x00400658 : lu i a0 0x40 0x0040065c : ja lr t9 0x00400660 : addiu a0 a0,2080 0x00400664 : lw ra 28(sp) 0x00400668 : move v0 , zero 0x0040066c : jr ra 0x00400670 : addiu sp ,sp,32 End of assembler dump. (gdb) s 0x00400658 in main () (gdb) s 0x0040065c in main () (gdb) s 0x2ab2de60 in p r i n t f () from / l i b / l i b c . s o . 6 (gdb) x/s $a0 0x400820: "h e llo , world" (gdb)

1.5.5. Вывод Основная разница между кодом x86/ARM и x64/ARM64 в том, что указатель на строку теперь 64­ битный. Действительно, ведь для того современные CPU и стали 64-битными, потому что подеше­ (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

29

вела память, её теперь можно поставить в компьютер намного больше, и чтобы её адресовать, 32-х бит уж е недостаточно. Поэтому все указатели теперь 64-битные.

1.5.6. Упражнения • h ttp ://c h a lle n g e s .r e /4 8 • h ttp ://c h a lle n g e s .r e /4 9

1.6. Пролог и эпилог функций Пролог ф ункции это инструкции в самом начале функции. Как правило, это что-то вроде такого фрагмента кода: push mov sub

ebp ebp, esp esp, X

Эти инструкции делают следующее: сохраняют значение регистра EBP на будущее, выставляют EBP равным ESP, затем подготавливают место в стеке для хранения локальных переменных. EBP сохраняет свое значение на протяжении всей функции, он будет использоваться здесь для до­ ступа к локальным переменным и аргументам. Можно было бы использовать и ESP, но он постоянно меняется и это не очень удобно. Эпилог ф ункции аннулирует выделенное место в стеке, восстанавливает значение EBP на старое и возвращает управление в вызывающую функцию: mov pop ret

esp, ebp ebp 0

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

1.6.1. Рекурсия Наличие эпилога и пролога может несколько ухудш ить эффективность рекурсии. Больше о рекурсии в этой книге: 3.5.3 (стр. 484).

1.7. Стек Стек в компьютерных науках — это одна из наиболее фундаментальных структур данных 46. AKA47 LIFO48. Технически это просто блок памяти в памяти процесса + регистр ESP в x86 или RSP в x64, либо SP в ARM, который указывает где-то в пределах этого блока. Часто используемые инструкции для работы со стеком — это PUSH и POP (в x86 и Thumb-режиме ARM). PUSH уменьшает ESP/RSP/SP на 4 в 32-битном режиме (или на 8 в 64-битном), затем записывает по адресу, на который указывает ESP/RSP/SP, содержимое своего единственного операнда. POP это обратная операция — сначала достает из указателя стека значение и помещает его в операнд (который очень часто является регистром) и затем увеличивает указатель стека на 4 (или 8). 46wikipedia.org/wiki/Call_stack 47 - (Также известный как) 48Last In First Out (последним вошел, первым вышел)

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

30

В самом начале регистр-указатель указывает на конец стека. Конец стека находится в начале блока памяти, выделенного под стек. Это странно, но это так. PUSH уменьшает регистр-указатель, а POP — увеличивает. В процессоре ARM, тем не менее, есть поддержка стеков, растущих ка к в сторону уменьшения, так и в сторону увеличения. Например, инструкции STMFD/ LDMFD, STMED49/ LDMED50 предназначены для descending-стека (рас­ тет назад, начиная с высоких адресов в сторону низких). Инструкции STMFA51/ LDMFA52, STMEA53/ LDMEA54 предназначены для ascending-стека (растет впе­ ред, начиная с низких адресов в сторону высоких).

1.7.1. Почему стек растет в обратную сторону? Интуитивно мы можем подумать, что, ка к и любая другая структура данных, стек мог бы расти вперед, т.е. в сторону увеличения адресов. Причина, почему стек растет назад, видимо, историческая. Когда компьютеры были большие и занимали целую комнату, было очень легко разделить сегмент на две части: для кучи и для стека. Заранее было неизвестно, насколько большой может быть куча или стек, т а к что это решение было самым простым. Начало кучи

Вершина стека

В [D. M. Ritchie and K. Thompson, The UNIX Time Sharing System, (1974)]55можно прочитать: The user-core part of an image is divided into three logical segments. The program text segment begins at location 0 in the virtual address space. During execution, this segment is write-protected and a single copy of it is shared among all processes executing the same program. At the first 8K byte boundary above the program te xt segment in the virtual address space begins a nonshared, writable data segment, the size of which may be extended by a system call. Starting at the highest address in the virtual address space is a stack segment, which automatically grows downward as the hardware's stack pointer fluctuates. Это немного напоминает ка к некоторые студенты пиш ут два конспекта в одной тетрадке: первый конспект начинается обычным образом, второй пишется с конца, перевернув тетрадку. Конспекты могут встретиться где-то посредине, в случае недостатка свободного места.

1.7.2. Для чего используется стек? С о х р а н е н и е а д р е с а в о звр а та у п р а в л е н и я x86 При вызове другой ф ункции через CALL сначала в стек записывается адрес, указывающий на место после инструкции CALL, затем делается безусловный переход (почти ка к JMP) на адрес, указанный в операнде. CALL — это аналог пары инструкций PUSH a d d r e s s _ a f t e r _ c a ll / JMP. 49Store Multiple Empty Descending (инструкция ARM) 50Load Multiple Empty Descending (инструкция ARM) 51Store Multiple Full Ascending (инструкция ARM) 52Load Multiple Full Ascending (инструкция ARM) 53Store Multiple Empty Ascending (инструкция ARM) 54Load Multiple Empty Ascending (инструкция ARM) 55Т акж е доступно здесь: h t t p : / / g o . y u r ic h e v . c o m / 1 7 2 7 0

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

31

RET вытаскивает из стека значение и передает управление по этому адресу — это аналог пары инструкций POP tmp / JMP tmp. Крайне легко устроить переполнение стека, запустив бесконечную рекурсию: void f ( ) { f(); }; MSVC 2008 предупреждает о проблеме: c:\tmp6>cl ss.cpp /Fass.asm M icrosoft (R) 3 2 -b it C/C++ Optimizing Compiler Version 15.00.21022.08 fo r 80x86 Copyright (C) M icrosoft Corporation. A l l rig h ts reserved. ss.cpp c:\tmp6\ss.cpp(4) : warning C4717: ' f ' 4 runtime stack overflow

: recursive on a l l co n tro l paths, function w i l l cause /

...но, тем не менее, создает нужны й код: ?f@@YAXXZ PROC ; Line 2 push mov ; Line 3 c a ll ; Line 4 pop ret ?f@@YAXXZ ENDP

; f ebp ebp, esp ?f@@YAXXZ

; f

ebp 0 ; f

...причем, если включить оптимизацию (/Ox), то будет даже интереснее, без переполнения стека, но работать будет корректно56: ?f@@YAXXZ PROC ; Line 2 $LL3@f: ; Line 3 jmp SHORT $LL3@f ?f@@YAXXZ ENDP

; f

; f

GCC 4.4.1 генерирует точно такой ж е код в обоих случаях, хотя и не предупреждает о проблеме.

ARM Программы для ARM т а кж е используют стек для сохранения RA, куда нужно вернуться, но несколь­ ко иначе. Как уж е упоминалось в секции «Hello, world!» (1.5.3 (стр. 18)), RA записывается в регистр LR (link register). Но если есть необходимость вызывать какую -то другую ф ункцию и использовать регистр LR ещё раз, его значение желательно сохранить. Обычно это происходит в прологе функции, часто мы видим там инструкцию вроде PUSH {R4-R7,LR}, а в эпилоге POP {R4-R7,PC} — т а к сохраняются регистры, которые будут использоваться в текущ ей функции, в том числе LR. Тем не менее, если некая функция не вызывает никаких более функций, в терминологии RISC она называется leaf function57. Как следствие, « le a ^ -функция не сохраняет регистр LR (потому что не изменяет его). А если эта функция небольшая, использует мало регистров, она может не исполь­ зовать стек вообще. Таким образом, в ARM возможен вызов небольших leaf-функций не используя стек. Это может быть быстрее чем в старых x86, ведь внешняя память для стека не используется 58. Либо это может быть полезным для тех ситуаций, когда память для стека ещё не выделена, либо недоступна, 56здесь ирония 57infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka13785.html 58Когда-то, очень давно, на PDP-11 и VAX на инструкцию CALL (вызов других функций) могло тратиться вплоть до 50% времени (возможно из-за работы с памятью), поэтому считалось, что много небольших функций это анти-паттерн [Eric S. Raymond, The A r t o f UNIX Prog ram m in g, (2003)Chapter 4, Part II].

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

32

Некоторые примеры таки х функций: 1.10.3 (стр. 103), 1.10.3 (стр. 103), 1.277 (стр. 317), 1.293 (стр. 335), 1.23.5 (стр. 335), 1.187 (стр. 211), 1.185 (стр. 209), 1.204 (стр. 227).

Передача параметров ф ункц и и Самый распространенный способ передачи параметров в x86 называется «cdecl»: push arg3 push arg2 push arg1 c a ll f add esp, 12 ; 4*3=12 Вызываемая ф ункция получает свои параметры т а к ж е через указатель стека. Следовательно, т а к расположены значения в стеке перед исполнением самой первой инструкции функции f ( ) : ESP ESP+4 ESP+8 ESP+0xC

адрес возврата аргум ент#1, маркируется в IDA ка к arg_0 аргум ент#2, маркируется в IDA ка к arg_4 аргум ент#3, маркируется в IDA ка к arg_8

См. т а кж е в соответствующем разделе о других способах передачи аргументов через стек (6.1 (стр. 726)). Кстати, вызываемая функция не имеет информации о количестве переданных ей аргументов. Ф у н к­ ции Си с переменным количеством аргументов (как p r i n t f ( ) ) определяют их количество по спе­ цификаторам строки формата (начинающиеся со знака %). Если написать что-то вроде: printf("% d %d %d", 1234); p r i n t f ( ) выведет 1234, затем ещё два случайных числа59, которые волею случая оказались в стеке рядом. Вот почему не та к у ж и важно, ка к объявлять ф ункцию m a in ( ) : ка к m a in (), m a in ( in t a rg c , char * a r g v [ ] ) либо m a in ( in t a rg c , char * a r g v [ ] , char * e n v p [ ] ) . В реальности, CRT-код вызывает m ain() примерно так: push push push c a ll

envp argv argc main

Если вы объявляете m ain() без аргументов, они, тем не менее, присутствуют в стеке, но не исполь­ зуются. Если вы объявите m ain() ка к m a in ( in t a rg c , char * a r g v [ ] ) , вы можете использовать два первых аргумента, а третий останется для вашей ф ункции «невидимым». Более того, можно даже объявить m a in ( in t a rg c ), и это будет работать.

А л ьтерн ативн ы е способы передачи аргум ентов Важно отметить, что, в общем, никто не заставляет программистов передавать параметры именно через стек, это не является требованием к исполняемому коду. Вы можете делать это совершенно иначе, не используя стек вообще. В каком-то смысле, популярный метод среди начинающих использовать язык ассемблера, это пе­ редавать аргументы в глобальных переменных, например: 59В строгом смысле, они не случайны, скорее, непредсказуемы: 1.7.4 (стр. 37)

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

33

Листинг 1.36: Код на ассемблере

Но у этого метода есть очевидный недостаток: ф-ция do_something() не сможет вызвать саму себя рекурсивно (либо, через какую -то стороннюю ф-цию), потому что тогда придется затереть свои собственные аргументы. Та ж е история с локальными переменными: если хранить их в глобаль­ ных переменных, ф-ция не см ожет вызывать сама себя. К тому же, этот метод не безопасный для мультитредовой среды60. Способ хранения подобной информации в стеке заметно всё упрощает — он может хранить столько аргументов ф-ций и/или значений вообще, сколько в нем есть места. В [Donald E. Knuth, The Art of Computer Programming, Volume 1, 3rd ed., (1997), 189] можно прочи­ тать про еще более странные схемы передачи аргументов, которые были очень удобны на IBM System/360. В MS-DOS был метод передачи аргументов через регистры, например, этот фрагмент кода для древней 16-битной MS-DOS выводит "Hello, world!": mov mov in t

dx, msg ah, 9 21h

; адрес сообщения ; 9 означает ф-цию "вывод строки" ; DOS "s y s c a ll"

mov in t

ah, 4ch 21h

; ф-ция "закончить программу" ; DOS "s y s c a ll"

msg

db 'H ello, World! \ $ '

Это очень похоже на метод 6.1.3 (стр. 727). И еще на метод вызовов сисколлов в Linux (6.3.1 (стр. 740)) и Windows. Если ф-ция в MS-DOS возвращает булево значение (т.е., один бит, обычно сигнализирующ ий об ошибке), часто использовался флаг CF. Например: mov lea mov in t jc mov

ah, 3ch ; создать файл dx, filename c l, 1 21h erro r f i l e _handle, ax

erro r:

В случае ошибки, флаг CF будет выставлен. Иначе, хэндл только что созданного файла возвраща­ ется в AX. Этот метод до сих пор используется программистами на ассемблере. В исходных кодах Windows Research Kernel (который очень похож на Windows 2003) мы можем найти такое (файл base/ntos/ke/i386/cpu.asm): 60При корректной реализации, кажды й тред будет иметь свой собственный стек со своими аргументами/переменными.

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

34

public Get386Stepping

Get386Stepping proc

c a ll jnc mov ret

M ultiplyTest short G3s00 ax, 0

Perform m u ltip lic a tio n te s t i f nc, muttest is ok

c a ll jnc mov ret

Check386B0 short G3s05 ax, 100h

Check fo r B0 stepping i f nc, i t ' s B 1 /la te r I t is B 0 /e a rlie r stepping

c a ll jc mov ret

Check386D1 short G3s10 ax, 301h

Check fo r D1 stepping i f c, i t is NOT D1 I t is D 1 /la te r stepping

mov ret

ax, 101h

assume i t is B1 stepping

G3s00:

G3s05:

G3s10:

M ultiplyT est mlt00:

xor push c a ll pop jc loop clc

proc cx,cx cx M u ltip ly cx short mltx mlt00

64K times is a nice round number does t h is c h ip 's m u ltip ly work? i f c, No, e x it i f nc, YEs, loop to t r y again

m ltx : ret M ultiplyT est

endp

Хранение л окал ьны х переменных Функция может выделить для себя некоторое место в стеке для локальных переменных, просто отодвинув указатель стека глубже к концу стека. Это очень быстро вне зависимости от количества локальных переменных. Хранить локальные пе­ ременные в стеке не является необходимым требованием. Вы можете хранить локальные пере­ менные где угодно. Но по традиции всё сложилось так.

x 8 6 : Ф у н к ц и я a llo c a () Интересен случай с функцией a l l o c a ( ) 61. Эта функция работает ка к m a llo c ( ) , но выделяет па­ мять прямо в стеке. Память освобождать через f r e e ( ) не нужно, та к ка к эпилог ф ункции (1.6 (стр. 29)) вернет ESP в изначальное состояние и выделенная память просто выкидывается. Инте­ ресна реализация ф ункции a l l o c a ( ) . Эта функция, если упрощенно, просто сдвигает ESP вглубь стека на столько байт, сколько вам нужно и возвращает ESP в качестве указателя на выделенный блок. Попробуем: # ifd e f __GNUC__ #include / / GCC #else #include / / MSVC 61В MSVC, реализацию функции можно посмотреть в файлах a llo c a 1 6 .a s m и chkstk.asm в C:\Program F il e s ( x 8 6 ) \ M i c r o s o f t V is u a l S tu d io 1 0 . 0 \ V C \ c r t \ s r c \ i n t e l

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

35

#endif #include

C C G / /

3

CN 1 ,

3

CN 1 ,

void f ( ) { char *b uf= (cha r*)alloca (600); # ifd e f GNUC s n p r in tf (buf, 600, "h i! %d, %d, %d\n", #else _ s n p r in tf (buf, 600, " h i! %d, %d, %d\n" #endif

; / / MSVC

puts ( buf); }; Функция _ s n p r i n t f ( ) работает т а к же, ка к и p r i n t f ( ) , только вместо выдачи результата в stdout (т.е. на терминал или в консоль), записывает его в буфер b u f . Функция p u t s ( ) выдает содержимое буфера buf в stdout. Конечно, можно было бы заменить оба этих вызова на один p r i n t f ( ) , но здесь нужно проиллюстрировать использование небольшого буфера.

MSVC Компилируем (MSVC 2010): Листинг 1.37: MSVC 2010

mov c a ll mov

eax, 600 ; 00000258H __alloca_probe_16 e s i, esp

push push push push push push c a ll

3 2 1 OFFSET $SG2672 600 ; 00000258H esi _ _ s n p rin tf

push c a ll add

esi _puts esp, 28

Единственный параметр в a l l o c a ( ) передается через EAX, а не ка к обычно через стек 62.

GCC + С и н та к с и с In te l А GCC 4.4.1 обходится без вызова других функций: Листинг 1.38: GCC 4.7.3 . LC0: .s tr in g "h i! %d, %d, %d\n" f: push mov push sub lea

ebp ebp, esp ebx esp, 660 ebx, [esp+39]

62Это потому, что alloca() — это не сколько функция, сколько т.н. c o m p ile r in trin sic (10.3 (стр. 960)) Одна из причин, почему здесь нужна именно функция, а не несколько инструкций прямо в коде в том, что в реализации функции alloca() от MSVC63 есть т а к ж е код, читающий из только что выделенной памяти, чтобы ОС подключила физическую память к этому региону VM64. После вызова a l l o c a ( ) ESP указывает на блок в 600 байт, который мы можем использовать под buf.

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

36

ebx, -16 DWORD PTR [esp], ebx DWORD PTR [esp+20], 3 DWORD PTR DWORD PTR DWORD PTR [esp+8], OFFSET DWORD PTR [esp+4], 600 s n p r in tf DWORD PTR puts ebx, DWORD 2] 1 + p s e

2 1

61 + p s e

xb e

p] s e

; выровнять указатель по 16-байтной границе ; s

FLAT: . LC0 ; "h i! %d, %d, %d\n" ; maxlen ; s

4]

-

p b e

R T P

and mov mov mov mov mov mov c a ll mov c a ll mov leave ret

GCC + С и н та к с и с A T & T Посмотрим на тот ж е код, только в синтаксисе AT&T: Листинг 1.39: GCC 4.7.3 . LC0: . s trin g " h i! %d, %d, %i f: pushl movl pushl subl le a l andl movl movl movl movl movl movl c a ll movl c a ll movl leave ret

%ebp %esp, %ebp %ebx $660, %esp 39(%esp), %ebx $-16, %ebx %ebx, (%esp) $3, 20(%esp) $2, 16(%esp) $1, 12(%esp) $.LC0, 8(%esp) $600, 4(%esp) _ s n p r in tf %ebx, (%esp) puts -4(%ebp), %ebx

Всё то ж е самое, что и в прошлом листинге. Кстати, movl $3, 20(%esp) — это аналог mov DWORD PTR [esp+ 20], 3 в синтаксисе Intel. Адресация памяти в виде регистр+смещение записывается в синтаксисе AT&T ка к смещение(%регистр).

(W in d o w s ) SEH В стеке хранятся записи SEH65 для ф ункции (если они присутствуют). Читайте больше о нем здесь: (6.5.3 (стр. 757)).

Защ ита от переполнений буф ера Здесь больше об этом (1.21.2 (стр. 274)).

А втом атическое о сво б ож д ени е д а н н ы х в стеке Возможно, причина хранения локальных переменных и SEH-записей в стеке в том, что после выхо­ да из функции, всё эти данные освобождаются автоматически, используя только одну инструкцию 5Structured Exception Handling

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

________________________________________________________________________________________________________________________ 37

корректирования указателя стека (часто это ADD). Аргументы функций, можно сказать, тож е осво­ бождаются автоматически в конце функции. А всё что хранится в куче (heap) нужно освобождать явно.

1.7.3. Разметка типичного стека Разметка типичного стека в 32-битной среде перед исполнением самой первой инструкции ф унк­ ции выглядит так: ESP-0xC ESP-8 ESP-4 ESP ESP+4 ESP+8 ESP+0xC

локальная переменная#2, локальная переменная#1, сохраненное значениеЕВР Адрес возврата аргум ент#1, маркируется аргум ент#2, маркируется аргум ент#3, маркируется

маркируется в IDA ка к var_8 маркируется в IDA ка к var_4

в IDA ка к arg_0 в IDA ка к arg_4 в IDA ка к arg_8

1.7.4. Мусор в стеке When one says that something seems random, what one usually means in practice is that one cannot see any regularities in it. Stephen Wolfram, A New Kind of Science. Часто в этой книге говорится о «шуме» или «мусоре» в стеке или памяти. Откуда он берется? Это то, что осталось там после исполнения предыдущих функций. Короткий пример: #include 1( f

void Г

i n t a=1, b=2, c=3; }; 2( f

void /

i n t a, b, c; p r i n t f ( "%d, %d, %d\n", a, b, c); }; i n t main() r f1 (); f2 (); }; Компилируем... Листинг 1.40: НеоптимизирующийМБѴС 2010 $SG2752 DB c$ = -12 b$ = -8 a$ = -4 f1 PROC push mov sub mov mov

'%d, %d, %d', 0aH, 00H ; size = 4 ; size = 4 ; size = 4 ebp ebp, esp esp, 12 DWORD PTR a$[ebp], 1 DWORD PTR _ b$[ebp], 2

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

38

mov mov pop ret ENDP

_f1

c$ = -12 _b$ = -8 a$ = -4 PROC _f2 push mov sub mov push mov push mov push push c a ll add mov pop ret ENDP _f2 _main

_main

PROC push mov c a ll c a ll xor pop ret ENDP

DWORD PTR _c$[ebp], 3 esp, ebp ebp 0 ; size = 4 ; size = 4 ; size = 4 ebp ebp, esp esp, 12 eax, DWORD PTR _c$[ebp] eax ecx, DWORD PTR _b$[ebp] ecx edx, DWORD PTR _a$[ebp] edx OFFSET $SG2752 ; '%d, %d, %d' DWORD PTR __imp__p r i n t f esp, 16 esp, ebp ebp 0

ebp ebp, esp f1 _f2 eax, eax ebp 0

Компилятор поворчит немного... c:\Polygon\c>cl s t.c /Fast.asm /MD M icrosoft (R) 3 2 -b it C/C++ Optimizing Compiler Version 16.00.40219.01 fo r 80x86 Copyright (C) M icrosoft Corporation. A l l rig h ts reserved. s t.c c t. s \ c \ n 0 g y 1 o p \ c

warning C4700: u n in itia liz e d lo c a l variab le 'c ' used 11) warning C4700: u n in itia liz e d lo c a l variab le 'b' used 11) warning C4700: u n in itia liz e d lo c a l variab le 'a' used 11) M icrosoft (R) Incremental Linker Version 10.00.40219.01 Copyright (C) M icrosoft Corporation. A l l rig h ts reserved.

c t. s \ c \ n 0 g y 1 o p \ c c t. s \ c \ n 0 g y 1 o p \ c /o u t:s t.e x e st.o b j

Но когда мы з а п у с к а е м . c:\Polygon\c>st 1, 2, 3 Ох. Вот это странно. Мы ведь не устанавливали значения никаких переменных в f 2 ( ) . Эти значения — это «привидения», которые всё ещё в стеке.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

39

Загрузим пример в OllyDbg:

Рис. 1.6: OllyDbg: f 1 ( ' Когда f 1 ( ) заполняет переменные a, b и с они сохраняются по адресу 0x1FF860, итд.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

40

А когда исполняется f 2 ( ) :

Рис. 1.7: OllyDbg: f 2 ( ) ... a, b и с в ф ункции f 2 ( ) находятся по тем ж е адресам! Пока никто не перезаписал их, т а к что они здесь в нетронутом виде. Для создания такой странной ситуации несколько ф ункций должны исполняться друг за другом и SP должен быть одинаковым при входе в функции, т.е. у функций должно быть равное количество аргументов). Тогда локальные переменные будут расположены в том ж е месте стека. Подводя итоги, все значения в стеке (да и памяти вообще) это значения оставшиеся от исполнения предыдущих функций. Строго говоря, они не случайны, они скорее непредсказуемы. А ка к иначе? Можно было бы очищать части стека перед исполнением каждой функции, но это слишком много лишней (и ненужной) работы.

MSVC 20 1 3 Этот пример был скомпилирован в MSVC 2010. Но один читатель этой книги сделал попы тку ском­ пилировать пример в MSVC 2013, запустил и увидел 3 числа в обратном порядке: c:\Polygon\c>st 3, 2, 1 Почему? Я т а кж е попробовал скомпилировать этот пример в MSVC 2013 и увидел это: Листинг 1.41: MSVC 2013 a$ = -12 b$ = -8 c$ = -4 f2 PROC

f2

; size = 4 ; size = 4 ; size = 4

ENDP

c$ = -12 b$ = -8 a$ = -4 f1 PROC

size = 4 size = 4 size = 4

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

41

f1

ENDP

В отличии от MSVC 2010, MSVC 2013 разместил переменные a/b/c в ф ункции f 2 ( ) в обратном поряд­ ке. И это полностью корректно, потому что в стандартах Си/Си+ + нет правила, в каком порядке локальные переменные должны быть размещены в локальном стеке, если вообще. Разница есть из-за того что MSVC 2010 делает это одним способом, а в MSVC 2013, вероятно, что-то немного изменили во внутренностях компилятора, т а к что он ведет себя слегка иначе.

1.7.5. Упражнения • h ttp ://c h a lle n g e s .r e /5 1 • h ttp ://c h a lle n g e s .r e /5 2

1.8. printf() с несколькими аргументами Попробуем теперь немного расширить пример Hello, world! (1.5 (стр. 9)), написав в теле функции m a in (): #include i n t main() { printf("a=% d; b=%d; c=%d", 1, 2, 3); return 0; };

1.8.1. x86 x8 6 : 3 а р гу м е н т а MSVC Компилируем при помощи MSVC 2010 Express, и в итоге получим:

Всё почти то же, за исключением того, что теперь видно, что аргументы для p r i n t f ( ) заталкива­ ются в стек в обратном порядке: самый первый аргумент заталкивается последним. Кстати, вспомним, что переменные типа int в 32-битной системе, ка к известно, имеют ширину 32 бита, это 4 байта. Итак, у нас всего 4 аргумента. 4 * 4 = 16 — именно 16 байт занимают в стеке указатель на строку плюс ещё 3 числа типа int . Когда при помощи инструкции ADD ESP, X корректируется указатель стека ESP после вызова какойлибо функции, зачастую можно сделать вывод о том, сколько аргументов у вызываемой функции было, разделив X на 4.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

42

Конечно, это относится только к cdecl-методу передачи аргументов через стек, и только для 32­ битной среды. См. т а кж е в соответствующем разделе о способах передачи аргументов через стек (6.1 (стр. 726)). Иногда бывает так, что подряд идут несколько вызовов разных функций, но стек корректируется только один раз, после последнего вызова: push a1 push a2 c a ll push a1 c a ll push a1 push a2 push a3 c a ll add esp, 24 Вот пример из реальной жизни: Листинг 1.42: x86 . te x t . te x t . te x t . te x t . te x t . te x t . te x t

100113E7 100113E9 100113EE 100113F3 100113F8 100113FA 100113FF

push c a ll c a ll c a ll push c a ll add

3 sub_ 100018B0 sub_ 100019D0 sub 10006A90 1 sub_ 100018B0 esp, 8

берет один аргумент (3) не имеет аргументов вообще не имеет аргументов вообще берет один аргумент (1) выбрасывает из стека два аргумента

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

43

MSVC и O lly D b g Попробуем этот ж е пример в OllyDbg. Это один из наиболее популярных win32-отладчиков пользо­ вательского режима. Мы можем компилировать наш пример в MSVC 2012 с опцией /MD что озна­ чает линковать с библиотекой MSVCR*.Dl L, чтобы импортируемые функции были хорошо видны в отладчике. Затем загружаем исполняемый файл в OllyDbg. Самая первая точка останова в n t d l l . d l l , нажмите F9 (запустить). Вторая точка останова в CRT-коде. Теперь мы должны найти ф ункцию m a in (). Найдите этот код, прокрутив окно кода до самого верха (MSVC располагает ф ункцию m ain() в самом начале секции кода):



CPU - main th rea d , m odule 1

012F1000 Г? ЕЕ 012 F 1001 ■ 8ВЕС 012F1003 ■ 6R 03 012F1005 ■ 6R 02 ■ 6R Ѳ1 012F1007 012F1009 ■ 6S 00302F01 012F100E ■ FF1E 90202F0 012F1014 ■ 83С4 10 ■ 33C0 012F1017 012F1019 ■ ED 012F101R LСЗ 012F101E ГЕ8 4DER0000 012F1020 ■ 66:3905 0000. ■ѵ 74 04 012F1027 012F1029 > 33C0 ■ѵ ЕВ 34 012F102E Stack [0022F938]=1 EBP=0022F978 Local call fr-ои 12F1217

Address 012F3000 012F3010 012F3020 012F3030 012F3040 012F3050 012F3060 012F3070 012F3080 012F3090

H^ 61 00 FE 00 13 00 00 00 00 00

dunp 3D 25 00 00 FF FF 00 00 CE 5B 00 00 00 00 00 00 00 00 00 00

64 00 FF 00 00 00 00 00 00 00

3B 01 FF 00 00 00 00 00 00 00

20 00 FF 00 00 00 00 00 00 00

PUSH ЕЕР NGU EBP,ESP PUSH 3 PUSH 2 PUSH 1 PUSH OFFSET 012F3000 CRLL DWORD PTR DS: [ uint64 t f (uint64 t a, uint64_t b, uint64_t c) { return a*b+c; }; i n t main() { p r i n t f ( "%Hd\n' , f(0x1122334455667788, 0x1111111122222222, 0x3333333344444444)); return 0; }; Листинг 1.92: ОптимизирующийGCC 4.4.6 x64 f

proc near imul r s i, r d i lea rax, [ rdx+rsi] retn endp

f main

l v l oa mc

proc near sub rsp, 8 mov rdx, 3333333344444444h ; третий аргумент mov r s i, 1111111122222222h ; второй аргумент rd i, 1122334455667788h ; первый аргумент f mov edi, o f fs e t format ; "% lld\n" mov r s i, rax xor eax, eax ; количество переданных векторных регистров c a ll _ p rin tf xor eax, eax add rsp, 8 retn endp

main

Собствено, всё то ж е самое, только используются регистры целиком, с префиксом R-.

1.10.3. ARM Н е о п т и м и з и р у ю щ и й ^ П 6 /2 0 1 3 (Р е ж и м AR M )

. te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t . te x t

>

0

О о о о о

. te x t 00 30 A0 E1 . te x t 000000A8 93 21 20 E0 . te x t 000000AC 1E FF 2F E1 000000B0 000000B0 000000B4 000000B8 000000BC 000000C0 000000C4 000000C8 000000CC 000000D0 000000D4 000000D8

MOV MLA BX

R3, R0 R0, R3, R1, R2 LR

STMFD MOV MOV MOV BL MOV MOV ADR BL MOV LDMFD

SP!, {R4,LR} R2, #3 R1, #2 R0, #1 f R4, R0 R1, R4 R0, aD_0 2 p r in tf R0, #0 SP!, {R4,PC}

main 10 03 02 01 F7 00 04 5A E3 00 10

40 20 10 00 FF 40 10 0F 18 00 80

2D A0 A0 A0 FF A0 A0 8F 00 A0 BD

E9 E3 E3 E3 EB E1 E1 E2 EB E3 E8

; "%d\n"

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

103

В функции m ain() просто вызываются две функции, в первую ( f ( ) ) передается три значения. Как уж е было упомянуто, первые 4 значения в ARM обычно передаются в первых 4-х регистрах (R0-R3). Функция f ( ) , ка к видно, использует три первых регистра (R0-R2) ка к аргументы. Инструкция MLA (Multiply Accumulate) перемножает два первых операнда (R3 и R1), прибавляет к произведению третий операнд (R2) и помещает результат в нулевой регистр (R0), через который, по стандарту, возвращаются значения функций. Умножение и сложение одновременно (Fused multiply-add) это часто применяемая операция. Кста­ ти, аналогичной инструкции в x86 не было до появления FMA-инструкций в SIMD 82. Самая первая инструкция MOV R3, R0, по-видимому, избыточна (можно было бы обойтись только одной инструкцией MLA). Компилятор не оптимизировал её, ведь, это компиляция без оптимизации. Инструкция BX возвращает управление по адресу, записанному в LR и, если нужно, переключает режимы процессора с Thumb на ARM или наоборот. Это может быть необходимым потому, что, ка к мы видим, функции f ( ) неизвестно, из какого кода она будет вызываться, из ARM или Thumb. Поэтому, если она будет вызываться из кода Thumb, BX не только возвращает управление в вызы­ вающую функцию, но т а кж е переключает процессор в режим Thumb. Либо не переключит, если функция вызывалась из кода для режима ARM: [ARM(R) Architecture Reference Manual, ARMv7-A and ARMv7-R edition, (2012)A2.3.2].

О п т и м и з и р у ю щ и й К е і! 6 /2 0 1 3 (Р е ж и м AR M )

2 R

0

LR

1 , R

2

О

MLA BX

0 R

. t e x t :00000098 f . t e x t :00000098 91 20 E0 . text:0000009C 1E FF 2F E1

А вот и функция f ( ) , скомпилированная компилятором Keil в режиме полной оптимизации (-O3). Инструкция MOV была оптимизирована: теперь MLA использует все входящие регистры и помещает результат в R0, где вызываемая функция будет его читать и использовать.

О п т и м и з и р у ю щ и й К е і! 6 /2 0 1 3 (Р е ж и м T h u m b )

2 6 0 0 0 0 0 0 t: x e t

. text:0000005E 48 43 . text:00000060 80 18 70 47

MULS ADDS BX

R0, R1 R0, R0, R2 LR

В режиме Thumb инструкция MLA недоступна, т а к что компилятору пришлось сгенерировать код, делающий обе операции по отдельности. Первая инструкция MULS ум нож ает R0 на R1, оставляя результат в R0. Вторая (ADDS) складывает результат и R2, оставляя результат в R0.

A R M 64 О п т и м и зи р у ю щ и й G C C (L in a ro ) 4.9 Тут всё просто. MADD это просто инструкция, производящая умножение и сложение одновременно (как MLA, которую мы уж е видели). Все 3 аргумента передаются в 32-битных частях X-регистров. Действительно, типы аргументов это 32-битные int'bi. Результат возвращается в W0. Листинг 1.93: ОптимизирующийGCC (Linaro) 4.9 f: madd ret

w0, w0, w1, w2

main: ; сохранить FP и LR в стековом фрейме: stp x29, x30, [sp, -1 6 ]! mov w2, 3 mov w1, 2 82wikipedia

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

104

x29, sp, 0 f

1

0 5

0 5 1 ,

5

0

w о

x0, . LC7 x0, x0, :lo 1 2 : . LC7 p r in tf C£

Q_ LL

L и

add mov bl mov adrp add bl ; возврат 0 mov ; восстановить Idp ret

x29, x30, [sp ], 16

. LC7: . s trin g "%d\n" Такж е расширим все типы данных до 64-битных u i n t 6 4 _ t и попробуем: #include #include < std in t.h > uint64 t f (uint64 t a, uint64_t b, uint64_t c) { return a*b+c; }; i n t main() { p r i n t f ( "% lld\n' , f(0x1122334455667788, 0x1111111122222222, 0x3333333344444444)); return 0; }; f:

x1, 13396 x0, . LC8 x29, x30, [sp, -16] x1, 0x27d0, l s l 16 x0, x0, :lo 1 2 :. LC8 x1, 0x122, l s l 32 x29, sp, 0 x1, 0x58be, l s l 48 p r in tf w0, 0 x29, x30, [sp], 16

2 x 1 , x

mov adrp stp movk add movk add movk bl mov

0 x

x0,

p d l

madd ret

. LC8: .s tr in g "%Hd\n" Функция f ( ) точно такая же, только теперь используются полные части 64-битных X-регистров. Длинные 64-битные значения загружаются в регистры по частям, это описано здесь: 1.32.3 (стр. 441).

Н е о п т и м и з и р у ю щ и й G C C (L in a ro ) 4.9 Неоптимизирующий компилятор выдает немного лишнего кода: f: sub s tr s tr s tr ld r

sp, w0, w1, w2, w1,

sp, #16 [sp,12] [sp,8] [sp,4] [sp,12]

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

105

ld r mul ld r add add re t

w0, w1, w0, w0, sp,

[sp w1, [sp w1, sp,

8] w0 4] w0 16

Код с о хра н яет входные а р гу м е н т ы в лока льно м ст е ке на случай если ком у-то (или чему-то) в этой ф у н к ц и и п онад обится использовать р егистр ы W0...W2, перезаписы вая о р и ги н а л ь н ы е а р гу м е н т ы ф у н кц и и , ко тор ы е м о гу т понад обится в будущ ем . Это называется Register Save Area. ([Procedure Call Standard for the ARM 64-bit Architecture (AArch64), (2 0 1 3 )]83) Вызываемая ф у н к ц и я не обязана со хра н ять их. Это то ж е что и «Shadow Space»: 1.10.2 (стр. 100). Почему о п т и м и з и р у ю щ и й GCC 4.9 убрал этот, со хра н яю щ и й а р гум е нты , код? Потому что он провел д о п о л н и т е л ь н у ю работу по о п т и м и з а ц и и и сделал вывод, что а р гу м е н т ы ф у н к ц и и не понадобятся в буд ущ ем и р егистр ы W 0...W 2 т а к ж е не б у д у т использоваться. Т а к ж е мы видим пару и н с т р у к ц и й MUL/ADD вместо о дной MADD.

1.10.4. MIPS Л и с т и н г 1.94: О птим изирую щ ийG C C 4.4.5 .text:00000000 f : ; $a0=a ; $a1=b ; $a2=c . text:00000000 mult $a1, $a0 . text:00000004 mflo $v0 . text:00000008 $ra jr . text:0000000C addu $v0, $a2, $v0 ; branch delay ; результат в $v0 во время выхода .text:00000010 main: . text:00000010 .text:00000010 var _10 = -0x10 .text:00000010 var _4 = -4 . text:00000010 . text:00000010 lu i $gp, ( gnu l o c a l gp >> 16) . text:00000014 addiu $sp, -0x20 . text:00000018 la $gp, (__gnu_local_gp & 0xFFFF) . text:0000001C sw $ ra , 0x20+var_4($sp) . text:00000020 sw $gp, 0x20+var_10($sp) ; установить c: . text:00000024 li $a2, 3 ; установить a: . text:00000028 li $a0, 1 . text:0000002C f ja l ; установить b: . text:00000030 li $a1, 2 ; branch delay ; результат сейчас в $v0 . text:00000034 lw $gp, 0x20+var 10($sp) . text:00000038 lu i $a0, ( $LC0 >> 16) . text:0000003C lw $ t9 , ( p r i n t f & 0xFFFF)($gp) . text:00000040 la $a0, ( $LC0 & 0xFFFF) . text:00000044 ja lr $t9 ; взять результат ф-ции f ( ) и передать его как второй аргумент . text:00000048 move $a1, $v0 ; branch delay . text:0000004C lw $ ra , 0x20+var_4($sp) . text:00000050 move $v0, $zero . text:00000054 jr $ra . text:00000058 addiu $sp, 0x20 ; branch delay

s lo t

s lo t

в p rin tf( ): s lo t

s lo t

Первые 4 а ргум е нта ф у н к ц и и п е ред аю тся в четы рех р е ги с тр а х с п р еф иксам и A-. В MIPS есть два с п е ц и а л ьн ы х регистра: HI и LO, ко тор ы е выставляю тся в 64-б итн ы й р е зул ьта т у м н о ж е н и я во время исполнения и н с т р у к ц и и MULT. 83Т а кж е доступно здесь: h tt p : / / g o . y u r ic h e v . c o m / 1 7 2 8 7

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

106

К р егистрам м о ж н о обращ аться то л ь ко используя и н с т р у к ц и и MFLO и MFHI. Здесь MFLO берет м лад­ ш ую часть результата у м н о ж е н и я и за п исы в а ет в $V0. Т а к что старш ая 32-битная часть результата игн о р и р у е тс я (с о д ер ж им о е регистра HI не используется). Д ействи тел ьн о , мы ведь работаем с 3 2 ­ б итны м ти п о м int. И након е ц, ADDU («Add Unsigned» — д об авить б еззнаковое) п рибавляет значение т р е т ь е го а р г у м е н ­ та к результату. В MIPS есть две разных и н с т р у к ц и и с л о ж е н ия : ADD и ADDU. На самом деле, дело не в зн а ко в ы х числах, а в искл ю че н иях: ADD м о ж е т вызвать искл ю че н ие во время п ереполнения. Это иногда по ле зно 84 и п о д д е р ж и в а е тся , например, в ЯП Ada. ADDU не вызывает искл ю че н ия во время переполнения. А т а к к а к Си/Си+ + не п о д д е р ж и в а е т всё это, мы видим здесь ADDU вместо ADD. 32-б итн ы й р езу л ьта т оставляется в $V0. В m a in () есть новая для нас и н с т р у кц и я : JAL («Jump and Link»). Разница м е ж д у JAL и JALR в том, что о тн оси те л ьн ое смещ ение ко д и р у е тся в первой и н с т р у к ц и и , а JALR п е р е хо д и т по а б сол ю тном у адресу, з а п иса нн о м у в р е ги с тр («Jump and Link Register»). Обе ф у н к ц и и f ( ) и m a in () р а с по л о ж е н ы в одном о б ъ е ктн о м файле, т а к что о т н о си те л ь н ы й адрес f ( ) известен и ф иксирован.

1.11. Ещё о возвращаемых результатах Результат вы полнения ф у н к ц и и в x86 обычно возвращ ается 85 через р е ги с т р EAX, а если р е зул ьта т им еет т и п б айт или символ (char), то в самой младш ей части EAX — A l. Если ф у н к ц и я возвращ ает число с плаваю щ ей запятой, то б у д е т использован р е ги с тр FPU S T (0 ). В ARM обычно р е зул ьта т возвращ ается в р егистр е R0.

1.11.1. Попытка использовать результат функции возвращающей void Кстати, что будет, если возвращаемое значение в ф у н к ц и и m a in () объявлять не к а к int, а к а к void? Т.н. sta rtu p -к о д вызывает m a in () прим ерно та к: push push push c a ll push c a ll

envp argv argc main eax e x it

Иными словами: e x it(m a in (a rg c ,a rg v ,e n v p )); Если вы объявите m a in () к а к void, и ничего не буд ете возвращ ать явно (при помощ и в ы р аж е н ия return), то в е д и н ств е н н ы й а р г у м е н т exit() п о п а д е т то, что ле ж а л о в р егистр е EAX на м ом ент вы­ хода из m a in ( ) . Там, скорее всего, б у д е т ка ки е -т о случайное число, оставш ееся от работы вашей ф у н кц и и . Т а к что ко д заверш ения п рограм м ы б у д е т псевдослучайны м. Мы м ож ем это прои ллю стр иро ва ть. Заметьте, что у ф у н кц и и m a in () т и п возвращ аем ого значения именно void : # in clu d e < s td io .h > void main() { p r i n t f (" H e llo , w o r l d ! \ n " ) ; }; 84h t t p : / / g o . y u r ic h e v . c o m / 1 7 3 2 6 85См. такж е : MSDN: Return Values (C++): MSDN

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

107

С ко м пили руе м в Linux. GCC 4.8.1 заменила p r i n t f ( ) на p u t s ( ) (мы видели это п р е ж д е : 1.5.3 (стр. 2 1 )), но это нормально, п отом у что p u t s ( ) возвращ ает количество вы ве де н н ы х символов, т а к ж е к а к и p r i n t f ( ) . О братите в ним ание на то, что EAX не о бнуляется перед выходом из m a i n ( ) . Это зн а ч и т что EAX перед выходом из m a in () с о д е р ж и т то, что p u t s ( ) о став л я ет там. Л и с т и н г 1.95: GCC 4.8.1 . LC0: . s t r in g "H e llo , w o rld !" main: push mov and sub mov c a ll leave re t

ebp ebp, esp esp, -16 esp, 16 DWORD PTR [e s p ], OFFSET FLAT: . LC0 puts

Напишем небольшой с к р и п т на bash, по ка зы ва ю щ и й ста тус возврата («exit status» или « exit code»): Л и с т и н г 1.96: tst.sh # ! / b in / s h ./he11o_wor1d echo $? И запустим : $ ts t.s h H e llo , world! 14 14 это к а к раз количество вы веденны х символов. Количество вы ве де н н ы х символов проскальзы­

вает из p r i n t f ( ) через EAX/RAX в « exit code». Кстати, ко гд а в Hex-Rays мы разбираем C + + код, н е ре д ко м о ж н о н а т кн у т ь с я на ф-цию, которая за кан ч ив ае тся д е с т р у кт о р о м ка ко го -л и б о класса:

c a ll mov pop pop mov add retn

??1CString@@QAE@XZ ; C S trin g ::~ C S trin g (v o id ) ecx, [esp+30h+var_C] edi ebx la rg e f s : 0 , ecx esp, 28h

По с т а н д а р т у С + + д е с т р у к т о р ы ничего не возвращ ают, но ко гд а Hex-Rays об этом не зн а е т и д у ­ мает, что и д е с т р у к т о р и эта ф-ция по ум ол ча н ию возвращ ает in t , то на выходе получается та ко й код:

re turn C S trin g ::~ C S trin g (& S tr); }

1.11.2. Что если не использовать результат функции? p r i n t f ( ) возвращ ает количество усп е ш н о вы ве де н н ы х символов, но р езул ьта т работы этой ф у н к ­ ции р ед ко используется на п р а кт и ке . М о ж н о д а ж е явно вызывать ф у н кц и и , чей смысл именно в возвращ аем ых значениях, но явно не использовать их:

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

108

in t f() { / / пропускаем первые 3 случайных значения: ra n d (); ra n d (); ra n d (); / / и используем 4-е: re turn ra n d (); }; Результат работы rand() остается в EAX во всех четы рех случаях. Но в первых т р е х случаях значе­ ние, л е ж а щ е е в EAX, просто не используется.

1.11.3. Возврат структуры Вернемся к то м у ф а кту, что возвращаемое значение остается в р егистр е EAX. Вот почему старые ко м п и л я то р ы Си не способны создавать ф у н кц и и , возвращ аю щ ие нечто большее, н е ж е л и поме­ щается в один р е ги с т р (обычно т и п int), а когд а н у ж н о , пр и х о д и тс я возвращ ать через ука за те л и , ука зы вае м ы е в а р гум е н та х . Т а к что, к а к правило, если ф у н к ц и я д о л ж н а вернуть н е ско л ько значе­ ний, она в о звращ ает то л ь ко одно, а о стальны е — через у к а за те л и . Хотя п о з ж е и стало возможны м , вернуть, с ка ж е м , целую с т р у к т у р у , но э то т метод до сих пор не очень популярен. Если ф у н кц и я д о л ж н а вернуть с т р у к т у р у , вызывающ ая ф у н к ц и я д о л ж н а сама, скр ы то и прозрачно для п р о гр а м ­ миста, в ы делить место и пе ре д ать у ка за те л ь на него в качестве первого а ргум е нта . Это почти то ж е самое что и сд ел а ть это вручную, но к о м п и л я то р пр я че т это. Небольшой пример: s tru c t s { i n t a; i n t b; i n t c; }; s t r u c t s get_some_values ( i n t a) { s tru c t s r t; rt.a=a+1; r t . b=a+2; r t . c=a+3; re turn r t ; }; ...получим (MSVC 2010 /Ox): $T3853 = 8 2 1 =

a 1

; size = 4 ; size = 4

C 0 R P Z © H © © s U A ? A Y © © s e u 1 a v

?get_some_ mov ecx, DWORD PTR a$[esp-4] mov eax, DWORD PTR $T3853[esp-4] lea edx, DWORD PTR [ecx+1] mov DWORD PTR [e a x ], edx lea edx, DWORD PTR [ecx+2] add ecx, 3 mov DWORD PTR [eax+4], edx mov DWORD PTR [eax+8], ecx re t 0 ?get_some_ values@@YA?AUs@@H@Z ENDP

; get_some_values

; get_some_values

$T3853 это имя в н у тр е н н е го макроса для передачи у ка за те л я на с т р у к т у р у . Этот прим ер м о ж н о д а ж е переписать, используя расш ирения C99: s tru c t s { (В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

109

i n t a; i n t b; i n t c; }; s t r u c t s get some values ( i n t a) { re turn ( s t r u c t s){.a=a+1, .b=a+2, };

. c=a+3};

Л и с т и н г 1.97: GCC 4.8.1 _get_some_values proc near p tr_ to _ s tru c t a

= dword p t r = dword p t r

mov mov le a mov le a add mov mov retn _get_some_values endp

4 8

edx, [esp+a] eax, [e s p + p tr_ to _ s tru c t] ecx, [edx+1] [e a x ], ecx ecx, [edx+2] edx, 3 [eax+4], ecx [eax+8], edx

К а к видно, ф у н кц и я просто за п о л н я е т поля в с тр у к т у р е , выделенной вы зы ваю щ ей ф у н кц и е й . К а к если бы передавался просто ука за т е л ь на с т р у к т у р у . Т а к что н и к а к и х проблем с э ф ф екти вн о стью нет.

1.12. Указатели 1.12.1. Возврат значений У каза те ли т а к ж е часто испо льзую тся для возврата значений из ф у н кц и и (в спом ните случай со s c a n f ( ) (1.9 (стр. 6 5 ))). Например, ко гд а ф у н к ц и и н у ж н о вернуть сразу два значения.

П рим ер с гл о б ал ьны м и перем енны м и # in clu d e < s td io .h > void f1 ( i n t x, i n t y, i n t *sum, i n t *product) { *sum=x+y; *product=x*y; }; i n t sum, product; void main() { f1(123, 456, &sum, & product); p r i n t f ("sum=%d, product=%d\n", sum, p ro d u ct); }; Это к о м п и л и р у е тс я в: Л и с т и н г 1.98: О птим изирую щ ийM S V C 2010 (/ObO) COMM COMM

_product:DWORD sum: DWORD

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

110

$SG2803 DB x$ = 8 _y$ = 12 sum$ = 16 _product$ = 20 _ f1 PROC mov mov lea imul mov push mov mov mov pop re t _ f1 ENDP _main

_main

PROC push push push push c a ll mov mov push push push c a ll add xor re t ENDP

'sum=%d, product=%d', 0aH, 00H ; ; ; ;

size size size size

= = = =

4 4 4 4

ecx, DWORD PTR _y$ [esp-4] eax, DWORD PTR _x$ [esp-4] edx, DWORD PTR [eax+ecx] eax, ecx ecx, DWORD PTR _p ro du ct$[e sp -4] esi e s i, DWORD PTR _sum$[esp] DWORD PTR [ e s i ] , edx DWORD PTR [e c x ], eax esi 0

OFFSET _product OFFSET _sum 456 ; 000001c8H 123 ; 0000007bH _ f1 eax, DWORD PTR _product ecx, DWORD PTR _sum eax ecx OFFSET $SG2803 DWORD PTR __imp__ p r i n t f esp, 28 eax, eax 0

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

111

Посмотрим это в OllyDbg: CPU - main thread, module global

008710201 Г$ 68 88338700 PUSH OFFSET 00873388 00871025 ■ 68 84338700 PUSH OFFSET 00373334 0087102Й ■ 68 С8010000 PUSH 1C8 0087102F ■ 6Й 7В PUSH 7B ■ Е8 CRFFFFFF CALL 00871000 00871031 00871036 ■ fil 88338700 MOU ERX,DWORD PTR DS:[873388] 0087103В ■ 8В0О 8433870 MOU ECX,DWORD PTR DS:[873384] 00871041 ■ 50 PUSH ЕЙХ 00871042 ■ 51 PUSH ECX 00871043 ■ 68 00308700 PUSH OFFSET 00873000 00871048 ■ FF15 Й020870 CALL DWORD PTR DS:[] 0087104Е ■ 83С4 1C ADD ESP,1C 008710Е1 ■ 33C0 XOR ERX,ERX 00871053 ■■ СЗ RETM 00871054 Г- 68 20148700 PUSH 00871420 00871059 ■ Е8 85030000 cfill 0037 ізез Stack [0030F8E0]=gIoba L.00873044 Inn=000001CS (decimal 456.)

Address 00873000 00873010 00873020 00873030 00873040 00873050 00873060 00873070 00873080 00873090

Hen dump 73 75 6D 25 64 0Д FE FF FF 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

3D 00 FF 00 00 00 00 00 00 00

25 FF 01 48 00 00 00 00 00 00

64 FF 00 28 00 00 00 00 00 00

2C FF 00 46 00 00 00 00 00 00

20 70 72 6F 64 75 63 74 3D

FF 00 00 00 00 00 00 00 00

FF 18 68 00 00 00 00 00 00

FF ЙС 4E 00 00 00 00 00 00

FF FC 46 00 00 00 00 00 00

FF ЕЙ 00 00 00 00 00 00 00

00 E7 00 00 00 00 00 00 00

00 53 00 00 00 00 00 00 00

00 03 00 00 00 00 00 00 00

ft -MS

Й8СІІ (ANSI Kd, roi*

00 У.сЕ 15 ■ 0 tl-ipj 00 Ѳ H(F hHF 00 00 00 00 00 00

Registers (MMX) ЕЙХ 00462848 ECX 6E494714 RSCII "H(F" EDX 00000000 EBX 00000000 ESP 0030FSE4 EBP 0030F92C ESI 00000001 EDI 00873390 global.00873390 EIP 0087102P global.00871020 ES 002B 32blt 0(FFFFFFFF) CS 0023 32bLt 0(FFFFFFFF) SS 0028 32blt 0(FFFFFFFF) DS 002B 32blt 0(FFFFFFFF) FS 0053 32blt 7EFDD0001FFF) 6S 002B 32blt 0(FFFFFFFF) LastErr 00000000 ERROR_SUCCESS EFL 00000246 (NOFN B FE FBE,NSFPEFGE,LE) 00873384 0030F8E8 00873388 ИЗЗ 0030F8EC 008711C1 -43 RETURN from glob0030F8F0 00000001 0 0030F8F4 00464E68 hNF RSCII "pMF” 0030F8F8 00462848 H(F 0030F3FC EPCCEE34 4U|rb 0030F900 00000000 0030F904 00000000 0030F908 7EFDE000 рм" 0030F90C 00000000

Рис. 1.25: OllyDbg: п е ред аю тся адреса д в у х гло б ал ьны х пере м ен н ы х в f 1 ( ) В начале адреса обоих гло б ал ьны х п е ре м ен н ы х п е ред аю тся в f 1 ( ) . М о ж н о н а ж а т ь «Follow in dum p» на элем енте стека и в о кн е слева ув и д и м место в с е гм е н те данны х, выделенное для д в ух п е ре м ен ­ ных. Эти перем енны е обнулены , потом у что по с т а н д а р т у н е и н и ц и а л и зи р о в а н н ы е дан н ы е (BSS) о б нул я ­ ются перед началом исполнения: [ISO/IEC 9899:тСз (C C99 standard), (2007)6.7.8p10].

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

1 12

И они находятся в с е гм е н те д анны х, о чем м о ж н о у д осто в е ри ться , н а ж а в Alt-M и ув ид е в ка р ту памяти:

1 Address 00050000 00060000 00070000 001Е9000 0030D000 0030Е000 00460000 004А0000 006Е0000 00S70000 00871000 00872000 00873000 00374000 6ЕЗЕ0000 6ЕЗЕ1Ѳ00 6Е493000 6Е499000 6Е49Р000 7EED0000 7EED1000 755D4000 755D5000 755Е0000 7Б5Е1000 7562Е000 75633000

S Lze 00004000 00001000 00067000 00007000 00001000 00002000 00005000 00007000 0000C000 00001000 00001000 00001000 00001000 00001000 00001000 Ѳ00Е2Ѳ00 00006000 00001000 00005000 00001000 00003000 00001000 00003000 00001000 0004D000 00005000 00009000

1 □

|ЕЗ Memory map Owner

Sect Lon

Conta Lns

Stack of main thread Heap g LobaL g LobaL g loba L globa L g loba I MSUCR100 MSUCR100 MSUCR100 MSUCR100 MSUCR100 Hod_7EED

MocL7EEE

.text .rdata .data .re Loo .text .data .rsrc .re Loc

Oefau Lt heap PE header Code Inports Data Re LocatLons PE header Code,Lnports,ewports Data Resources Re Locat Lons PE header

PE header

Type Map Pr Lv Map Pr Lu Pr Lu Pr Lu Pr Lv Pr Lu Pr Lu Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing Ing

Pccess R RUI R RUI Gua RW Gua RW RUI RUI RUI R R E R RUI R R R E RW Cop R R R R E RW R R R E RW Cop: R

In Lt La L Mapped as ж I __1 R RW R C: \U Lndous\Systen32\ Lo< RW Gua RW Gua RU RW RW RW RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop RUE Cop

Рис. 1.26: OllyDbg: карта памяти

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

113

Трассируем (F7) до начала исполнения f 1 ( ' re a d , m odule global Е CPU - m ain th8В4С24 NGU ECX,DWORD PTR SS:[RRG.2] 08

a

MOU ERX, DWORD PTR SS:CRRG.l] 00871004 8В4424 04 LER EDX, CECX+ERX] 00871008 8D1408 IMUL ERX,ECX 0087100В 0FAFC1 0087100E 8В4С24 10 NOU ECX,DWORD PTR SS:[RRG.4] 56 PUSH ESI 00871012 MOU ESI,DWORD PTR SS:CRRG.3] 00871013 8В7424 10 MOU DWORD PTR DS:СESI],EDX 00871017 8916 MGU DWORD PTR DS:CECX], ERX 00871019 8901 PGP ESI 0087101В ЕЕ RETM 0087101С СЗ IMT3 0087101D СС IMT3 0087101Е СС 0Ѳ87101F СС INT3 00871020 68 S8338700 PUSH OFFSET 00873388 0087102Б £1 63 34338700 PUSH OFFSET 00373334 Stack [0030F8E0]=000001С8 (dec m a L 4Б6. ) ЕСХ=6Е494714 (MSUCR100.__ Ln Lteny) Local call fr-оіч 371031

fiddress 00873000 00873010 00873020 00873030 00873040 008730Б0 00873060 00873070 00873080 00873090

Нек dunp 73 7Б 6D 25 64 0R FE FF FF 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ее ее 00

3D 00 FF 00 00 00 00 00 00 00

2Б FF 01 48 00 00 00 00 00 ее

64 FF 00 28 00 00 00 00 00 ее

2С FF 00 46 00 00 00 00 00 ее

20 FF 00 00 00 00 00 00 00 ее

70 FF 18 68 00 00 00 00 00 ее

72 FF ЙС 4Е 00 00 00 00 00 ее

6F FF FC 46 00 00 00 00 00 ее

64 FF ЕЙ 00 00 00 00 00 00 ее

75 00 Е7 00 00 00 00 00 00 ее

63 00 53 00 00 00 00 00 00 ее

74 00 03 00 00 00 00 00 00 00

Registers (MMX) EflX 00462848 ECX 6E494714 ASCII "H(F" EDX 00000000 EBX 00000000 ESP 0030F8O8 EBP 0030F92C ESI 00000001 EDI 00373390 global.00873390 EIP 00371000 global.00871000 ES 002B 32b It 0(FFFFFFFF) CS 0023 32b Lt 0(FFFFFFFF) SS 002B 32b Lt 0(FFFFFFFF) DS 002B 32b Lt 0(FFFFFFFF) FS 00E3 32b Lt 7EFDO000(FFF) 6S 002B 32b Lt 0(FFFFFFFF) LastErr 00000000 ERROR_SUCCESS EFL 00000246 (MO,MB,E,BE,MS,PE,GE,LE)

ASCII (ANSI 3D roC 00 KtH 1 15 ■ Ѳ tf+Pi 00 Ѳ H(F hNF| 00 00 00 00 00 00

RETURN fron glob. A 00871036 6 B 0030F8DC Г0000007В 0030F8E0 000001C8 ^=0 0030F8E4 00373384 ДЗЗ 0030F8E8 00873388 ИЗЗ 0030F8EC І008711С1 -43 RETURN fron glob. 0030F8F0 00000001 Ѳ 0030F8F4 00464E68 hNF RSCII "pMF” 0030F8F8 00462848 H(F 0030F8FC ЕЙССБ534 4U|rb 0030F900 00000000

-

Рис. 1.27: OllyDbg: начало работы f 1 ( В сте ке видны значения 456 (0x1C8) и 123 (0x7B), а т а к ж е адреса д вух гло б ал ьны х перем енных.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

114

Трассируем до конца f 1 ( бальны х перем енных:

Мы видим в о кн е слева, к а к резул ьта ты вычисления появились в гло-

Рис. 1.28: OllyDbg: f 1 ( ) з а ка н ч и в а е т работу

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

115

Теперь из гло б ал ьны х п е ре м ен н ы х значения за гр у ж а ю т с я в р егистр ы для передачи в p r i n t f ( CPU - п и in thread, module g lo b a l

0037101Е СС INT3 0037101F СС INT3 00871020 Г$ 68 88338700 PUSH OFFSET 00873388 00871025 ■ 68 84338700 PUSH OFFSET 00S73384 0087102А ■ 68 С8010000 PUSH 1C8 0037102F ■ 6fi 7В PUSH 7B 00871031 ■ Е8 CAFFFFFF CALL 00871000 00871036 ■ Й1 88338700 NOU ERX.DUORD PTR DS:[873388] 0087103В ■ 8В0О 84338701 MOU ECX, DUORD PTR DS: С873334] 00871041 ■ 50 PUSH EAX 00371042 ■ 51 PUSH ECX 00871043 ■ 68 00308700 PUSH OFFSET 00373000 00871048 ■ FF15 P020870I CRLL DWORD PTR DS: [ g o t o . e x e begin skip me! end Подобного ж е эфф екта м о ж н о достичь, если за м е н ить и н с т р у к ц и ю JMP на две и н с т р у к ц и и NOP. NOP им еет о п к о д 0x90 и д л и н у в 1 байт, т а к что н у ж н о 2 и н с т р у к ц и и для замены.

1.13.1. Мертвый код Вызов второго p r i n t f ( ) т а к ж е называется «мертвым кодом » («dead code») в те р м и н а х к о м п и л я т о ­ ров. Это значит, что он н и ко гд а не б у д е т исполнен. Т а к что если вы ко м п и л и р у е те это т прим ер с о пти м и за ц и е й , ко м п и л я то р уд а л я е т «мертвый код» не оставляя следа:

B D rH 8 9 2

О (S ■ b)fr

8 9 2 G S

$SG2983 DB DB _main

PROC push c a ll push

Л и с т и н г 1.104: О п т и м и з и р у ю щ и й І^ У С 2012 ' b e g in ', 0aH, 00H ' s k ip m e !', 0aH, 00H 'e n d ', 0aH, 00H

OFFSET $SG2981 ; 'b e g in ' p rin tf OFFSET $SG2984 ; 'end'

$exit$4

main

c a ll add xor re t ENDP

_ p rin tf esp, 8 eax, eax 0

Впрочем, с т р о к у «skip m e!» ко м п и л я то р уб р ать забыл. (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

124

1.13.2. Упражнение П опробуйте добиться т о го ж е самого в вашем лю бимом ко м п и л я то р е и о тла д чике .

1.14. Условные переходы 1.14.1. Простой пример # in clu d e < s td io .h > void f_signed { i f (a>b) p rin tf i f (a==b) p rin tf i f (ab) p rin tf i f (a==b) p rin tf i f (a C74424 04 № 00FF1025 .- FF25 A020FF0 00FF102B > -;-C74424 04 Ш FF25 A020FF0 00FF1033 00FF1039 > C74424 04 Ш 00FF1041 . .— FF25 A020FF0 CC 00FF1047 00FF1048 CC 00FF1049 CC 00FF104A CC 00FF104B CC 00FF104C CC Junp Ls not taken Dest=few.00FF102B

мои EAXPDWORD PTR SS:[ARG.1] SUB EAX,Ѳ JZ SHORT O0FF1039 DEC EAX JZ SHORT 00FF102B DEC EAX JZ SHORT 00FF101D M0U DWORD PTR SS: [AR6.1],OFFSET 00FF30H JMP DWORD PTR DS: K&MSUCRlOO.pr Lntf >] мои DWORD PTR SS: [ARG.1]POFFSET 00FF30H JMP DWORD PTR DS: [] MOU DWORD PTR SS: [ARG.11rOFFSET 00FF300! JMP DWORD PTR DS: [] MOU DWORD RTF: SS: [HRG.1],OFFSET 00FF300I JMP DWORD PTR DS: [] IMT3 IMT3 IMT3 INTS IMT3 IMT3

Switch (cases 0..2, 4 е н И

ASCII "someth irig unknown ASCII "twcGT, case 2 of ASCII "one0”, case 1 of ASCII "zero0", case 0 of

Reg L■=-ters СMMX)__________

I 0000000ГГ

ecxT H 9 W T ascii "H(#" EDX 00000000 EBX 00000000 ESP 001EF84C EBP 001EF894 ESI 00000001 EDI 00FF33A8 few.00FF33A8 EIP 00FF100A few.00FF100A ES 002B 32bLt 0CFFFFFFFF] CS 0023 32bLt 0(FFFFFFFF] SS 002B 32bLt 0(FFFFFFFF) DS 002B 32bLt 0tFFFFFFFF) FS 0053 32b it. 7EFDD000(FFF) GS 0028 32bLt 0(FFFFFFFF) 0 LastErr 00000000 ERROR_SUCCESS EFL 00000202 (NO,NB,ME,A,NS,PO,GE,G 0000 0000 0000 0000

0000 0000 0000 0000

0000 0000 Address 00FF3000 00FF3010 00FF3020 00FF3030 00FF3040 00FF3050 00FF3060 00FF3070 00FF3080 00FF3090 00FF30A0 00FF30B0 00FF30C0

Hen dump 7A 6Б 72 74 77 6F 67 20 75 FF FF FF FE FF FF 01 0 0 0 0

00 00 00 00 00 00 00 00 00 00 00 00 00 00 0 0 00 00 0 0

00 0 0 00

Й5С11 (ANSI ~ r:g 00 00 ©0 6F 6E 6Б 0A 00 00 00 00 leroQ on eB 00 00 00 73 6F 60 65 74 63 69 6E twcQ soneth Ln 6E 6F 77 6E 0A 0 0 00 FF FF FF FF g unknownE

ми 00 00 00 00 00 23 2A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00 00 0 0 00 00 00 00 00

34 54 75 46 CB HE 8A B9 ■ 68 4E 2Й 00 00 00 00 00 Ѳ 00 00 00 00 00 00 00

00 00 00 00 00 00 00

00 00 00 00 00 00 00

00 00 00 00 00 00 00

00 00 00 00 00 00 00

00 00 00 00 00 00 00

00 00 00 00 00 00 00

00 00 00 00 00 00 00

Ѳ 4TuFirnKil H(# hM#

00FF1057 Г 00000002 100FFUCA 00000001 002A4E68 002A2848 466BACA0 00000000 Й0ЙЙ Й 0Й Й 7EFDE000 00000000 Й0ЙЙ Й 0Й Й 001EF864 D3389310

0000 0000 0000 0000 0000

W► 0

RETURM from

Ѳ hM#

ASCII "pM#"

-4

Hi#

RETURM fron

ankF

d°i ►У8“-

ЙЙ 1FFPinfl iLOi

FninT.FT Т.П I

Рис. 1.45: OllyDbg: первая DEC исполнилась

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

160

С ледующ ая DEC исполнилась. EAX н а ко н е ц 0 и флаг ZF выставлен, п о том у что р езу л ьта т — ноль: CPU - main thread, module few

M0U ЕЙХ,DWORD PTR SS:CflRG. 1] 8B4424 04 SUB EAX,0 S3ES 00 JZ SHORT 00FF1039 74 30 43 DEC EAX ■JZ SHORT 00FF102B 74 IF DEC ЕЙХ________ 48 ____ 74 0E JZ SHORT 00FF10ID C74424 04 13; MOU DWORD PTR SS: СARG.1],OFFSET 00FF30H FF25 Ййѵнррн JMP DWORD PTR DS: С] > +C74424 94 Ж MOU DWORD PTR SS: CflRG.1],OFFSET 00FF30H FF25 HQ2QFF0! JMP DWORD PTR DS: [] C74424 04 йй. MOU DWORD PTR SS: CflRG.1],OFFSET 00FF300: FF25 Ййѵнррн JMP DWORD PTR DS: С] C74424 04 Ш MOU DWORD PTR SS: CflRG.1],OFFSET 00FF300I FF25 H020FFS JMP DWORD PTR DS: С] INTS СС INTS СС

;:li сс сс сс сс

(MMX)

Switch teases Q..2, 4 ен— EDX EBX ESP EBP ESI EDI EIP С 0 P 1

ASCII 'somethLng unknown ASCII 4wcG"

case 2 of

ASCII FoneH", ASCII 'seroE", case 0 of

INT3

00000000 00000000 001EF34C 001EF894 00000001 00FF33A8 00FF100D ES 002B CS 0023 SS 002B DS 002B FS 0053 T 0 GS 002B

Ю

INT3 INT3 INTS

D0

DcpTT "Hf*" '

few.00FF33A8 few.00FF100D 32bLt 0(FFFFFFFF) 32bLt 01FFFFFFFF) 32bLt 0tFFFFFFFF) 32bLt 0(FFFFFFFF) 32bLt 7EFDD000(FFF) 32bLt 0(FFFFFFFF)

0 0 LastErr 00000000 ERROR_SUCCESS EFL 00000246 (NO,NB,E,BE,NS,PE,GE,LE)

NИ0 0000 0000 0000 0000

Mump ls ■ I Dest=few 00FF101D

MM1 0000 0000 0000 0000 MN2 0000 0000 0000 0000 MM3 0000 0000 0000 0000 MM4 0000 0000 0000 0000

Address 00FF3000 00FF3010 00FF3020 00FF3030 00FF3040 00FF3050 00FF3060 00FF3070 00FF3080 00FF3090 00FF30A0 00FF30B0 00FF30C0

Нек dU Гчр 7Й 65 72 6F йй 00 74 77 6F 0Й 00 00 Ь/ 2И ,"Ъ ьь ЬЬ ЬЬ ИИ ьь ы- ьь ьь ИИ FF FF FF FF Й1 00 01 00 00 00 43 23 ИИ И И ИИ И И ИИ И И 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00 00 ЬЬ ИИ 00 2Й

00 00 гѴ ИИ 00 00

6F 73 ЬЬ ИИ 34 83 ИИ И И ИИ 00 00 00 00 00 00 00 00 00

f.F 65 йй 00 6F ИН ИИ R4 4Е

6D ИИ ИИ 7F: 2Й

65 ИИ ИИ 46 00

74 ЬЬ ИИ СВ 00

ИИ

ИИ

ИИ

ИИ

00 00 00 00 00 00 00 00 00 00 00 00 ИИ И И ИИ И И ИИ И И ИИ И И ИИ И И ИИ ИИ ИИ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ЙЙ

RETURN from f.

ASCII [ANSI - Су 00 00 00 гего0 oneQ someth Ln 63 69 6Е twcS ЬЬ ЬЬ ЬЬ g unknowns ИИ ИИ ИИ ЙВ 8Й В9 ■ Ѳ 4TuFifnНЯІ 00 00 00 М Hi* hN* ИИ

00 00 00 ИИ 00 00

ИИ

00 00 00 ИИ 00 ЙЙ

RETURN from f. ASCII ”pN#”

ИИ

00 00 00 ИИ 00 00

ЙЙ1

ЙП1pconffl

d':'A ►У8и

Рис. 1.46: OllyDbg: вторая DEC исполнилась OllyDbg п оказы вает, что условны й п ереход сейчас сработает.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

161

Указа те ль на с т р о к у «two» сейчас б уд е т записан в стек:

Рис. 1.47: OllyDbg: у ка з а те л ь на с т р о ку сейчас за п иш е тся на место первого а ргу м е нта О братите внимание: т е к у щ и й а р гу м е н т ф у н к ц и и это 2 и 2 прямо сейчас в сте ке по а дресу 0X001EF850.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

1 62

MOV за писы в а ет у ка з а те л ь на с т р о к у по а дресу 0X001EF850 (см. о кн о стека). Переход сработал. Это самая первая и н с т р у к ц и я ф у н к ц и и p r i n t f ( ) в MSVCR100.DLL (этот прим ер был ско м п и л и р о в а н с о п ци ей /MD): CPU - main thread, module MSVCR100

НМИЖЕІ ЬЕ44558b

6A tu­ bs 3S56446E ES C0B3FRFF 33C0 33F6 3975 08 0F95C0 3BC6 75 15 E8 72B2FRFF C700 16000001 ES D0590200 83C8 FF EB 5F E8 78E4FHFF

PUSH 0C PUSH 6E445630 CALL 6E3F0950 XOR ERX,ERX XOR ESI ESI 6E445592 CMP DWORD PTR SS:CEBP+8],ESI 6E445594 SETME AL 6E445597 CMP ERX,ESI 6E44559R JME SHORT 6E4455B3 6E44559C CRLL _errno 6E44559E MOU DWORD PTR DS:CEflX],16 6E4455R3 CRLL _ Lnva Lid_paranet er_nо Lnfо 6E4455R9 OR ERX,FFFFFFFF 6E4455RE JMP SHORT 6E445612 6E4455B1 CALL _Lob_func 6E4455B3 6E4455B8 PUSH 20 6A 20 POP EBX 5B 6E4455BH ADD ERX,EBX 03C3 6E4455BB PUSH EAX 50 6E4455BD 6E4455EE PUSH 1 6fi 01 E8 F453FBFF CALL 6E3FR9B9 6E4455C0 St ack [001EF84S]=feu.00FF3064 Inm=0000000C (dec і.гчаI 12.) 6E44558B 6E445590

INT MSUCR1 00. p i- Lntf (f orr j

С MSUCR100._errn о CONST 16 => EXDEU С MSUCR100._ Lnya LLd_parar

Нек dunp 65 72 ,-■'4 rV ь|67 20 75 hh l-h l-h 00FF3040 hb hl- hl-

00FF3050 00FF3060 00FF3070 00FF3080 00FF3090 00FF30R0 00FF30B0 00FF30C0

6F ин 6Е ыЬЬ ии

0R 00 00 00 6F ИИ ИИ И И ИИ г'З 6В 6Е 6F 77 6Е ИИ ИИ № ИИ ии 01 ии № ИИ 34 4У 28 21-1 ии ЬИ ИИ ии № ИИ ИИ

ИИ

ИИ

6Е 65 0R 00 61- 6U ЬЬ і^4 0Н 00 00 FF ИИ ИИ № ии Ь4 ,-'Ь 4ь СВ 4L ііН № и и

00 68 FF ии НВ ии ии

00 Ь^ FF ии УН ии ии

ИИ

ИИ

ИИ

ИИ

ИИ

ИИ

ИИ



ИИ

ИИ

ИИ

ИИ



ИИ

ИИ

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ИИ ИИ ИИ ИИ ИИ ИИ 00 ИИ ИИ ИИ ИИ № ИИ ИИ ИИ И И 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

RSCII "H(#"

few.00FF33R8 MSUCR100.prIntf 32b It 0(FFFFFFFF) 32b Lt 0(FFFFFFFF) 32b it 0(FFFFFFFF) 32b it 0(FFFFFFFF) 32b it 7EFDD000CFFF) 32b it 0(FFFFFFFF)

00

-MSUCR100.6E3FA9B9

EFL 00000246 (MO,MB,E,BE,MS,PE,GE,LE)

Ri-gl = 1

LastErr 00000000 ERROFLSUCCESS

0000 0000 0000 0000 0000 0000 0000 0000 0000 ИМ3 0000 0000 0000 0000 MM4 0000 0000 0000 0000 ММ0 0000 MM1 0000 MM2 0000

ASCII iRMSI - Cy 00 |его0 oneS 6L t wrQ soneth in FF g unknown^

ВУ ■ 01 И И И И 00 a И И И И ИИ ИИ ИИ ИИ т ИИ ИИ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00000000 6E494714 00000000 00000000 001EF84C 001EF894 00000001 00FF33RS 6E445584 ES 002В CS 0023 SS 002B DS 002В FS 0053 GS 002B

"Arg2

MSUCR100.printf Address 00FF3000 00FF3010 00FF3020 00FF3030

Registers (MMX)

ERX ECX EDX EBX ESP EBP ESI EDI EIP

о 4TuFir.nКЛІ Hi* hN*

001EF И IJ III II IІ Ш І Ш utiltroba и и h г о И 1 и Г.ГIur.l 1 rrui'l few ШГГ1И..И Ш1ЕРМ41 Ш 001EF353 00000001 ѳ 001EF85C 002R4E68 hN* ASCII "pNtt" 001EF360 002Н2848 Н(* 001EF864 466BRCR0 aMkF 001EF868 00000000 001EF36C 0 0 0 00000 001EF870 7EFDE000 И«'" 001EF374 00000000 001EF878 00000000 001EF87C 001EF864 d':'A 001EF330 D3339310 HH1FFRR4 ИЙ 1FFЯГІЙ PnintF

Рис. 1.48: OllyDbg: первая и н с т р у к ц и я в p r i n t f ( ) в MSVCR100.DLL Теперь p r i n t f ( ) счи та е т с т р о ку на 0X00FF3010 к а к свой е д и н с тв е н н ы й а р гу м е н т и вы во ди т стр оку.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

163

Это самая последняя и н с т р у к ц и я ф у н к ц и и p r i n t f ( ) : CPU - main ■ 6E4455DE ■ 6E4455DF ■ 6Е4455Е2 ■ 6Е4455Е7 ■ 6Е4455Е9 ■ 6Е4455ЕН ■ 6E4455EF ■ 6E4455F2 ■ 6E4455F7 ■ 6E4455F9 ■ 6E4455FH ■ 6E4455FB ■ 6Е445600 ■ 6Е445603 ■ 6Е44560О ■ 6E44560F ■

thread, module MSVCR100 50 PUSH EAX 56 PUSH ESI

Registers (MMX)

FF75 08 PUSH DUORD PTR SS:[EBP+8] Е8 49Е4FAFF CALL _Lob_func 03C3 ADD EAX,EBX 50 PUSH EAX Е8 2Е710100 CALL 6E45C71D 8945 Е4 NOU DWORD PTR SS:[EBP-1C],EAX Е8 39E4FAFF CALL _Lob_func 03C3 ADD EAX,EBX 50 PUSH EAX 57 PUSH EDI Е8 ACB0FBFF CALL 6E4006AC 83С4 18 ADD ESP,18 С745 FC FEFF MOU DWORD PTR SS:[EBF-4],-2 E8 09000000 CALL SE445618 8B45 E4 MOU EAX,DWORD PTR SS:LEBP-1C] 6Е445612 > E8 7EB3FAFF CALL 6E3F0995 RETH K=CE№Jh L- C3 6Е445618 Г§ E8 13E4FAFF CALL _Lob_func ■ 83C0 20 HDD EAX,20 Top of stack [001EF84C]=few.00FF1057 MSUCR100.pr Lntf+93 Address He:-: dump 00FF3000 7A 6Б 72 00FF3010 74 77 6F 00FF3020 67 20 75 00FF3030 FF FF FF 00FF3040 FE FF FF 00FF3050 01 00 00 00FF3060 00 00 00 00FF3070 00 00 00 00FF3080 00 00 00 00FF3090 00 00 00 00FF30R0 00 00 00 00FF30B0 00 00 00 00FF30C0 00 00 00

0H 00 00 00 6F 6E 6Б 0H 00 00 0 0 00 00 00 73 6F 6D 65 74 68 ев 6E 6F 77 6E 0H 00 00 FF FF 0 0 00 00 00 00 00 00 00 00 0 0

00 00

ffirg2 1 Argl L MSUCR100.6E4006AC

00000004 ЬE445617 0009DC88 00000000 001EF84C 001EF394 00000001 00FF33H3 6E445617 ES 002B CS 0023 SS 002B DS 002B FS 0053 GS 002B

NSUCR100.6E445617

few.00FF33P8 MSUCR100.6E445617 32bLt 0(FFFFFFFF) 32bLt 0tFFFFFFFF) 32bLt 0(FFFFFFFF) 32bLt 0(FFFFFFFF) 32bLt 7EFDD000(FFF) 32bLt 01FFFFFFFF) 00000000 ERROR_SUCCESS СNO„NB,E,BE,NS,PE,GE,LE)

ASCII (FINSI

69 6E twoG somethLn FF FF g unknowns 0 0 00

01 00 00 00 34 Б4 75 46 CB HE: 8н B9 ■

48 28 2A 00 68 4E 2A 00 00 0 0 0 0 00 0 0 0 00 00 00 00 00 00 00 00 0 0 0 0 00 0 0 00 00 00 00 00 00 00 00 0 0 0 0 00 0 0 00 00 00 00 00 00 00 00 0 0 0 0 00

ERX ECX EDX EBX ESP EBP ESI EDI EIF

0

4TuFiiViKil

H(* hN#

00 00 00 00 00 00 00 00 00 00 00 00 0 0 00 00 00 00 00 00 00 00 0 0 0 0 00 0 0 00 00 00 00 00 00 00 00 0 0 0 0 00 0 0 00 00 00 00 00 00 00 00 0 0 0 0 00

001EF850 001EF854 001EF858 001EF85C 001EF860 001EF864 001EF868 001EF86C 001EF870 001EF874 001EF878 001EF87C

001EF880

тш ш

RETURN fron f ASCII "twcGT RETURN from f ASCII "pN#"

P.-.intF-r

Рис. 1.49: OllyDbg: последняя и н с т р у к ц и я в p r i n t f ( ) в MSVCR100.DLL Строка «two» была то л ь ко что выведена в консоли.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

t

164

Нажм ем F7 или F8 (сделать шаг, не входя в ф у н кц и ю ) и вернемся...нет, не в ф у н к ц и ю f ( ) но в m a in ( ) :

Рис. 1.50: OllyDbg: возврат в m a in () Да, это прямой п ереход из вн утр е н н о с те й p r i n t f ( ) в m a in ( ) . Потому к а к RA в сте ке у ка з ы в а е т не на ка ко е -то место в ф у н к ц и и f ( ) а в m a in ( ) . И CALL 0x00FF1000 это и н с т р у к ц и я вызывающ ая ф ункцию f ( ) .

A R M : О п т и м и з и р у ю щ и й К е і І 6 /2 0 1 3 ( Р е ж и м A R M )

8



8

»

Г)

1 — U1

о о

0

О О

Г+

t

X

(D

E 0



4 1 0 0 0 0 0 t: x e t

0 0

"П О

0 0

F 8 F 0

6 1 0 0 0 0 0 t: x e t

о

#

CMP ADREQ BEQ CMP ADREQ BEQ CMP ADRNE ADREQ

О

E3 02 0A E3 02 0A E3 12 02

1:

tJ

00 00 50 . text:00000150 13 . text:00000154 05 00 00 . text:00000158 01 00 50 4B 0F . t e x t :00000160 02 00 . t e x t :00000164 02 50 . t e x t :00000168 4A 8F 4E . t e x t :00000170 . t e x t :00000170 . t e x t :00000170 . t e x t :00000170 78 18 00

R

ч-

. text:0000014C

R0, aZero ; "z e ro \n " lo c 170 R0, #1 R0, aOne ; "one\n" loc_170 R0, #2 R0, aSomethingUnkno ; "something unknown\n" R0, aTwo ; "two\n"

lo c _ 1 7 0 : ; CODE XREF: f1+8 ; f1+14 EA B __2 p r i n t f

Мы снова не см ож ем сказать, глядя на э т о т код, был ли в о р и ги н а л ь н о м исходном коде switch() либо ж е н е с ко л ько опер а тор о в if(). Т а к или иначе, мы снова видим здесь и н с т р у к ц и и с п р е д и ка та м и , например, ADREQ ((Equal)), которая б уд е т исполняться т о л ь ко если R0 = 0, и т о гд а в R0 б у д е т з а гр у ж е н адрес с т р о ки «zero\n». С ледующ ая и н с т р у кц и я BEQ п е р е н а п р а в и т исполнение на lo c _ 1 7 0 , если R0 = 0. Кстати, на бл ю д а тел ьн ы й чи тате ль м о ж е т спросить, сраб о та ет ли BEQ нормально, ведь ADREQ пе­ ред ним у ж е заполнила р е ги с т р R0 чем-то другим ? Сработает, п о том у что BEQ п р оверяет флаги, у ста н ов л е н н ы е и н с т р у к ц и е й CMP, а ADREQ ф лаги н и к а к не м од иф ицирует. Далее всё просто и знаком о. Вызов p r i n t f ( ) один, и в самом конце, мы у ж е рассм атривали подоб­ ный т р ю к (1.8.2 (стр. 5 3 )). К вызову ф у н к ц и и p r i n t f ( ) в ко н ц е в е дут тр и пути. (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

165

Последняя и н с т р у к ц и я CMP R0, #2 здесь н у ж н а , чтобы у зн а ть а = 2 или нет. Если это не т а к, то при помощ и ADRNE (NotEqual) в R0 б у д е т з а гр у ж е н у ка з а те л ь на с т р о к у «something а у ж е было проверено на 0 и 1 до этого, и здесь a т очно не п о п а д а е т под эти ко н стан ты .

unknown \n», ведь

Ну а если R0 = 2, в R0 б уд е т з а гр у ж е н у ка з а т е л ь на с т р о к у «two\n» при помощ и и н с т р у к ц и и ADREQ.

A R M : О п т и м и з и р у ю щ и й ^ ! 6 /2 0 1 3 ( Р е ж и м T h u m b ) .te x t .te x t .te x t .te x t .te x t .te x t .te x t .te x t .te x t .te x t

000000D4 000000D4 000000D6 000000D8 000000DA 000000DC 000000DE 000000E0 000000E2 000000E4

10 00 05 01 05 02 05 91 04

B5 28 D0 28 D0 28 D0 A0 E0

f1 : PUSH CMP BEQ CMP BEQ CMP BEQ ADR B

{R4,LR} R0, #0 zero case R0, #1 one case R0, #2 two_case R0, aSomethingUnkno ; "something unknown\n" d efault_case

. t e x t 000000E6 . t e x t 000000E6 95 A0 . t e x t 000000E8 02 E0

zero _case: ; CODE XREF: f1+4 ADR R0, aZero ; "ze ro \n " B d efault_case

. t e x t 000000EA . t e x t 000000EA 96 A0 . t e x t 000000EC 00 E0

one case: ; CODE XREF: f1+8 ADR R0, aOne ; "one\n" B d efault_case

.te x t .te x t .te x t .te x t .te x t .te x t

two_ case: ; CODE XREF: f1+C ADR R0, aTwo ; "two\n" d e f a u lt case ; CODE XREF: f1+10 ; f1+14 BL 2 p rin tf POP {R4,PC}

000000EE 000000EE 97 A0 000000F0 000000F0 000000F0 06 F0 7E F8 000000F4 10 BD

К а к у ж е было отмечено, в T h u m b -р е ж и м е нет в о зм о ж н о с ти добавлять условны е п р е д и ка т ы к боль­ ш и н ств у и н с т р у к ц и й , т а к что T h u m b -ко д вышел п о х о ж и м на ко д x86 в стиле CISC, вполне понятны й.

A R M 6 4 : Н е о п т и м и з и р у ю щ и й G C C ( L in a r o ) 4 .9 . LC12: . s t r i n g "zero" . LC13: . s t r i n g "one" . LC14: . s t r i n g "two" . LC15: . s t r i n g "something unknown" f1 2 : stp add s tr ld r cmp beq cmp beq cmp bne adrp add bl b

x29, x30, [sp, - 3 2 ]! x29, sp, 0 w0, [x29 ,28] w0, [x29 ,28] w0, 1 . L34 w0, 2 . L35 w0, wzr .L38 ; переход на метку по умолчанию x 0 , . LC12 ; "zero" x 0 , x 0 , : lo 1 2 : . LC12 puts . L32

adrp add

x 0 , . LC13 ; "one" x 0 , x 0 , : lo 1 2 : . LC13

. L34:

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

166

bl b

puts . L32

adrp add bl b

x 0 , . LC14 ; "two" x0, x0, :lo 12 : LC14 puts .L32

adrp add bl nop

x 0 , . LC15 ; "something unknown" x0, x0, :lo 12 : LC15 puts

ld p re t

x29, x30, [sp]

. L35:

. L38:

. L32: 2 3

Входное значение им еет т и п int, поэтом у для него используется р е ги стр W0, а не целая часть ре­ гистра X0. У каза те ли на с т р о ки п е ред аю тся в p u t s ( ) при помощ и пары и н с т р у к ц и й ADRP/ADD, к а к было п о к а ­ зано в прим ере «Hello, world!»: 1.5.3 (стр. 23).

A R M 6 4 : О п т и м и з и р у ю щ и й G C C ( L in a r o ) 4 .9 f1 2 : 0 w

1

w О

2

cmp beq . L31 cmp beq .L32 cbz w0, . L35 ; метка по умолчанию adrp x 0 , . LC15 ; "something unknown" add x0, x0, :lo 12 : . LC15 b puts . L35: adrp x 0 , . LC12 ; "zero" add x0, x0, :lo 12 : . LC12 b puts . L32: adrp x 0 , . LC14 ; "two" add x0, x0, :lo 12 : . LC14 b puts .L31: adrp x 0 , . LC13 ; "one" add x0, x0, :lo 12 : . LC13 b puts Ф р а гм е н т кода более о птим и зи р о в а н н ы й . И н с тр у кц и я CBZ (Compare and Branch on Zero — ср авн ить и п е ре йти если ноль) соверш ает переход если W0 ноль. Здесь т а к ж е прямой переход на p u t s ( ) вместо вызова, к а к у ж е было описано: 1.16.1 (стр. 156).

MIPS Л и с т и н г 1.152: О птим изирую щ ийG C C 4.4.5 (IDA) f: lu i

$gp,

(__gnu_local_gp >> 16)

li beq la

$v0, 1 $a0, $v0, loc_60 $gp, (__gnu_local_gp & 0xFFFF)

; это 1?

; branch

; это 2? li $v0, 2 beq $a0, $v0, loc_4C or $at, $zero ; branch delay s lo t , не равно 0:

NOP

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

167

bnez or

$a0, loc_38 $at, $zero ; branch delay s l o t , NOP

lu i lw or jr la

$a0, $ t9 , $at, $t9 $a0,

lu i lw or jr la

# CODE XREF: f+1C $a0, ( $LC3 > > 1 6 ) # "something unknown" $ t9 , (puts $at, $zero ; load delay s lo t , $t9 $a0, & 0xFFFF) # "something unknown" ; branch delay s lo t

lu i lw or jr la

# CODE XREF: f+14 $a0, ( $LC2 > > 1 6 ) # "two" $ t9 , (puts $at, $zero ; load delay s lo t , $t9 $a0, & 0xFFFF) # "two" ; branch delay s lo t

lu i lw or jr la

# CODE XREF: f+8 $a0, ( $LC1 >> 16) # "one" $ t9 , (puts $at, $zero ; load delay s lo t , $t9 $a0, & 0xFFFF) # "one" ; branch delay s lo t

; случай нуля:

P O N

loc_38:

( $LC0 > > 1 6 ) # "zero" (puts & 0xFFFF)($gp) $zero ; load delay s lo t , branch delay s l o t , NOP ( $LC0 & 0xFFFF) # "zero'

; branch delay s l o t

g $

CL

F F F F x 0 &

P O N

J

_

3 C $

loc_4C:

g $

CL

F F F F x 0 &

P O N

J

_

2 C $

lo c _ 6 0 :

g $

CL

F F F F x 0 &

P O N

J

_

1 C $

Ф у н к ц и я всегда за ка н чи в ае тся вызовом p u t s ( ) , т а к что здесь мы видим переход на p u t s ( ) (JR: «Jump Register») вместо перехода с сохранением RA («jum p and link»). Мы говорили об этом ранее: 1.16.1 (стр. 1 56 ). Мы т а к ж е часто видим NOP-и н с т р у к ц и и после LW. Это «load delay slot»: ещё один delay slot в MIPS. И н с тр укц и я после LW м о ж е т исполняться в т о т момент, ко гд а LW з а г р у ж а е т значение из памяти. Впрочем, сл едую щ ая и н с т р у кц и я не д о л ж н а использовать р езул ьта т LW. Современные MIPS-процессоры ж д у т , если сл едую щ ая и н с т р у кц и я испо л ьзует р езул ьта т LW, т а к что всё это у ж е устарело, но GCC всё еще добавляет NOP-ы для более с та ры х процессоров. Вообще, это м о ж н о игн о р ир ова ть.

Вывод О ператор switch() с малым количеством вариантов т р у д н о отличим от пр им енения к о н с т р у к ц и и л и с т и н г . 1 .1 6 .1.

if/else:

1.16.2. И если много Если ветвлений с л иш ком много, то ге н е р и р о в а ть с л иш ком д л и н н ы й ко д с м н огочисл е н н ы м и JE/JNE у ж е не т а к удобно. # in clu d e < s td io .h >

a c

( ( ( ( (

; break; "one\n"); break; "tw o \n "); break; "th re e \n " ); break " f o u r \ n " ) ; break; ("something unknown\n"); break; n \ or e z

e s a a c c

void f ( i n t a) { switch (a) { 0: p r i n t f se 1: p r i n t f ca se 2: p r i n t f ca se 3: p r i n t f se 4: p r i n t f d e f a u lt : p r i n t f

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

168

}; i n t main() { f (2 ); / / t e s t };

x86 Н еоптимизирую щ ийM SVC Рассмотрим пример, с ко м п и л и р о в а н н ы й в (MSVC 2010): Л и с т и н г 1.153: MSVC 2010 tv64 = -4 8 =

a 1 f

; size = 4 ; size = 4

PROC push ebp mov ebp, esp push ecx mov eax, DWORD PTR _a$[ebp] mov DWORD PTR tv 6 4 [e b p ], eax cmp DWORD PTR tv 6 4 [e b p ], 4 SHORT $LN1@f ja mov ecx, DWORD PTR tv64[ebp] DWORD PTR $LN11@f[ecx*4] jmp $LN6@f: push OFFSET $SG739 ; 'z e r o ', 0aH, 00H c a ll _ p r in tf add esp, 4 SHORT $LN9@f jmp $LN5@f: push OFFSET $SG741 ; 'o n e ', 0aH, 00H c a ll _ p r in tf add esp, 4 SHORT $LN9@f jmp $LN4@f: push OFFSET $SG743 ; 't w o ', 0aH, 00H c a ll _ p r in tf add esp, 4 SHORT $LN9@f jmp $LN3@f: push OFFSET $SG745 ; 't h r e e ' , 0aH, 00H c a ll _ p r in tf add esp, 4 SHORT $LN9@f jmp $LN2@f: push OFFSET $SG747 ; ' f o u r ' , 0aH, 00H c a ll _ p r in tf add esp, 4 SHORT $LN9@f jmp $LN1@f: push OFFSET $SG749 ; 'something unknown', 0aH, 00H c a ll _ p r in tf add esp, 4 $LN9@f: mov esp, ebp ebp pop re t 0 npad 2 ; выровнять следующую метку $LN11@f: DD $LN6@f ; 0 DD $LN5@f ; 1 DD $LN4@f ; 2 DD $LN3@f ; 3 DD $LN2@f ; 4 f ENDP (В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

169

Здесь п р о и с х о д и т следую щ ее: в теле ф у н к ц и и есть набор вызовов p r i n t f ( ) с разными а р г у м е н ­ тами. Все они имеют, ко н еч н о ж е , адреса, а т а к ж е вн утр е н н и е сим вол иче ские м етки, которы е присвоил им ко м п ил я то р . Т а к ж е все эти м е тки ука зы в а ю тся во вн утр е н н е й та б ли це $LN11@f. В начале ф ун кц и и , если а больше 4, то сразу п р о и с х о д и т переход на м е т ку $LN1@f, где вызывается p r i n t f ( ) с а р гу м е н т о м 's o m e th in g unknown'. А если а м еньш е или равно 4, то это значение у м н о ж а е т с я на 4 и прибавляется адрес таблицы с переходам и ($LN11@f). Т аким образом, получается адрес в н утр и таблицы , гд е л е ж и т н у ж н ы й адрес в н утр и тела ф ун кц и и . Например, возьмем а равным 2. 2 * 4 = 8 (ведь все элем енты та б ли цы — это адреса внутри 3 2 -б и тн о го процесса, т а к и м образом, к а ж д ы й элем ент за н им а е т 4 байта). 8 приб авить к $LN11@f — это б у д е т эл ем ент таблицы , гд е л е ж и т $LN4@f. JMP в ы т а с ки в а е т из т аблицы адрес $LN4@f и д е ла е т безусловны й переход туда. Эта таблица иногд а называется ju m ptab le или branch table92. А т а м вызывается p r i n t f ( ) с а р г у м е н т о м ' t w o ' . Дословно, и н с т р у кц и я jmp DWORD PTR $LN 11@ f[ecx*4] о зн ач а е т перейти по DWORD, который лежит по адресу $LN11@f + ecx * 4. npad (.1.7 (стр. 9 9 9 )) это макрос ассемблера, в ы равниваю щ ий начало таблицы , чтобы она распо­ лагалась по а дресу кр а т н о м у 4 (или 16). Это н у ж н о для того, чтобы процессор мог эф ф ективнее з а гр у ж а т ь 32-битны е значения из пам яти через ш и ну с памятью, кэш -пам ять, итд.

92Сам метод раньше назывался c o m p u te d GOTO В ранних версиях Фортрана: wikipedia. Не очень-то и полезно в наше время, но каков термин!

(В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

170

O lly D b g Попробуем э то т пример в OllyDbg. Входное значение ф у н к ц и и (2) з а гр у ж а е т с я в EAX: CPU - main thread, module lot

010B1000 rs 010B1001 010B1003 010B1004 010B100H 010B100E 010B1010 010B1013 010В101Й 010B101F

010B102E

EE 8BEC El 8B4E 08 894E FC 837D FC 04 77 ЕЙ 8B4D FC FF248D 7C100I 68 00300B01 FF1E Й0200В0 83C4 04 ЕВ 4E 68 08300B01 FF1E Й0200В0 83C4 04 ЕВ 3E 68 10300B01 FF1E Й0200В0 83C4 04 ЕВ 2E

PUSH EBP NOU EBP,ESP pijg|_| ECX MOU EPX,DWORD PTR SS:[EBP+8] MOU DWORD PTR SS: СЕВР-4] ,ERX CMP DWORD PTR SS:[EBP-4],4 JR SHORT 01ѲВ1Ѳ6Й MOU ECX,DWORD PTR SS:[EBP-4] JMP DWORD PTR DS: [ECX#4+10B107C] PUSH OFFSET 010B3000 CALL DWORD PTR DS: [ 5), вы полнится переход на м е т ку default_case. Но если

ДО

< 5 и ADDCC сработает, то п р о и зо й д е т следую щ ее.

Значение в R0 у м н о ж а е т с я на 4. Ф а кт и ч е с ки , LSL#2 в суф ф иксе и н с т р у к ц и и о зн ач а е т « с д ви г влево на 2 бита». Но к а к б уд е т видно п о зж е (1.19.2 (стр. 2 1 8 )) в с е кц и и «Сдвиги», с д в и г влево на 2 бита, это э кв и в а ­ л е нтн о его у м н о ж е н и ю на 4. Затем полученное ДО * 4 прибавляется к т е к у щ е м у зн а чен ию PC, совершая, та к и м образом, переход на о д н у из р а с п о л о ж е н н ы х н и ж е и н с т р у к ц и й B (Branch). На м ом ент исполнения ADDCC, с о д е р ж и м о е PC на 8 б айт больше (0x180), чем адрес по ко тор о м у распол ож ена сама и н с т р у кц и я ADDCC (0x178), либо, говоря иным языком, на 2 и н с т р у к ц и и больше. Это связано с работой конвейера процессора ARM: пока исполняется и н с т р у к ц и я ADDCC, процессор у ж е н а ч ин ае т обрабаты вать и н с т р у к ц и ю после следую щ ей, поэтом у PC у ка зы в а е т туд а. Э тот ф а кт н у ж н о запом нить. Если а = 0, т о гд а к PC н иче го не б уд е т прибавлено и в PC з а п иш е тся а кт у а л ь н ы й на т о т мом ент PC (ко то р ы й больше на 8) и п р о и зо й д е т переход на м е т к у loc_180. Это на 8 б айт дальш е места, где н а ход ится и н с т р у к ц и я ADDCC. Если

а

= 1, т о гд а в PC за п иш е тся

При к а ж д о й добавленной к

а

P C

+8 +

а*4 = P C

+8 + 1* 4 =

PC

+ 12 = 0ж184. Это адрес м е тки loc_184.

е д и н и ц е и то го в ы й PC ув еличивается на 4.

4 это длина и н с т р у к ц и и в р е ж и м е ARM и одно вр ем е н н о с этим, длина к а ж д о й и н с т р у к ц и и B, их здесь сл е дуе т 5 в ряд. 95ДОО—складывание чисел

(В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

176

К а ж д а я из э ти х пяти и н с т р у к ц и й B пе р е д а е т уп р ав л е н ие дальш е, гд е собственно и п р о и с х о д и т то, что за п ро гр а м м ир ов а н о в о ператоре switch(). Там п р о и сх о д и т за гр у з ка у ка з а те л я на свою стр оку, итд.

A R M : О п т и м и з и р у ю щ и й ^ ! 6 /2 0 1 3 ( Р е ж и м T h u m b ) Л и с т и н г 1.156: О пти м и зи р у ю щ и й К е іІ 6/2013 (Режим Thum b)

0

о о о о о "П гп

000000F6 000000F6 000000F6 10 B5 000000F8 03 00 000000FA 06 F0 69 F8

000000FF 00000105 00000106 00000106 00000106 00000108

05 04 06 08 0A 0C 10 00

8D A0 06 E0

EXPORT f2 f2 PUSH MOVS BL

{R4,LR} R3, R0 __ARM_common_switch8_thumb ; switch 6 cases

DCB 5 DCB 4, 6, 8, 0xA, 0xC, 0x10 ; jump ta b le f o r switch statement ALIGN 2 zero case ; CODE XREF: f2+4 ADR R0, aZero ; jumptable 000000FA case 0 B loc_118

0000010A 0000010A 0000010A 8E A0 0000010C 04 E0

one case ; CODE XREF: f2+4 ADR R0, aOne ; jumptable 000000FA case 1 B loc_118

0000010E 0000010E 0000010E 8F A0 00000110 02 E0

two case ; CODE XREF: f2+4 ADR R0, aTwo ; jumptable 000000FA case 2 B loc_118

00000112 00000112 00000112 90 A0 00000114 00 E0

three case ; CODE XREF: f2+4 ADR R0, aThree ; jumptable 000000FA case 3 B loc_118

00000116 00000116 00000116 91 A0 00000118 00000118 00000118 00000118 06 F0 6A F8 0000011C 10 BD

loc_118 ; CODE XREF: f2+12 ; f2+16 BL 2 p rin tf POP {R4,PC}

82 A0 00000120 FA E7

d e f a u lt case ; CODE XREF: f2+4 ADR R0, aSomethingUnkno ; jumptable 000000FA d e f a u lt case B loc_118

000061D0 000061D0 000061D0 78 47

EXPORT ARM common switch8 thumb __ARM_common_switch8_thumb ; CODE XREF: example6_f2+4 BX PC

E 1 1 0 0 0 0

О

0000011E 0000011E

fo u r case ; CODE XREF: f2+4 ADR R0, aFour ; jumptable 000000FA case 4

000061D2 00 00 ALIGN 4 000061D2 ; End o f fu n c tio n ARM common switch8 thumb 000061D2 000061D4 __32__ARM_common_switch8_thumb ; CODE XREF: ARM common switch8 _thumb 000061D4 01 C0 5E E5 LDRB R12, [LR,#-1] 000061D8 0C 00 53 E1 CMP R3, R12 000061DC 0C 30 DE 27 LDRCSB R3, [ LR,R12] 000061E0 03 30 DE 37 LDRCCB R3, [LR,R3] 000061E4 83 C0 8E E0 ADD R12, LR, R3,LSL#1 000061E8 1C FF 2F E1 BX R12 000061E8 ; End o f fu n c tio n 32 ARM common switch8 thumb

(В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

177

В р е ж и м а х T h u m b и Thum b-2 у ж е нельзя надеяться на то, что все и н с т р у к ц и и им ею т о д н у длину. М о ж н о д а ж е сказать, что в этих р е ж и м а х и н с т р у к ц и и перем енной длины, к а к в x86. Т а к что здесь добавляется с пециал ьная таблица, с о д е р ж а щ а я инф ормацию о том, к а к м ного ва­ риантов здесь, не включая варианта по ум олчанию , и смещ ения, для к а ж д о г о варианта. К а ж д о е см ещ ение к о д и р у е т м етку, куд а н у ж н о пе ре д ать упр ав л е н ие в со о тв е тс тв у ю щ е м случае. Для т о го чтобы работать с та б л и ц е й и со вер ш и ть переход, вызывается сл уж е б н а я ф ун кц и я

__ARM_common_switch8_thumb. Она н ачинается с и н с т р у к ц и и BX PC, чья ф у н кц и я — п е р е кл ю ч и ть п роцессор в ARM-р еж им . Далее ф ун кц и я , работающ ая с таблицей. Она с л иш ком сл о ж н а я для рассм отрения в данном месте, т а к что п р оп устим это. Но м о ж н о отм е тить , что эта ф у н кц и я исп о л ь зует р е ги с тр LR к а к у ка за т е л ь на таблицу. Д ейст ви тел ь н о , после вызова этой ф ун кц и и , в LR был записан адрес после и н с т р у кц и и BL __ARM_common_switch8_thumb, а там к а к раз и начинается таблица. Ещё м о ж н о о тм е ти ть , что ко д для это го выделен в о тд е л ь н у ю ф у н к ц и ю для то го , чтобы не н у ж н о было к а ж д ы й раз ге н е р и р о в а ть то чн о т а к о й ж е ф р а гм е н т кода для к а ж д о г о в ы ра ж е н и я switch(). IDA распознала эту с л у ж е б н у ю ф у н кц и ю и та б л и ц у а в то м а ти ч е с ки дописала ко м м е н та р и и к м еткам вроде ju m p t a b le 000000FA case 0.

MIPS Л и с т и н г 1.157: О птим изирую щ ийG C C 4.4.5 (IDA) f: lu i $gp, (__gnu_local_gp >> 16) ; перейти на loc_24, если входное значение меньше 5: s ltiu $v0, $a0, 5 bnez $v0, loc_24 la $gp, (__gnu_local_gp & 0xFFFF) ; branch delay s l o t ; входное значение больше или равно 5 ; вывести "something unknown" и закончить: lu i $a0, ($LC5 >> 16) # "something unknown" lw $t9, (puts & 0xFFFF)($gp) or $at, $zero ; NOP jr $t9 la $a0, ($LC5 & 0xFFFF) # "something unknown" ; branch delay s lo t lo c _ 2 4 : # CODE XREF: f+8 ; загрузить адрес таблицы переходов ; LA это псевдоинструкция, на самом деле здесь пара LUI и ADDIU: la $v0, off_120 ; умножить входное значение на 4: s ll $a0, 2 ; прибавить умноженное значение к адресу таблицы: addu $a0, $v0, $a0 ; загрузить элемент из таблицы переходов: lw $v0, 0($a0) or $at, $zero ; NOP ; перейти по адресу, полученному из таблицы: jr $v0 or $at, $zero ; branch delay s l o t , NOP sub_44: ; вывести "th re e " и закончить lu i $a0, ( $LC3 lw $t9, (puts or $at, $zero jr $t9 la $a0, ($LC3 sub_58: ; вывести " fo u r" и закончить (В целях сбора с т а т и с т и к и )

# DATA XREF: .rodata:0000012C > > 1 6 ) # "th re e " & 0xFFFF)($gp) ; NOP & 0xFFFF)

# "th re e " ; branch delay s lo t

# DATA XREF: .rodata:00000130

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

178

$

CL

$a0, > > 1 6 ) # " fo u r" $ t9 , ( puts $at, $zero ; NOP $t9 $a0, & 0xFFFF) # "fo u r" LJ_ LJ_ LJ_ LJ_ x 0 t-3 4 C L $ 4 C L $

lu i lw or jr la

; branch delay s l o t

$

CL

F F F F x 0 &

sub_6C: # DATA XREF: rodata:off_120 ; вывести "zero " и закончить lu i $a0, ( $LC0 > > 1 6 ) # "zero" lw $ t9 , ( puts or $at, $zero ; NOP $t9 jr la $a0, & 0xFFFF) # "zero" ; branch delay s l o t 0 C L $

rodata:00000124

sub_94: # DATA XREF: ; вывести "two" и закончить lu i $a0, ( $LC2 > > 1 6 ) # "two" lw $ t9 , ( puts & 0xFFFF)($gp) or $at, $zero ; NOP jr $t9 la $a0, & 0xFFFF) # "two"

rodata:00000128

1 C L $

sub_80: # DATA XREF: ; вывести "one" и закончить lu i $a0, ( $LC1 >> 16) # "one" lw $ t9 , ( puts & 0xFFFF)($gp) or $at, $zero ; NOP jr $t9 la $a0, & 0xFFFF) # "one"

branch delay s lo t

2 C L $

branch delay s lo t

; может быть размещено в секции .rodata: off_120: .word sub_6C .word sub_80 .word sub_94 .word sub_44 .word sub 58 Новая для нас и н с т р у кц и я здесь это SLTIU («Set on Less Than Im m ediate Unsigned» — у ста н ов ить, если меньше чем значение, б еззнаковое сравнение). На самом деле, это то ж е что и SLTU («Set on Less Than Unsigned»), но «I» о значает « im m ediate», т.е. число м о ж е т быть задано в самой и н с тр у кц и и . BNEZ это «Branch if Not Equal to Zero» (переход если не равно нулю). Код очень п о х о ж на ко д для д р у ги х ISA. SLL («Shift Word Left Logical» — л о ги ч е с ки й с д в и г влево) со вер ш а е т у м н о ж е н и е на 4. MIPS всё-таки это 32-битны й процессор, т а к что все адреса в таблице переходов (jumptable) 32-битные.

Вывод Прим ерны й с ке л е т оператора switch(): Л и с т и н г 1.158: x86 MOV REG, in p u t CMP REG, 4 ; максимальное количество меток JA d e f a u lt SHL REG, 2 ; найти элемент в таблице. сдвинуть на 3 бита в x64 MOV REG, jump_table[REG] JMP REG case1: ; делать что-то JMP e x i t case2: ; делать что-то JMP e x i t case3:

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

179

; делать что-то JMP e x i t case4: ; делать что-то JMP e x i t case5: ; делать что-то JMP e x i t d e fa u lt:

e x it:

jump ta b le dd dd dd dd dd

case1 case2 case3 case4 case5

Переход по а дресу из та б ли цы переходов м о ж е т быть т а к ж е реализован т а к о й и н с тр у кц и е й : JMP ju m p _ ta b le [R E G * 4 ]. Или JMP ju m p _ ta b le [R E G *8 ] в x64. Таблица переходов (jumptable) это просто массив у ка за тел е й, к а к это б у д е т вскоре описано: 1.21.5 (стр. 2 8 6 ).

1.16.3. Когда много case в одном блоке Вот очень часто используемая ко н с т р у к ц и я : н е с ко л ь ко case м о ж е т быть использовано в одном блоке: # in clu d e < s td io .h > void f ( i n t a) { switch (a) { case 1: case 2: case 7: case 10 p rin tf break; case 3: case 4: case 5: case 6: p rin tf break; case 8: case 9: case 20: case 21: p rin tf break; case 22: p rin tf break; d e f a u lt: p rin tf break; }; };

("1 , 2, 7, 10\n" ) ;

("3 , 4, 5 \ n " ) ;

("8 , 9, 2 1 \n ") ;

( " 2 2 \ n " );

( " d e f a u l t \ n " );

i n t main( (В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

180

{ f(4 ); }; Слиш ком расто чите л ьн о ге н е р и р о в а ть к а ж д ы й б л о к для к а ж д о г о случая, п оэтом у обычно ге н е р и ­ руется к а ж д ы й б л о к плюс н е кий диспетчер.

MSVC Л и с т и н г 1.159: О п т и м и з и р у ю щ и й І^ Ѵ С 2010

6 0 8 2 G S

_a$ = 8 PROC _f mov dec cmp ja movzx jmp $LN5@f: mov jmp $LN4@f: mov jmp $LN3@f: mov jmp $LN2@f: mov jmp $LN1@f: mov jmp npad $LN11@f: DD DD DD DD DD $LN10@f: DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB

1' 7 5 2 2, 4, 9

B D 4 0 8 2 G 5 DB

1 0 ', 0aH, 00H 0aH, 00H , 0aH, 00H '2 2 ', 0aH, 00H ' d e f a u l t ', 0aH, 00H 3 8

2 0 8 2 G S

$SG2800 DB DB

1 ,

B D 8 9 7 2 G S

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

eax, DWORD PTR _a$[esp-4] eax eax, 21 SHORT $LN1@f eax, BYTE PTR $LN10@f[eax] DWORD PTR $LN11@f[eax*4] DWORD PTR _ a $ [e s p -4 ], OFFSET $SG2798 ; '1, 2, 7, 10' DWORD PTR __imp__p r i n t f DWORD PTR _ a $ [e s p -4 ], OFFSET $SG2800 ; '3, 4, 5' DWORD PTR __imp__p r i n t f DWORD PTR _ a $ [e s p -4 ], OFFSET $SG2802 ; '8, 9, 21' DWORD PTR __imp__p r i n t f DWORD PTR _ a $ [e s p -4 ], OFFSET $SG2804 ; '22' DWORD PTR __imp__p r i n t f DWORD PTR _ a $ [e s p -4 ], OFFSET $SG2806 ; 'd e f a u l t ' DWORD PTR __imp__p r i n t f 2 ; выровнять таблицу $LN11@f по 16-байтной границе $LN5@f $LN4@f $LN3@f $LN2@f $LN1@f 0 0 1 1 1 1 0 2 2 0 4 4 4 4 4 4 4 4 4 2

; ; ; ; ;

вывести вывести вывести вывести вывести

1, 2, 7, 10' 3, 4, 5' 8, 9, 21' 22' d e f a u lt '

a=1 a=2 a=3 a=4 a=5 a=6 a=7 a=8 a=9 a=10 a=11 a=12 a=13 a=14 a=15 a=16 a=17 a=18 a=19 a=20

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

181

2 3

2 2 = a

_f

DB DB ENDP

1 2 = a

58 59 60

Здесь видим две таблицы : первая таблица ($LN10@f) это таблица индексов, а вторая таблица ($LN11@f) это массив у ка з а те л е й на блоки. В начале, входное значение используется к а к и н д е кс в та б ли це и н д е кс ов (строка 13). Вот кр а т к о е описан ие зна чений в таблице: 0 это первый б л о к case (для зна чений 1, 2, 7, 10), 1 это второй (для зна чений 3, 4, 5), 2 это тр е т и й (для зна чений 8, 9, 21), 3 это ч е тв ер ты й (для значений 22), 4 это для блока по ум олчанию . Мы получаем и н д е кс для второй та б ли цы у ка за т е л е й на блоки и переходим ту д а (строка 14). Ещё н у ж н о о т м е ти т ь то, что здесь нет случая для нулевого вх о д н о го значения. Поэтому мы видим и н с т р у к ц и ю DEC на с т р о ке 10 и таблица начинается с а = 1. Потому что незачем вы делять в та б ли це эл ем ент для а = 0. Это очень часто используем ы й шаблон. В чем ж е экономия? Почему нельзя сделать т а к, к а к у ж е о бсуж д а л о с ь (1.16.2 (стр. 173)), используя т о л ь ко о д н у таб ли цу, с о д е р ж а щ у ю у ка з а те л и на блоки? Причина в том, что элем енты в таблице и н д е кс ов за н и м а ю т т о л ь ко по 8-б итн о м у байту, поэтом у всё это более ко м п а ктн о .

GCC GCC д е ла е т т а к, к а к у ж е о бсуж д а л о с ь ( 1.16.2 (стр. 173)), используя просто та б л и ц у у ка за тел е й.

A R M 6 4 : О п т и м и з и р у ю щ и й G C C 4 .9 .1 Во-первых, здесь нет кода, ср аб а ты в а ю щ е го в случае если входное значение — 0, т а к что GCC пы тается сделать та б л и ц у переходов более к о м п а к т н о й и н а ч ин ае т со случая, ко гд а входное зн а ­ чение — 1. GCC 4.9.1 для ARM64 испо л ь зует д а ж е более ин те ре сн ы й тр ю к. Он м о ж е т з а ко д и р о в а ть все см е­ щения к а к 8-битны е байты. Вспомним, что все и н с т р у к ц и и в ARM64 им ею т размер в 4 байта. GCC т а к ж е исп о л ь зует т о т ф акт, что все см ещ ения в моем кр о х о тн о м примере находятся д о с т а ­ то чн о б л изко д р у г о т д руга. Т а к что таблица переходов с о с то и т из байт. Л и с т и н г 1.160: О птимизирую щ ийG C C 4.9.1 ARM64 f1 4 : ; входное значение в W0 sub w0, w0, #1 cmp w0, 21 ; переход если меньше или равно (беззнаковое): bis .L9 . L2: ; вывести " d e f a u lt " : adrp x0, . LC4 add x0, x0, :lo12:.LC 4 b puts . L9: ; загрузить адрес таблицы переходов в X1: adrp x1, .L4 add x1, x1, : lo 1 2 : . L4 ; W0=input_value-1 ; загрузить байт из таблицы: id rb w0, [x1,w0,uxtw] ; загрузить адрес метки L rtx : adr x1, .L rtx 4 ; умножить элемент из таблицы на 4 (сдвинув на 2 бита влево) и прибавить (или вычесть) к адресу L rtx: add x0, x1, w0, sxtb #2 ; перейти на вычисленный адрес: (В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

182

1

О

7

br x0 ; эта метка указывает на сегмент кода ( t e x t ) : .L rtx 4 : .s e c tio n .rodata ; всё после выражения " . s e c t io n " выделяется в сегменте только для чтения (ro d a ta ): . L4: . byte (. L3 - . L rtx 4 ) / 4 ; case 1 . byte (. L3 - . L rtx 4 ) / 4 ; case 2 .byte (. L5 - . L rtx 4 ) / 4 ; case 3 .byte (. L5 - . L rtx 4 ) / 4 ; case 4 .byte (. L5 - . L rtx 4 ) / 4 ; case 5 .byte (. L5 - . L rtx 4 ) / 4 ; case 6 .byte (. L3 - . L rtx 4 ) / 4 ; case 7 .byte (. L6 - . L rtx 4 ) / 4 ; case 8 .byte (. L6 - . L rtx 4 ) / 4 ; case 9 .byte (. L3 - . L rtx 4 ) / 4 ; case 10 .byte (. L2 - . L rtx 4 ) / 4 ; case 11 .byte (. L2 - . L rtx 4 ) / 4 ; case 12 .byte (. L2 - . L rtx 4 ) / 4 ; case 13 .byte (. L2 - . L rtx 4 ) / 4 ; case 14 .byte (. L2 - . L rtx 4 ) / 4 ; case 15 .byte (. L2 - . L rtx 4 ) / 4 ; case 16 .byte (. L2 - . L rtx 4 ) / 4 ; case 17 .byte (. L2 - . L rtx 4 ) / 4 ; case 18 .byte (. L2 - . L rtx 4 ) / 4 ; case 19 .byte (. L6 - . L rtx 4 ) / 4 ; case 20 .byte (. L6 - . L rtx 4 ) / 4 ; case 21 .byte (. L7 - . L rtx 4 ) / 4 ; case 22 .te x t ; всё после выражения " . t e x t " выделяется в сегменте кода ( t e x t ) : . L7: ; вывести "22" adrp x0, .LC3 add x0, x0, :lo12:.LC3 b puts . L6: вывести "8, 9 21" adrp x0, . LC2 add x0, x0, :lo 12 : . LC2 b puts L5: вывести "3, 4 5" adrp x0, . LC1 add x0, x0, :lo 12 : . LC1 b puts L3: вывести "1, 2 adrp x0, . LC0 add x0, x0, :lo 12 : . LC0 b puts LC0: . s t r in g LC1: . s t r in g LC2: . s t r in g LC3: . s t r in g LC4: . s t r in g " d e f a u lt " 01

7 2,

1 ,

5 4,

3

1 " 2

9

8

2" 2

С ком пил ируем э то т пример к а к о б ъ е ктн ы й файл и о ткро е м е го в IDA. Вот таблица переходов:

rodata:0000000000000064 rodata:0000000000000064 rodata:0000000000000064 $d rodata:0000000000000065 rodata:0000000000000066 rodata:0000000000000067 (В целях сбора с т а т и с т и к и )

Л и с т и н г 1.161: ju m p ta b le in IDA AREA . rodata, DATA,, READONLY ; ORG 0x64 DCB 9 ; case 1 DCB 9 ; case 2 DCB 6 ; case 3 DCB 6 ; case 4 если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

183

. rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata . rodata

0000000000000068 0000000000000069 000000000000006A 000000000000006B 000000000000006C 000000000000006D 000000000000006E 000000000000006F 0000000000000070 0000000000000071 0000000000000072 0000000000000073 0000000000000074 0000000000000075 0000000000000076 0000000000000077 0000000000000078 0000000000000079 000000000000007B ; .rodata

DCB 6 DCB 6 DCB 9 DCB 3 DCB 3 DCB 9 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 0xF7 DCB 3 DCB 3 DCB 0 ends

case case case case case case case case case case case case case case case case case case

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

В случае 1, 9 б уд е т у м н о ж е н о на 9 и прибавлено к а дресу м е тки L r t x 4 . В случае 22, 0 б у д е т у м н о ж е н о на 4, в р езул ьта те это 0. Место сразу за м е тко й L r t x 4 это м етка L7, гд е н а ход ится код, вы водящ ий «22». В с е гм е н те кода нет та б ли цы переходов, место для нее выделено в о тд ел ьн о й с е кц и и .rodata (нет особой н у ж д ы распо л агать её в с е гм е н те кода). Там есть т а к ж е о тр и ц а те л ь н ы е байты (0xF7). Они испол ьзую тся для перехода назад, на код, выво­ д я щ и й с т р о к у «default» (на .L2).

1.16.4. Fall-through Ещё од но популярное использование оператора s w i t c h ( ) это т.н. «fallthrough» («провал»). Вот п ро­ стой пр им е р 96: {

bool

c r a h c e( c a p s e t i h w _s i

1 2 3 4 5 6 7 8 9 10 11

switch (c) { case case case case

' ' : / / f a llt h r o u g h ' \ t ' : / / f a llt h r o u g h ' \ r ' : / / f a llt h r o u g h '\ n ': re turn tru e ; d e f a u lt : / / not whitespace re turn f a ls e ;

} } Немного сл ож нее, из ядра Linux97:

1 char n col, nco2; 2 3 void f ( i n t if_ fr e q _ k h z ) 4 { 5 switch (if_ fre q _ k h z ) { 6 d e fa u lt: 7 p rin tf("IF = % d KHz i s not supportted, 3250 assumed\n", if _ fr e q _ k h z ) ; 8 / * f a llt h r o u g h * / 9 case 3250: / * 3.25Mhz * / 10 ncol = 0x34; 11 nco2 = 0x00; 12 break; 13 case 3500: / * 3.50Mhz * / 14 ncol = 0x38; 15 96Взято отсюда: h t t p s : / / g i t h u b . c o m / a z o n a l o n / p r g r a a s / b l o b / m a s t e r / p r o g 1 l i b / l e c t u r e _ e x a m p l e s / i s _ w h i t e s p a c e . c 97h t t p s : / / g i t h u b . c o m / t o r v a l d s / l i n u x / b l o b / m a s t e r / d r i v e r s / m e d i a / d v b - f r o n t e n d s / l g d t 3 3 0 6 a . c

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

184

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

nco2 = 0x00; break; case 4000: / * 4.00Mhz * / nco1 = 0x40; nco2 = 0x00; break; case 5000: / * 5.00Mhz * / nco1 = 0x50; nco2 = 0x00; break; case 5380: / * 5.38Mhz * / nco1 = 0x56; nco2 = 0x14; break; } }; Л и с т и н г 1.162: О п ти м и зи р у ю щ и й GCC 5.4.0 x86 . LC0: . s t r in g f:

%d KHz i s not supportted, 3250 assumed\n"

= F I

sub mov cmp je jg cmp je cmp jne mov mov add re t

esp, eax, eax, . L3 . L4 eax, . L5 eax, . L2 BYTE BYTE esp,

12 DWORD PTR 4000

cmp je cmp jne mov mov add re t

eax, . L7 eax, . L2 BYTE BYTE esp,

5000

sub push push c a ll add

esp, 8 eax OFFSET FLAT: . LC0 p rin tf esp, 16

mov mov add re t

BYTE PTR nco1, 52 BYTE PTR nco2, 0 esp, 12

mov mov add re t

BYTE PTR nco1, 64 BYTE PTR nco2, 0 esp, 12

mov mov add re t

BYTE PTR nco1, 80 BYTE PTR nco2, 0 esp, 12

61 + p s e

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

3250 3500 PTR nco1, 56 PTR nco2, 0 12

. L4:

5380 PTR nco1, 86 PTR nco2, 20 12

. L2:

. L5:

. L3:

. L7:

На м е тку .L5 уп р ав л е н ие м о ж е т пе ре йти если на входе ф-ции число 3250. Но на эту м е т ку м ож н о попасть и с д р у го й стороны: мы видим что м е ж д у вызовом p r i n t f ( ) и м е тко й .L5 нет н и к а к и х пероходов. (В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

185

Теперь мы м ож ем понять, почему иногд а switch() является и сто ч н ико м ош ибок: если забыть д о ­ п исать break, это п р е к р а т и т вы р а ж е н и е switch() в fallthrough, и вместо о д н о го блока для к а ж д о г о условия, б у д е т исполняться сразу н ескол ько.

1.16.5. Упражнения Упражнение#1 Вполне в о зм о ж н о перед ел ать пример на Си в л и с ти н ге 1.16.2 (стр. 167) т а к, чтобы при ко м п и л я ­ ции получалось д а ж е ещё меньше кода, но работать всё б уд е т то чн о т а к ж е . П опробуйте э того добиться.

1.17. Циклы 1.17.1. Простой пример x86 Для о р га н и з а ц и и ц икл ов в а р х и т е к т у р е x86 есть старая и н с т р у кц и я LOOP. Она п р оверяет значение регистра ECX и если оно не 0, д е ла е т д е к р е м е н т ECX и переход по метке, у к а за н н о й в операнде. Возможно, эта и н с т р у кц и я не сл иш ком удобная, по том у что у ж е почти не бывает соврем енны х ком пил яторов, ко тор ы е использовали бы её. Т а к что если вы видите гд е -то LOOP, то с большой ве ро ятн остью это вручную н а пи сан н ы й ко д на ассемблере. Обычно, ц икл ы на Си/Си+ + создаю тся при помощ и f o r ( ) , w h i l e ( ) , d o / w h i l e ( ) . Начнем с f o r ( ) . Это вы р а ж е н и е описы ва е т и н иц иа л иза ци ю , условие, о п ер а ци ю после к а ж д о й ите ра ц ии (и н кр е м е н т/ д е к р е м е н т ) и тело цикла. f o r (инициализация; условие; после каждой итерации) { тело_цикла; } Примерно т а к ж е , ге н е ри р уе м ы й ко д и б у д е т состоять из э ти х четы р е х частей. Возьмем пример: # in clu d e < s td io .h > void p r i n t i n g _ f u n c t i o n ( i n t i ) { p r i n t f ( " f ( % d ) \n " , i ) ; }; i n t main() { in t i; f o r (i= 2 ; i i n t f 1 ( i n t a) { re turn a*7; }; i n t f 2 ( i n t a) { re turn a*28; (В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

215

}; i n t f 3 ( i n t a) { return a*17; };

x86

Л и с т и н г 1.193: О птимизирую щ ийM S VC 2012

> E

D X II

X II m

E >

; a*7 a$ = 8 _ f1 PROC mov ; ECX=a lea ; EAX=ECX*8 sub re t ENDP

_ f1

> E

D X II

X II m

E >

; a*28 a$ = 8 _ f2 PROC mov ; ECX=a lea ; EAX=ECX*8 sub

_ f2

(

X Л Л rvj

E > X II m >

shl re t ENDP

ecx, DWORD PTR _a$[esp-4] eax, DWORD PTR [ecx*8] eax, ecx ECX*8-ECX=ECX*7=a*7 0

ecx, DWORD PTR _a$[esp-4] eax, DWORD PTR [ecx*8] eax, ecx ECX*8-ECX=ECX*7=a*7 eax, 2 a*7)*4=a*28 0

; a*17 a$ = 8 _f3 PROC mov eax, DWORD PTR _a$[esp-4] ; EAX=a shl eax, 4 ; EAX=EAX> 16) -0x50 (__gnu_local_gp & 0xFFFF) 0x50+var_4($sp) 0x50+var_40($sp) (time & 0xFFFF)($gp) $zero ; load delay s lo t, NOP $zero ; branch delay s lo t, NOP 0x50+var_40($sp) $sp, 0x50+var_38 (lo c a ltim e _ r & 0xFFFF)($gp) $sp, 0x50+seconds 0x50+var_38($sp) ; branch delay s lo t 0x50+var_40($sp) 0x50+year($sp) ( p r i n t f & 0xFFFF)($gp) $LC0 # "Year: %d\n" 1900 ; branch delay s lo t 0x50+var_40($sp) 0x50+month( $sp) ( p r i n t f & 0xFFFF)($gp) ( $LC1 >> 16) # "Month: %d\n" ( $LC1 & 0xFFFF) # "Month: %d\n" 0x50+var_40($sp) 0x50+day($sp) ( p r i n t f & 0xFFFF)($gp) ( $LC2 >> 16) # "Day: %d\n"

branch delay s lo t

( $LC2 & 0xFFFF) # "Day: %d\n" ; branch delay s lo t 0x50+var_40($sp) 0x50+hour($sp) ( p r i n t f & 0xFFFF)($gp) ( $LC3 >> 16) # "Hour: %d\n" ( $LC3 & 0xFFFF) # "Hour: %d\n" 0x50+var_40($sp) 0x50+minutes($sp) ( p r i n t f & 0xFFFF)($gp) ( $LC4 >>16) # "Minutes: %d\n"

branch delay s lo t

( $LC4 & 0xFFFF) # "Minutes: %d\n' 0x50+var_40($sp) 0x50+seconds($sp) ( p r i n t f & 0xFFFF)($gp) ( $LC5 >> 16) # "Seconds: %d\n"

branch delay s lo t

( $LC5 & 0xFFFF) # "Seconds: %d\n' 0x50+var_4($sp) $zero ; load delay s lo t, NOP

branch delay s lo t

0x50

. a s c ii "Year: %d\n" . a s c ii "Month: %d\n" . a s c ii "Day: %d\n"

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

357

74 75 76

$LC3: $LC4: $LC5:

. a s c ii "Hour: %d\n" . a s c ii "Minutes: %d\n" . a s c ii "Seconds: %d\n"

Это тот пример, где branch delay slot-ы могут нас запутать. Например, в строке 35 есть инструкция addiu $a1, 1900, добавляющая 1900 к числу года. Но она исполняется перед исполнением соответствующей JALR в строке 34, не забывайте.

С труктура к а к набор перем енны х Чтобы проиллюстрировать то что структура — это просто набор переменных, лежащ их в одном месте, переделаем немного пример, еще раз заглянув в описание структуры tm: ли сти н г.1.332. #include #include void main() { i n t tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday, tm_isdst; tim e_t unix_time; unix_time=time(NULL); lo c a ltim e _ r (&unix_time, &tm_sec); p r in tf p r in tf p r in tf p r in tf p r in tf p r in tf

( 'Year: %d\n", tm_year+1900 ( 'Month: %d\n", tm_mon); ( 'Day: %d \n ", tm_mday); ( "Hour: %d\n", tm_hour); ( 'Minutes: %d\n", tm_min); ( "Second s: %d\n", tm_sec);

}; N.B. В l o c a l t i m e _ r передается указатель именно на tm_sec, т.е. на первый элемент «структуры». В итоге, и этот компилятор поворчит: Листинг 1.336: GCC 4.7.3 GCC_tm2.c: In function 'm ain': GCC_tm2.c:11:5: warning: passing argument 2 of 'lo c a ltim e _ r ' from incompatible pointer type [ / 4 enabled by d e fa u lt] In f i l e included from GCC_tm2.c:2:0: /u s r/in c lu d e /tim e .h :5 9 :1 2 : note: expected 's t r u c t tm * ' but argument is of type ' i n t * ' Тем не менее, сгенерирует такое: Листинг 1.337: GCC 4.7.3 main

proc near

var_30 var_2C unix time tm_sec tm_min tm hour tm_mday tm_mon tm_year

= = = = = = = = =

dr o w d

dword p tr p tr dword p tr dword p tr dword p tr dword p tr dword p tr dword p tr dword p tr

push mov and sub c a ll mov c a ll

-30h -2Ch -1Ch -18h -14h -10h -0Ch -8 -4

ebp ebp, esp esp, 0FFFFFFF0h esp, 30h __main [esp+30h+var_30], 0 ; arg 0 time

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

358

main

mov lea mov lea mov c a ll mov add mov mov c a ll mov mov mov c a ll mov mov mov c a ll mov mov mov c a ll mov mov mov c a ll mov mov mov c a ll leave retn endp

[esp+30h+unix_time], eax eax, [esp+30h+tm_sec] [esp+30h+var_2C], eax eax, [esp+30h+unix_time] [esp+30h+var_30], eax lo c a ltim e _ r eax, [esp+30h+tm_year] eax, 1900 [esp+30h+var_2C], eax [esp+30h+var_30], o ffs e t p r in tf eax, [esp+30h+tm_mon] [esp+30h+var_2C], eax [esp+30h+var_30], o ffs e t p r in tf eax, [esp+30h+tm_mday] [esp+30h+var_2C], eax [esp+30h+var_30], o ffs e t p r in tf eax, [esp+30h+tm_hour] [esp+30h+var_2C], eax [esp+30h+var_30], o ffs e t p r in tf eax, [esp+30h+tm_min] [esp+30h+var_2C], eax [esp+30h+var_30], o ffs e t p r in tf eax, [esp+30h+tm_sec] [esp+30h+var_2C], eax [esp+30h+var_30], o ffs e t p r in tf

aYearD ; "Year: %d\n"

aMonthD ; "Month: %d\n"

aDayD ; "Day: %d\n"

aHourD ; "Hour: %d\n"

aMinutesD ; "Minutes: %d\n"

aSecondsD ; "Seconds: %d\n"

Этот код почти идентичен уж е рассмотренному, и нельзя сказать, была ли структура в оригиналь­ ном исходном коде либо набор переменных. И это работает. Однако, в реальности т а к лучше не делать. Обычно, неоптимизирующий компиля­ тор располагает переменные в локальном стеке в том ж е порядке, в котором они объявляются в функции. Тем не менее, никакой гарантии нет. Кстати, какой-нибудь другой компилятор может предупредить, что переменные tm_year, tm_mon, tm_mday, tm_hour, tm_min, но не tm_sec, используются без инициализации. Действительно, ведь компилятор не знает что они будут заполнены при вызове функции l o c a l t i m e _ r ( ) . Мы выбрали именно этот пример для иллюстрации, потому что все члены структуры имеют тип int. Это не сработает, если поля структуры будут иметь размер 16 бит (WORD), ка к в случае со структурой SYSTEMTIME — GetSystemTime() заполнит их неверно (потому что локальные перемен­ ные выровнены по 32-битной границе). Читайте об этом в следующей секции: «Упаковка полей в структуре» (1.25.4 (стр. 361)). Так что, структура — это просто набор переменных лежащ их в одном месте, рядом. Можно было бы сказать, что структура — это инструкция компилятору, заставляющая его уд е р ж и ­ вать переменные в одном месте. Кстати, когда-то, в очень ранних версиях Си (перед 1972) структур не было вовсе [Dennis M. Ritchie, The development o f the C language, (1993)]165. Здесь нет примера с отладчиком: потому что он будет полностью идентичным тому, что вы уж е видели.

С т р у к т у р а к а к м ассив 3 2 - б и т н ы х сл о в 165Также доступно здесь: h ttp ://g o .y u ric h e v .c o m /1 7 2 6 4

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

359

#include #include void main() { s tru c t tm t ; tim e_t unix_time; in t i; unix_time=time(NULL); lo c a ltim e _ r (&unix_time, & t); fo r (i=0; i

0

X о о о о о о Г) гп

0

X о о о о о о 1 — 1

D 2 0 0 0 0 0 0 x

О

(45) 0x00000033 (51) 0x00000017 (23) (26) 0x00000006 (6) 0x00000072 (114) 0x00000006 (6) 6 0 2

0x00000001 (1)

Переменные здесь в том ж е порядке, в котором они перечислены в определении структуры: 1.332 (стр. 353). Вот ка к это компилируется: Листинг 1.338: ОптимизирующийGCC 4.8.1

p] s e r t p

proc near push ebp mov ebp, esp push esi push ebx and esp, 0FFFFFFF0h sub esp, 40h mov dword 0 ; timer lea ebx, [esp+14h] c a ll _time lea esi, [esp+38h] mov [esp+4], ebx ; tp mov [esp+10h], eax lea eax, [esp+10h] mov [esp], eax ; timer c a ll _lo ca ltim e _r nop lea esi, [esi+0] ; NOP loc_80483D8: ; EBX здесь это указатель на структуру, ESI - указатель на её конец. mov eax, [ebx] ; загрузить 32-битное слово из массива add ebx, 4 ; следующее поле в структуре mov dword p tr [esp+4], o f fs e t a0x08xD ; "0x%08X (%d)\n" mov dword p tr [esp], 1 mov [esp+0Ch], eax ; передать значение в p r i n t f ( ) mov [esp+8], eax ; передать значение в p r i n t f ( ) c a ll ___p rin tf_ c h k cmp ebx, e s i ; достигли конца структуры? jnz short loc_80483D8 ; нет - тогда загрузить следующее lea esp, [ebp-8] (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

360

pop pop pop retn endp

main

ebx esi ebp

И действительно: место в локальном стеке в начале используется ка к структура, затем ка к массив. Возможно даже модифицировать поля структуры через указатель. И снова, это сомнительный хакерский способ, который не рекомендуется использовать в настоя­ щем коде.

Упражнение В качестве упражнения, попробуйте модифицировать (увеличить на 1) текущ ий номер месяца об­ ращаясь со структурой ка к с массивом.

С т р у к т у р а к а к м ассив б а й т Можно пойти еще дальше. Можно привести (cast) указатель к массиву байт и вывести его: #include #include void main() { s tru c t tm t ; tim e_t unix_time; in t i, j ; unix_time=time(NULL); lo c a ltim e _ r (&unix_time, & t); fo r (i=0; i> 16) addiu $sp, -0x28 la $gp, (__gnu_local_gp & 0xFFFF) sw $ra, 0x28+var_4($sp) sw $gp, 0x28+var_10($sp) подготовить байт из 32-бит ного big-endian значения: sra $t0, $a0, 24 move $v1, $a1 подготовить байт из 32-битного big-endian значения: (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

368

sra lw sw lu i sw sw sw sw la move move ja lr move lw or jr addiu $LC0:

$v0, $t9 , $a0, $a0, $a3, $a1, $a2, $a3, $a0, $a1, $a2, $t9 $a3, $ra, $at, $ra $sp,

4 2 2, a $

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

( p r i n t f & 0xFFFF)($gp) 0x28+arg 0($sp) ( $LC0 >>16) # "a=%d; b=%d; c=%d; d=%d\n" 0x28+var_18($sp) 0x28+arg_4($sp) 0x28+arg_8($sp) 0x28+arg C($sp) ( $LC0 & 0xFFFF) # "a=%d; b=%d; c=%d; d=%d\n" $t0 $v1 $v0 ; branch delay s lo t 0x28+var_4($sp) $zero ; load delay s lo t, NOP 0x28 ; branch delay s lo t

. a s c ii "a=%d

b=%d; c=%d; d=%d\n"

Поля структуры приходят в регистрах $A0..$A3 и затем перетасовываются в регистры $A1..$A3 для p r i n t f ( ) , в то время как 4-е поле (из $A3) передается через локальный стек используя SW. Но здесь есть две инструкции SRA («Shift Word Right Arithmetic»), которые готовят поля типа char. Почему? По умолчанию, MIPS это big-endian архитектура 2.8 (стр. 466), и Debian Linux в котором мы работаем, т а кж е big-endian. Так что когда один байт расположен в 32-битном элементе структуры, он занимает биты 31..24. И когда переменную типа char нужно расширить до 32-битного значения, она должна быть сдви­ нута вправо на 24 бита.

char это знаковый тип, т а к что здесь нужно использовать арифметический сдвиг вместо логиче­ ского.

Ещ е к о е -ч т о Передача структуры ка к аргумент функции (вместо передачи указателя на структуру) это то же что и передача всех полей структуры по одному. Если поля в структуре пакуются по умолчанию, то функцию f() можно переписать так: void f(c h a r a, i n t b, char c, i n t d) { p r i n t f ("a=%d; b=%d; c=%d; d=%d\n", a, b, c, d); }; И в итоге будет такой ж е код.

1.25.5. Вложенные структуры Теперь, ка к насчет ситуаций, когда одна структура определена внутри другой структуры? #include s tru c t in n e r_ s tru c t { i n t a; i n t b; }; s tru c t o u te r_ s tru c t { char a; i n t b; s tru c t in n e r_ s tru c t c; char d; (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

369

i n t e; }; void f ( s t r u c t outer s tru c t s) { p r i n t f ("a=%d; b=%d; c a=%d; c.b=%d; d=%d; e=%d\n", s.a, s.b, s .c .a , s c.b, s.d, s.e ); }; i n t main() { s tru c t o u te r_ s tru c t s; 1 ; = a s

s . b=2; s.c.a=100; s.c.b=101; s . d=3; s . e=4; f(s );

}; ...в этом случае, оба поля i n n e r _ s t r u c t просто будут располагаться между полями a,b и d,e в o u te r_ s tru c t. Компилируем (MSVC 2010):

$SG2802 DB

Листинг 1.348: О п тим и зи рую щ и й І^Ѵ С 2010 /ObO ' a=%d; b=%d; c.a=%d; c.b=%d; d=%d; e=%d', 0aH, 00H

TEXT SEGMENT s$ = 8 f PROC mov eax, DWORD PTR _s$[esp+16] movsx ecx, BYTE PTR _ s$[esp+12] mov edx, DWORD PTR _s$[esp+8] push eax mov eax, DWORD PTR _s$[esp+8] push ecx mov ecx, DWORD PTR _s$[esp+8] push edx movsx edx, BYTE PTR _ s$[esp+8] push eax push ecx push edx push OFFSET $SG2802 ; 'a=%d; b=%d c a ll _ p rin tf add esp, 28 ret 0 f ENDP s$ = -24 main PROC sub esp, 24 push ebx push esi push edi mov ecx, 2 sub esp, 24 mov eax, esp С этого момента, EAX это синоним ESP: mov BYTE PTR _s$[esp+60], 1 mov ebx, DWORD PTR _s$[esp+60] mov DWORD PTR [eax], ebx mov DWORD PTR [eax+4], ecx lea edx, DWORD PTR [ecx+98] lea e s i, DWORD PTR [ecx+99] lea edi, DWORD PTR [ecx+2] mov DWORD PTR [eax+8], edx mov BYTE PTR _s$[esp+76], 3 mov ecx, DWORD PTR _s$[esp+76] (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

37 0

mov DWORD PTR [eax+12], esi mov DWORD PTR [eax+16], ecx mov DWORD PTR [eax+20], edi c a ll _f add esp, 24 edi pop esi pop xor eax, eax ebx pop add esp, 24 ret 0 _main ENDP Очень любопытный момент в том, что глядя на этот код на ассемблере, мы даже не видим, что была использована какая-то еще другая структура внутри этой! Так что, пожалуй, можно сказать, что все вложенные структуры в итоге разворачиваются в одну, линейную или одномерную структуру. Конечно, если заменить объявление s t r u c t i n n e r _ s t r u c t c; на s t r u c t i n n e r _ s t r u c t * c ; (объяв­ ляя таким образом указатель), ситуация будет совсем иная.

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

371

O lly D b g Загружаем пример в OllyDbg и смотрим на o u t e r _ s t r u c t в памяти:

Рис. 1.108: OllyDbg: Перед исполнением p r i n t f ( ) Вот ка к расположены значения в памяти: • (outer_struct.a) (байт) 1 + 3 байта случайного мусора; • (outer_struct.b) (32-битное слово) 2; • (inner_struct.a) (32-битное слово) 0x64 (100); • (inner_struct.b) (32-битное слово) 0x65 (101); • (outer_struct.d) (байт) 3 + 3 байта случайного мусора; • (outer_struct.e) (32-битное слово) 4.

1.25.6. Работа с битовыми полями в структуре П р и м е р CPUID Язык Си/Си+ + позволяет указывать, сколько именно бит отвести для каж д ого поля структуры. Это удобно если нужно экономить место в памяти. К примеру, для переменной типа bool достаточно одного бита. Но, это не очень удобно, если нужна скорость. Рассмотрим пример с инструкцией CPUID170. Эта инструкция возвращает информацию о том, какой процессор имеется в наличии и какие возможности он имеет. Если перед исполнением инструкции в EAX будет 1, то CPUID вернет упакованную в EAX такую информацию о процессоре: 3:0 (4 бита) 7:4 (4 бита) 11:8 (4 бита) 13:12 (2 бита) 19:16 (4 бита) 27:20 (8 бит)

Stepping Model Family Processor Type Extended Model Extended Family

MSVC 2010 имеет макрос для CPUID, а GCC 4.4.1 — нет. Поэтому для GCC сделаем эту функцию сами, используя его встроенный ассемблер171. 170wikipedia 171Подробнее о встроенном ассемблере GCC

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

372

#include # ifd e f __GNUC__ s t a t ic in li n e void c p u id (in t code, i n t *a, i n t *b, i n t *c, i n t *d) { asm v o l a t i l e ( " c p u id ": "= a " ( * a ) ," = b " ( * b ) ," = c " ( * c ) ," = d " ( * d ) :" a " ( c o d e ) ) ; } #endif # ifd e f _MSC_VER #include < in tr in .h > #endif s tru c t CPUID_1_EAX { unsigned i n t stepping:4; unsigned i n t model:4; unsigned i n t fa m ily _ id :4 ; unsigned i n t processor_type:2; unsigned i n t reserved1:2; unsigned i n t extended_model_id:4; unsigned i n t extended_family_id:8; unsigned i n t reserved2:4; }; i n t main() { s tru c t CPUID_1_EAX *tmp; i n t b [4 ]; # ifd e f _MSC_VER __cpuid(b,1); #endif # ifd e f __GNUC__ cpuid (1, &b[0], &b[1], &b[2], & b[3]); #endif tmp=(struct CPUID_1_EAX *)& b[0]; p rin tf p rin tf p rin tf p rin tf p rin tf p rin tf

"stepping=%d\n", tmp->stepping); "model=%d\n", tmp->model); "family_id=% d\n", tm p->fam ily_id); "processor_type=%d\n", tmp->processor_type); "extended_model_id=%d\n", tmp->extended_model_id); "extended_family_id=%d\n", tmp->extended_family_id)

return 0; }; После того ка к CPUID заполнит EAX/EBX/ECX/EDX, у нас они отразятся в массиве b [ ] . Затем, мы имеем указатель на структуру CPUID_1_EAX, и мы указываем его на значение EAX из массива b [ ] . Иными словами, мы трактуем 32-битный in t ка к структуру. Затем мы читаем отдельные биты из структуры.

MSVC Компилируем в MSVC 2008 с опцией /Ox: Листинг 1.349: О п тим и зи рую щ и й І^Ѵ С 2008 _b$ = -16 ; size = 16 _main PROC sub esp, 16 push ebx xor

ecx, ecx

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

373

mov cpuid push lea mov mov mov mov

esi e s i, DWORD PTR _b$[esp+24] DWORD PTR [ e s i] , eax DWORD PTR [e si+ 4], ebx DWORD PTR [e si+ 8], ecx DWORD PTR [esi+12], edx

mov mov and push push c a ll

e s i, DWORD PTR _b$[esp+24] eax, esi eax, 15 eax OFFSET $SG15435 ; 'stepping=%d', 0aH, 00H _ p rin tf

mov shr and push push c a ll

ecx, esi ecx, 4 ecx, 15 ecx OFFSET $SG15436 ; 'model=%d', 0aH, 00H _ p rin tf

mov shr and push push c a ll

edx, esi edx, 8 edx, 15 edx OFFSET $SG15437 ; 'family_id=% d', 0aH, 00H _ p rin tf

mov shr and push push c a ll

eax, esi eax, 12 eax, 3 eax OFFSET $SG15438 ; 'processor_type=%d', 0aH, 00H _ p rin tf

mov shr and push push c a ll

ecx, esi ecx, 16 ecx, 15 ecx OFFSET $SG15439 ; 'extended_model_id=%d', 0aH, 00H _ p rin tf

shr and push push c a ll add pop

e s i, 20 e s i, 255 esi OFFSET $SG15440 ; 'extended_family_id=%d', 0aH, 00H _ p rin tf esp, 48 esi

xor pop

eax, eax ebx

add ret main

eax, 1

esp, 16 0 ENDP

Инструкция SHR сдвигает значение из EAX на то количество бит, которое нуж но пропустить, то есть, мы игнорируем некоторые биты справа. А инструкция AND очищает биты слева которые нам не нужны, или же, говоря иначе, она оставляет по маске только те биты в EAX, которые нам сейчас нужны.

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

374

MSVC + O lly D b g Загрузим пример в OllyDbg и увидим, какие значения были установлены в EAX/EBX/ECX/EDX после исполнения CPUID:

Рис. 1.109: OllyDbg: После исполнения CPUID В EAX установлено 0X000206A7 (мой CPU — Intel Xeon E3-1220). В двоичном виде это 0600000000000000100000011010100111. Вот ка к распределяются биты по полям в моем случае: поле reserved2 extended_family_id extended_model_id reserved1 processor_id fa m ily jd model stepping

в двоичном виде 0000 00000000 0010 00 00 0110 1010 0111

в десятичном виде 0 0 2 0 0 6 10 7

Листинг 1.350: Вывод в консоль stepping=7 model=10 family_id=6 processor_type=0 extended_model_id=2 extended_family_id=0

GCC Попробуем GCC 4.4.1 с опцией -O3. Листинг 1.351: ОптимизирующийGCC 4.4.1 proc near ; DATA XREF: _start+17

main push mov and push

ebp ebp, esp esp, 0FFFFFFF0h esi

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

375

mov push mov sub cpuid mov and mov mov mov c a ll mov shr and mov mov mov c a ll mov shr and mov mov mov c a ll mov shr and mov mov mov c a ll mov shr shr and and mov mov mov c a ll mov mov mov c a ll add xor pop pop mov pop retn main

e si, 1 ebx eax, esi esp, 18h e si, eax eax, 0Fh [esp+8], eax dword p tr [esp+4], dword p tr [esp], 1 ___p rin tf_ch k eax, esi eax, 4 eax, 0Fh [esp+8], eax dword p tr [esp+4], dword p tr [esp], 1 ___p rin tf_ch k eax, esi eax, 8 eax, 0Fh [esp+8], eax dword p tr [esp+4], dword p tr [esp], 1 ___p rin tf_ch k eax, esi eax, 0Ch eax, 3 [esp+8], eax dword p tr [esp+4], dword p tr [esp], 1 ___p rin tf_ch k eax, esi eax, 10h e si, 14h eax, 0Fh e si, 0FFh [esp+8], eax dword p tr [esp+4], dword p tr [esp], 1 ___p rin tf_ch k [esp+8], esi dword p tr [esp+4], dword p tr [esp], 1 ___p rin tf_ch k esp, 18h eax, eax ebx esi esp, ebp ebp

o f fs e t aSteppingD ; "stepping=%d\n"

o f fs e t aModelD ; "model=%d\n"

o f fs e t aFamily_idD ; "family_id=%d\n"

o f fs e t aProcessor_type ; "processor_type=%d\n"

o f fs e t aExtended_model ; "extended_model_id=%d\n"

o f fs e t unk_80486D0

endp

Практически, то ж е самое. Единственное что стоит отметить это то, что GCC решил зачем-то объ­ единить вычисление extended_m odel_id и e x te n d e d _ fa m ily _ id в один блок, вместо того чтобы вычислять их перед со­ ответствующим вызовом p r i n t f ( ) .

Работа с т и п о м f l o a t к а к со с т р у к т у р о й Как уж е ранее указывалось в секции о FPU (1.20 (стр. 219)), и float и double содержат в себе знак, мантиссу и экспоненту. Однако, можем ли мы работать с этими полями напрямую? Попробуем с flo a t . 31 30

23 22

S экспонента

0

мантисса

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

376

( S — знак ) #include #include #include #include

< s td lib .h >

s tru c t flo a t_ a s _ s tru c t { unsigned i n t fra c tio n : 23; / / мантисса unsigned i n t exponent : 8; / / экспонента + 0x3FF unsigned i n t sign : 1; / / бит знака }; f lo a t f ( f l o a t _in ) { f lo a t f=_in; s tru c t flo a t_ a s _ s tru c t t ; assert (sizeof ( s tr u c t flo a t_ a s _ s tru c t) == sizeof ( f l o a t ) ) ; memcpy (&t, &f, sizeof ( f l o a t ) ) ; t.sign= 1; / / установить отрицательный знак t.exponent=t.exponent+2; / / умножить d на 2" (n здесь 2) memcpy (&f, &t, sizeof ( f l o a t ) ) ; return f ; }; i n t main() { p r i n t f ("% f\n", f(1 .2 3 4 )); }; Структура f l o a t _ a s _ s t r u c t занимает в памяти столько ж е места сколько и float, то есть 4 байта или 32 бита. Далее мы выставляем во входящем значении отрицательный знак, а т а кж е прибавляя двойку к экспоненте, мы тем самым умножаем всё значение на 22, то есть на 4. Компилируем в MSVC 2008 без включенной оптимизации: Листинг 1.352: НеоптимизирующийMSVC 2008 _t$ = -8 _f$ = -4 __in$ = 8 ?f@@YAMM@Z push mov sub

; size = 4 ; size = 4 ; size = 4 PROC ; f ebp ebp, esp esp, 8

fld fs tp

DWORD PTR __in$[ebp] DWORD PTR _f$[ebp]

push lea push lea push c a ll add

4 eax, DWORD PTR _f$[ebp] eax ecx, DWORD PTR _t$[ebp] ecx _memcpy esp, 12

mov or mov

edx, DWORD PTR _t$[ebp] edx, -2147483648 ; 80000000H - выставляем знак минус DWORD PTR _t$ [e b p ], edx

mov shr

eax, DWORD PTR _t$[ebp] eax, 23 ; 00000017H - выкидываем мантиссу

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

377

and add and shl mov and

eax, eax, eax, eax, ecx, ecx,

255 ; 000000ffH - оставляем здесь только экспоненту 2 ; прибавляем к ней 2 255 ; 000000ffH 23 ; 00000017H - пододвигаем результат на место бит 30:23 DWORD PTR _t$[ebp] -2139095041 ; 8 0 7 fffffH - выкидываем экспоненту

; складываем оригинальное значение без экспоненты с новой только что вычисленной экспонентой: or ecx, eax mov DWORD PTR _t$ [e b p ], ecx push lea push lea push c a ll add

4 edx, DWORD PTR _t$[ebp] edx eax, DWORD PTR _f$[ebp] eax _memcpy esp, 12

fld

DWORD PTR _f$[ebp]

mov pop ret ?f@@YAMM@Z

esp, ebp ebp 0 ENDP ; f

Слегка избыточно. В версии скомпилированной с флагом /Ox нет вызовов memcpy(), там работа происходит сразу с переменной f. Но по неоптимизированной версии будет проще понять. А что сделает GCC 4.4.1 с опцией -O3? Листинг 1.353: ОптимизирующийGCC 4.4.1 ; f(flo a t) public _Z1ff _Z1ff proc near var_4 arg_0

= dword p tr -4 = dword p tr 8

push mov sub mov or mov and shr add movzx shl or экспоненты mov f ld leave retn _Z1ff endp main

ebp ebp, esp, eax, eax, edx, eax, edx, edx, edx, edx, eax,

esp 4 [ebp+arg_0] 80000000h eax 807FFFFFh 23 2 dl 23 edx

; выставить знак минуса ; ; ; ; ; ;

оставить в EAX только знак и мантиссу подготовить экспоненту прибавить 2 сбросить все биты кроме 7:0 в EAX в 0 подвинуть новую только что вычисленную экспоненту на свое место соеденить новую экспоненту и оригинальное значение без

[ebp+var_4], eax [ebp+var_4]

public main proc near push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 10h f ld ds:dword_8048614 ; -4.936 fs tp qword p tr [esp+8] mov dword p tr [esp+4], o ffs e t asc_8048610 ; "%f\n" mov dword p tr [esp], 1 c a ll ___p rin tf_ c h k xor eax, eax

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

378

main

leave retn endp

Да, функция f ( ) в целом понятна. Однако, что интересно, еще при компиляции, невзирая на ме­ шанину с полями структуры, GCC умудрился вычислить результат функции f ( 1 . 2 3 4 ) еще во время компиляции и сразу подставить его в аргумент для p r i n t f ( ) !

1.25.7. Упражнения • h ttp ://c h a lle n g e s .r e /7 1 • h ttp ://c h a lle n g e s .r e /7 2

1.26. Объединения (union) union в Си/Си+ + используется в основном для интерпретации переменной (или блока памяти) од­ ного типа ка к переменной другого типа.

1.26.1. Пример генератора случайных чисел Если нам нуж ны случайные значения с плавающей запятой в интервале от 0 до 1, самое простое это взять ГПСЧ вроде Mersenne twister. Он выдает случайные беззнаковые 32-битные числа (иными словами, он выдает 32 случайных бита). Затем мы можем преобразовать это число в float и затем разделить на RAND_MAX (OxFFFFFFFF в данном случае) — полученное число будет в интервале от 0 до 1. Но ка к известно, операция деления — это медленная операция. Да и вообще хочется избежать лишних операций с FPU. Сможем ли мы избежать деления? Вспомним состав числа с плавающей запятой: это бит знака, биты мантиссы и биты экспоненты. Для получения случайного числа, нам нужно просто заполнить случайными битами все биты ман­ тиссы! Экспонента не может быть нулевой (иначе число с плавающей точкой будет денормализованным), т а к что в эти биты мы запишем 0b01111111 — это будет означать что экспонента равна единице. Далее заполняем мантиссу случайными битами, знак оставляем в виде 0 (что значит наше число положительное), и вуаля. Генерируемые числа будут в интервале от 1 до 2, т а к что нам еще нужно будет отнять единицу. В моем примере172 применяется очень простой линейный конгруэнтный генератор случайных чи­ сел, выдающий 32-битные числа. Генератор инициализируется текущ им временем в стиле UNIX. Далее, тип float представляется в виде union — это конструкция Си/Си+ + позволяющая интерпре­ тировать часть памяти по-разному. В нашем случае, мы можем создать переменную типа union и затем обращаться к ней ка к к float или ка к к uint32_t. Можно сказать, что это хак, причем грязный. Код целочисленного ГПСЧ точно такой же, ка к мы уж е рассматривали ранее: 1.24 (стр. 340). Так что и в скомпилированном виде этот код будет опущен. #include #include < std in t.h > #include / / определения, данные и ф-ции для целочисленного PRNG / / константы из книги Numerical Recipes const uint32_t RNG_a=1664525; const uint32_t RNG_c=1013904223; uint32_t RNG_state; / / глобальная переменная void my_srand(uint32_t i ) { 172идея взята здесь: h ttp ://g o .y u ric h e v .c o m /1 7 3 0 8

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

379

RNG_state=i; }; uint32_t my_rand() { RNG_state=RNG_state*RNG_a+RNG_c; return RNG_state; }; / / определения и ф-ции FPU PRNG: union u in t3 2 _ t_ flo a t { uint32_t i ; f lo a t f ; }; f lo a t flo a t_ ra n d () { union u in t3 2 _ t_ flo a t tmp; tmp.i=my_rand() & 0 x 0 0 7 fffff | 0x3F800000; return tm p.f-1; }; / / тест i n t main() { my_srand(time(NULL)); / / инициализация PRNG fo r ( i n t i=0; i subs r4, r4, #1 bne 14 mov r0, r4 pop {r4 , pc} andeq r0 , r0 , r0 # о

push {r3 , l r } bl 10 v ld r s0, [pc, #20] 5c < flo a t rand+0x24> bic r3 , r0 , #-16777216 ; 0xff000000 bic r3 , r3 , #8388608 ; 0x800000 o rr r3 , r3 , #1065353216 ; 0x3f800000 vmov s15, r3 vsub. f32 s0, s15, s0 pop {r3 , pc} svccc 0x00800000

О

00000038 < flo a t rand>: 38: e92d4008 3c: e b fffffe 40: ed9f0a05 44: e3c034ff 48: e3c33502 4c: e38335fe 50: ee073a90 54: ee370ac0 58: e8bd8008 5c: 3f800000

0x64 38

Инструкции по адресам 0x5c в f l o a t _ r a n d ( ) и 0x38 в main() это (псевдо-)случайный мусор.

1.26.2. Вычисление машинного эпсилона Машинный эпсилон — это самая маленькая гранула, с которой может работать FPU 173. Чем больше бит выделено для числа с плавающей точкой, тем меньше машинный эпсилон. Это 2-23 = 1.19е- 0 7 для float и 2-52 = 2.22e- 16 для double. См.также: статью в Wikipedia. Любопытно, что вычислить машинный эпсилон очень легко: #include #include < std in t.h > union u in t_ f lo a t { uint32_t i ; f lo a t f ; }; f lo a t calculate_m achine_epsilon(float s ta r t) { union u in t_ f lo a t v; v .f= s ta rt; v.i+ + ; return v . f - s t a r t ; } 173В русскоязычной литературе встречается также термин «машинный ноль».

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

382

void main() { p r i n t f ("%g\n", calculate_m achine_epsilon(1.0)); }; Ч т о м ы з д е с ь д е л а е м э т о о б х о д и м с я с м а н т и с с о й ч и с л а в ф о р м а т е IEEE 7 5 4 к а к с ц е л о ч и с л е н н ы м ч и с ­ л о м и п р и б а в л я е м е д и н и ц у к н е м у . И т о г о в о е ч и с л о с п л а в а ю щ е й т о ч к о й б у д е т р а в н о starting_value + machine_epsilon, т а к ч т о н а м н у ж н о п р о с т о в ы ч е с т ь и з н а ч а л ь н о е з н а ч е н и е ( и с п о л ь з у я а р и ф м е т и к у с п л а в а ю щ е й т о ч к о й ) ч т о б ы и з м е р и т ь , к а к о е ч и с л о о т р а ж а е т о д и н б и т в о д и н а р н о й т о ч н о с т и (float). union з д е с ь н у ж е н ч т о б ы м ы м о г л и о б р а щ а т ь с я к ч и с л у в ф о р м а т е IEEE 7 5 4 к а к к о б ы ч н о м у ц е л о ­ ч и с л е н н о м у . П р и б а в л е н и е 1 к н е м у на с а м о м д е л е п р и б а в л я е т 1 к мантиссе ч и с л а , х о т я , н у ж н о ска за ть , пе р е п о л н е н и е т а к ж е в о зм ож н о, что п р и в е д е т к пр и б а в л е ни ю е д и н и ц ы к э кс п о н е н те .

x86 Л и с т и н г 1 .3 5 7 : О п т и м и з и р у ю щ и й І ^ Ѵ С 2 0 1 0

-p s e 0 31 vt R T P

4] 4]

-p s e 0 31 vt R T P

tv130 = 8 _v$ = 8 _ s ta rt$ = 8 _calculate_machine_epsilon PROC f ld DWORD PTR _sta rt$ [e sp -4 ] fs t DWORD PTR _v$[esp-4] inc DWORD PTR _v$[esp-4] fsubr DWORD PTR _v$[esp-4] fs tp DWORD f ld DWORD ret 0 calculate_machine_epsilon ENDP

; это лишняя инструкция ; \ эта пара инструкций ; /

В т о р а я и н с т р у к ц и я FST и з б ы т о ч н а я : н е т н е о б х о д и м о с т и с о х р а н я т ь в х о д н о е з н а ч е н и е в э т о м ж е м е ­ сте (ко м п и л я то р реш ил вы д е л и ть п е р е м е н н ую v в том ж е месте л о ка л ь н о го стека, гд е н ахо д и тся и в х о д н о й а р г у м е н т ) . Д а л е е о н о и н к р е м е н т и р у е т с я п р и п о м о щ и INC, к а к е с л и э т о о б ы ч н а я ц е л о ­ ч и с л е н н а я п е р е м е н н а я . З а т е м о н о з а г р у ж а е т с я в FPU к а к е с л и э т о 3 2 - б и т н о е ч и с л о в ф о р м а т е IEEE 7 5 4 , FSUBR д е л а е т о с т а л ь н у ю ч а с т ь р а б о т ы и р е з у л ь т а т в ST0. П о с л е д н я я п а р а и н с т р у к ц и й FSTP/FLD и з б ы т о ч н а , н о к о м п и л я т о р н е с о п т и м и з и р о в а л её.

A R M 64 Р а сш ир им э т о т п р и м е р до 6 4-б ит:

#include #include < std in t.h > typedef union { uint64_t i ; double d; } uint_double; double calculate_machine_epsilon(double s ta r t) { uint_double v; v .d = s ta rt; v.i+ + ; return v . d - s t a r t ; } void main() { p r i n t f ("%g\n", calculate_m achine_epsilon(1.0)); }; В A R M 6 4 н е т и н с т р у к ц и и д л я д о б а в л е н и я ч и с л а к D - р е г и с т р у в FPU, т а к ч т о в х о д н о е з н а ч е н и е ( п р и ­ ш е д ш е е в D 0 ) в н а ч а л е к о п и р у е т с я в GPR, и н к р е м е н т и р у е т с я , к о п и р у е т с я в р е г и с т р FPU D1, з а т е м п р оисхо д ит вы читание.

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

383

Листинг 1.358: ОптимизирующийGCC 4.9 ARM64 calculate_machine_epsilon: fmov x0, d0 ; загрузить входное значение типа double в X0 add x0, x0, 1 ; X0++ fmov d1, x0 ; переместить его в регистр FPU fsub d0, d1, d0 ; вычесть ret Смотрите т а кж е этот пример скомпилированный под x64 с SIMD-инструкциями: 1.31.4 (стр. 438).

MIPS Новая для нас здесь инструкция это MTC1 («Move To Coprocessor 1»), она просто переносит данные из GPR в регистры FPU. Листинг 1.359: ОптимизирующийGCC 4.4.5 (IDA) calculate_machine_epsilon: mfcl $v0, $f12 or $at, $zero ; NOP addiu $v1, $v0, 1 mtcl $v1, $f2 $ra jr sub. s $f0, $f2, $f12 ; branch delay s lo t

Вывод Трудно сказать, понадобится ли кому-то такая эквилибристика в реальном коде, но ка к уж е было упомянуто много раз в этой книге, этот пример хорошо подходит для объяснения формата IEEE 754 и union в Си/Си+ + .

1.26.3. Замена инструкции FSCALE Agner Fog в своей работе Optimizing subroutines in assembly language / An optimization guide for x86 platform s 174 указывает, что инструкция FPU FSCALE (вычисление 2n) может быть медленной на многих CPU, и он предлагает более быструю замену. Вот мой перевод его кода на ассемблер на Си/Си+ + : #include < std in t.h > #include union u in t_ f lo a t { uint32_t i ; f lo a t f ; }; f lo a t f l t _ 2 n ( i n t N) { union u in t_ f lo a t tmp; tmp. i=(N, arg=arg@entry=0x0) at msort.c:297 #8 0xb7e42dcf in __GI_qsort (b = 0 x b ffff0 f8 , n=10, s=4, cmp=0x804844d ) at msort.c:307 #9 0x0804850d in main ()

1.27.3. Опасность указателей на ф-ции Как мы можем видеть, ф-ция q s o r t ( ) ож идает указатель на ф-цию, которая берет на вход два ар­ гумента типа void* и возвращает целочисленное число. Если в вашем коде есть несколько разных ф-ций сравнения (одна сравнивает строки, другая — числа, итд), очень легко их перепутать друг с другом. Вы можете попытаться отсортировать массив строк используя ф-цию сравнивающую числа, и компилятор не предупредит вас об ошибке.

1.28. 64-битные значения в 32-битной среде В среде, где GPR-ы 32-битные, 64-битные значения хранятся и передаются ка к пары 32-битных значений 181.

1.28.1. Возврат 64-битного значения #include < std in t.h > uint64_t f () { return 0x1234567890ABCDEF; };

x8 6 64-битные значения в 32-битной среде возвращаются из функций в паре регистров EDX:EAX. Листинг 1.367: О п тим и зи рую щ и й І^Ѵ С 2010 f

_f

PROC mov mov ret ENDP

eax, -1867788817 edx, 305419896 0

90abcdefH 12345678H

181Кстати, в 16-битной среде, 32-битные значения передаются 16-битными парами точно так же: 3.29.4 (стр. 642)

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

397

ARM 64-битное значение возвращается в паре регистров R0-R1 — (R1 это старшая часть и R0 — младшая часть): Листинг 1.368: ОптимизирующийКеіІ 6/2013 (Режим ARM) ||f||

PROC LDR LDR BX ENDP

r0,|L0.12| r 1 ,|L 0 .1б| lr

|L0.12| DCD

0x90abcdef

DCD

0x12345678

|L0.16|

MIPS 64-битное значение возвращается в паре регистров V0-V1 ($2-$3) — (V0 ($2) это старшая часть и V1 ($3) — младшая часть): Листинг 1.369: ОптимизирующийGCC 4.4.5 (assembly listing)

lu i lu i li jr li

$3,-1867841536 $2,305397760 $3,$3,0xcdef $31 $2,$2,0x5678

# 0 x ffffffff9 0 a b 0 0 0 0 # 0 0 0 0 4 3 2 1 x 0

li li o ri j o ri

Листинг 1.370: ОптимизирующийGCC 4.4.5 (IDA) $v1, 0x90AB $v0, 0x1234 $v1, 0x90ABCDEF $ra $v0, 0x12345678

1.28.2. Передача аргументов, сложение, вычитание #include < std in t.h > uint64 t f add (uint64 t a, uint64_t b) { return a+b; }; void f add te s t () { # ifd e f GNUC p r i n t f ( "%lld\n" f_add(12345678901234, 23456789012345)); #else p r i n t f ( "%I64d\n ', f_add(12345678901234, 23456789012345)); #endif }; uint64 t f sub (uint64 t a, uint64_t b) { return a-b; };

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

398 x86

Листинг 1.371: О п тим и зи рую щ и й І^Ѵ С 2012 /Ob1 a$ = 8 _b$ = 16 _f_add PROC mov add mov adc ret _f_add ENDP

; size = 8 ; size = 8 eax, eax, edx, edx, 0

DWORD DWORD DWORD DWORD

PTR PTR PTR PTR

_a$[esp-4] _b$[esp-4] _a$[esp] _b$[esp]

_f_add _ te s t PROC push 5461 ; push 1972608889 ; push 2874 ; push 1942892530 ; c a ll _f_add push edx push eax push OFFSET $SG1436 ; c a ll _ p rin tf add esp, 28 ret 0 _f_add _ te s t ENDP _f_sub

f sub

PROC mov sub mov sbb ret ENDP

eax, eax, edx, edx, 0

DWORD DWORD DWORD DWORD

PTR PTR PTR PTR

00001555H 75939f79H 00000b3aH 73ce2ff2H

'%I64d', 0aH, 00H

_a$[esp-4] _b$[esp-4] _a$[esp] _b$[esp]

В f _ a d d _ t e s t ( ) видно, ка к каждое 64-битное число передается двумя 32-битными значениями, сначала старшая часть, затем младшая. Сложение и вычитание происходит т а кж е парами. При сложении, в начале складываются младшие 32 бита. Если при сложении был перенос, выстав­ ляется флаг CF. Следующая инструкция ADC складывает старшие части чисел, но т а кж е прибавля­ ет единицу если CF = 1. Вычитание т а кж е происходит парами. Первый SUB может т а кж е включить флаг переноса CF, ко­ торый затем будет проверяться в SBB: если флаг переноса включен, то от результата отнимется единица. Легко увидеть, ка к результат работы f_ a d d ( ) затем передается в p r i n t f ( ) . Листинг 1.372: GCC 4.8.1 -O1 -fno-inline f add: mov mov add adc ret f_a d d _te st: sub mov mov mov mov c a ll mov mov mov c a ll

eax, edx, eax, edx,

DWORD DWORD DWORD DWORD

esp, 28 DWORD PTR DWORD PTR DWORD PTR DWORD PTR _f_add DWORD PTR DWORD PTR DWORD PTR _ p rin tf

PTR PTR PTR PTR

[esp+12] [esp+16] [esp+4] [esp+8]

[esp+8], 1972608889 [esp+12], 5461 [esp], 1942892530 [esp+4], 2874

; ; ; ;

75939f79H 00001555H 73ce2ff2H 00000b3aH

[esp+4], eax [esp+8], edx [esp], OFFSET FLAT: LC0 ; "%lld\n"

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

399

add ret

esp, 28

mov mov sub sbb ret

eax, edx, eax, edx,

_f_sub: DWORD DWORD DWORD DWORD

PTR PTR PTR PTR

[esp+4] [esp+8] [esp+12] [esp+16]

Код GCC почти такой же.

ARM Листинг 1.373: ОптимизирующийКеіІ 6/2013 (Режим ARM) 2r

0r 0r

2r

f sub PROC SUBS SBC BX ENDP

0r 0r

f add PROC ADDS ADC BX ENDP

r1, r1,r3 lr

r1, r1,r3 lr

f add te s t PROC PUSH {r4 ,lr} LDR r2,|L0.68| LDR r3,|L0.72| LDR r0,|L0.76| LDR r1,|L0.80| BL f_add POP {r4 ,lr} MOV r2, r0 MOV r3, r1 ADR r0,|L0.84| B __2 p r in tf ENDP

0x75939f79 0x00001555 0x73ce2ff2 0x00000b3a

"%I64d\n"

|L0.68| DCD

0x75939f79

DCD

0x00001555

DCD

0x73ce2ff2

DCD

0x00000b3a

DCB

"%I64d\n",0

|L0.72| |L0.76| |L0.80| |L0.84|

Первое 64-битное значение передается в паре регистров R0 и R1, второе — в паре R2 и R3. В ARM т а кж е есть инструкция ADC (учитывающая флаг переноса) и SBC («subtract with carry» — вычесть с переносом). Важная вещь: когда младшие части слагаются/вычитаются, используются и нструк­ ции ADDS и SUBS с суффиксом -S. Суффикс -S означает «set flags» (установить флаги), а флаги (осо­ бенно флаг переноса) это то что однозначно нужно последующим инструкциями ADC/SBC. А иначе инструкции без суффикса -S здесь вполне бы подошли (ADD и SUB).

MIPS Листинг 1.374: ОптимизирующийGCC 4.4.5 (IDA) f_add: ; $a0 - старшая часть a (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

400

$a1 - младшая часть a $a2 - старшая часть b $a3 - младшая часть b addu $v1, $a3, $a1 ; суммировать младшие части addu $a0, $a2, $a0 ; суммировать старшие части будет ли перенос сгенерирован во время суммирования младших частей? установить $v0 в 1, если да s ltu $v0, $v1, $a3 jr $ra прибавить 1 к старшей части результата, если перенос должен был быть сгенерирован addu $v0, $a0 ; branch delay s lo t $v0 - старшая часть результата $v1 - младшая часть результата sub: $a0 $a1 $a2 $a3

; ;

; ;

старшая младшая старшая младшая

часть a часть a часть b часть b subu $v1, $a1, $a3 ; вычитать младшие части subu $v0, $a0, $a2 ; вычитать старшие части будет ли перенос сгенерирован во время вычитания младших частей? устано вить $a0 в 1, если да s ltu $a1, $v1 jr $ra вычесть 1 из старшей части результата, если перенос должен был быть сгенерирован subu $v0, $a1 ; branch delay s lo t старшая часть результата $v0 младшая часть результата $v1

f_a d d _te st: var_10 var 4

= -0x10 = -4 lu i addiu la sw sw lu i lu i li li li ja l li lw lu i lw lw la move move jr addiu

$LC0:

$gp, (__ gnu_local_gp >> 16) $sp, -0x20 $gp, (__ gnu_local_gp & 0xFFFF) $ra, 0x20+var_4($sp) $gp, 0x20+var_10($sp) $a1, 0x73CE $a3, 0x7593 $a0, 0xB3A $a3, 0x75939F79 $a2, 0x1555 f_add $a1, 0x73CE2FF2 $gp, 0x20+var_10($sp) $a0, ( $LC0 >>16) # "%lld\n" $t9, ( p r i n t f & 0xFFFF)($gp) $ra, 0x20+var_4($sp) $a0, ( $LC0 & 0xFFFF) # "%lld\n' $a3, $v1 $a2, $v0 $t9 $sp, 0x20

. a s c ii "%lld\n"

В MIPS н е т р е г и с т р а ф л а г о в , т а к ч т о э т а и н ф о р м а ц и я н е п р и с у т с т в у е т п о с л е и с п о л н е н и я а р и ф м е ­ тических операций. Т а к ч т о з д е с ь н е т и н с т р у к ц и й к а к ADC и л и SBB в x 8 6 . Ч т о б ы п о л у ч и т ь и н ф о р м а ц и ю о т о м , б ы л б ы в ы ­ с т а в л е н ф л а г п е р е н о с а , п р о и с х о д и т с р а в н е н и е ( и с п о л ь з у я и н с т р у к ц и ю SLTU), к о т о р а я в ы с т а в л я е т ц е л е в о й р е г и с т р в 1 и л и 0. Эта 1 и л и 0 з а т е м п р и б а в л я е т с я к и т о г о в о м у р е з у л ь т а т у , ил и в ы ч и т а е т с я .

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

401

1.28.3. Умножение, деление #include < std in t.h > a,

t _ 4 6 t n iu

t _ 4 6 tn iu

uint64_t f_mul / return a*b; };

b)

uint64_t f_ d iv ( uint64_t a, uint64_t b) r return a/b; }; t _ 4 6 t n iu

uint64_t f_rem ( uint64_t a, / return a % b; };

b)

x86 Листинг 1.375: О п тим и зи рую щ и й І^Ѵ С 2013 /Ob1 a$ = 8 ; size = 8 b$ = 16 ; size = 8 f mul PROC push ebp mov ebp, esp mov eax, DWORD PTR _b$[ebp+4] push eax mov ecx, DWORD PTR _b$[ebp] push ecx mov edx, DWORD PTR _a$[ebp+4] push edx mov eax, DWORD PTR _a$[ebp] push eax __ allm ul ; long long m u lt ip lic a tio n (умножение значений типа long long) c a ll pop ebp ret 0 f_mul ENDP a$ = 8 ; size = 8 b$ = 16 ; size = 8 f d iv PROC push ebp mov ebp, esp mov eax, DWORD PTR _b$[ebp+4] push eax mov ecx, DWORD PTR _b$[ebp] push ecx mov edx, DWORD PTR _a$[ebp+4] push edx mov eax, DWORD PTR _a$[ebp] push eax c a ll __a u lld iv ; unsigned long long d iv is io n (деление беззнаковых значений типа long long) pop ebp ret 0 f d iv ENDP a$ = 8 ; size := 8 b$ = 16 ; size = 8 f_ rem PROC push ebp mov ebp mov eax push eax mov ecx (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

402

_f_rem

push mov push mov push c a ll pop ret ENDP

ecx edx, DWORD PTR _a$[ebp+4] edx eax, DWORD PTR _a$[ebp] eax __aullrem unsigned long long remainder (вычисление беззнакового остатка) ebp 0

Умножение и деление — это более сложная операция, т а к что обычно, компилятор встраивает вызовы библиотечных функций, делающих это. Значение этих библиотечных функций, здесь: .5 (стр. 1005). Листинг 1.376: ОптимизирующийGCC 4.8.1 -fno-inline f mul: push mov mov mov mov imul imul mul add add pop ret

ebx edx, eax, ebx, ecx, ebx, ecx, edx ecx, edx, ebx

sub mov mov mov mov mov mov mov mov c a ll add ret

esp, 28 eax, DWORD PTR [esp+40] edx, DWORD PTR [esp+44] DWORD PTR [esp+8], eax eax, DWORD PTR [esp+32] DWORD PTR [esp+12], edx edx, DWORD PTR [esp+36] DWORD PTR [esp], eax DWORD PTR [esp+4], edx ___udivdi3 ; unsigned d iv is io n (беззнаковое деление) esp, 28

sub mov mov mov mov mov mov mov mov c a ll add ret

esp, 28 eax, DWORD PTR [esp+40] edx, DWORD PTR [esp+44] DWORD PTR [esp+8], eax eax, DWORD PTR [esp+32] DWORD PTR [esp+12], edx edx, DWORD PTR [esp+36] DWORD PTR [esp], eax DWORD PTR [esp+4], edx ___umoddi3 ; unsigned modulo (беззнаковый остаток) esp, 28

DWORD DWORD DWORD DWORD eax edx

PTR PTR PTR PTR

[esp+8] [esp+16] [esp+12] [esp+20]

ebx ecx

_ f_ d iv :

f rem:

GCC делает почти то ж е самое, тем не менее, встраивает код умножения прямо в функцию, посчи­ тав что т а к будет эффективнее. У GCC другие имена библиотечных функций: .4 (стр. 1005).

ARM Keil для режима Thumb вставляет вызовы библиотечных функций: Листинг 1.377: ОптимизирующийКеіІ 6/2013 (Режим Thumb) | | f_m ul|| PROC (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

403

PUSH BL POP ENDP

{r4 ,lr} __aeabi_ lmul { r4,pc}

|| f d i v | | PROC PUSH BL POP ENDP

{r4 ,lr} __aeabi_ uldivmod { r4,pc}

| | f rem|| PROC PUSH BL MOVS MOVS POP ENDP

{r4 ,lr} __aeabi_ uldivmod r0 , r2 r1 , r3 { r4,pc}

Keil для режима ARM, тем не менее, может сгенерировать код для умножения 64-битных чисел: Листинг 1.378: ОптимизирующийКеіІ 6/2013 (Режим ARM)

r0 , r12 { r4,pc}

1 r

||f_rem | PROC PUSH BL MOV MOV POP ENDP

3r 0r 1 , r

C O R P

v i d _f

PUSH BL POP ENDP

{r4 ,lr} r12, r4, r0, r2 r 1 , r 2, r 1 , r

IIf_mul | PROC PUSH UMULL MLA MLA MOV POP ENDP

{r4 ,lr} __aeabi_uldivmod { r4,pc}

{r4 ,lr} __aeabi_uldivmod r0 , r2 r1 , r3 { r4,pc}

MIPS ОптимизирующийGCC для MIPS может генерировать код для 64-битного умножения, но для 64­ битного деления приходится вызывать библиотечную функцию: Листинг 1.379: ОптимизирующийGCC 4.4.5 (IDA) f mul: mult mflo or or mult mflo addu or multu mfhi mflo jr addu

$a2, $v0 $at, $at, $a0, $a0 $v0, $at, $a3, $a2 $v1 $ra $v0,

$a1 $zero $zero $a3 $a0 $zero $a1

; NOP ; NOP

; NOP

$a2

f div: (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

404

var_10 = -0x10 var_4 = -4 lu i addiu la sw sw lw or ja lr or lw or jr addiu

$gp, $sp, $gp, $ra, $gp, $ t9 , $at, $t9 $at, $ra, $at, $ra $sp,

( gnu lo c a l gp >> 16) -0x20 (__gnu_local_gp & 0xFFFF) 0x20+var_4($sp) 0x20+var_10($sp) (__udivdi3 & 0xFFFF)($gp) $zero

$gp, $sp, $gp, $ra, $gp, $ t9 , $at, $t9 $at, $ra, $at, $ra $sp,

( gnu lo c a l gp >> 16) -0x20 (__gnu_local_gp & 0xFFFF) 0x20+var_4($sp) 0x20+var_10($sp) (__umoddi3 & 0xFFFF)($gp) $zero

$zero 0x20+var_4($sp) $zero 0x20

f_rem: var_10 = -0x10 var_4 = -4 lu i addiu la sw sw lw or ja lr or lw or jr addiu

$zero 0x20+var_4($sp) $zero 0x20

Тут т а к ж е много NOP-ов, это возможно заполнение delay slot-ов после инструкции умножения (она ведь работает медленнее прочих инструкций).

1.28.4. Сдвиг вправо #include < std in t.h > a)

V V

«^1

return

a

t _ 4 6 tn i u

f t _

uint64 { };

x8 6

a$ = 8 _f PROC mov mov shrd shr ret _f ENDP

Листинг 1.380: ОптимизирующийMSVC 2012 /Ob1 ; size = 8 eax, edx, eax, edx, 0

DWORD PTR a$[esp-4] DWORD PTR _a$[esp] edx, 7 7

Листинг 1.381: ОптимизирующийGCC 4.8.1 -fno-inline _ f: mov

edx, DWORD PTR [esp+8]

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

405

mov shrd shr ret

eax, DWORD PTR [esp+4] eax, edx, 7 edx, 7

Сдвиг происходит т а кж е в две операции: в начале сдвигается младшая часть, затем старшая. Но младшая часть сдвигается при помощи инструкции SHRD, она сдвигает значение в EAX на 7 бит, но подтягивает новые биты из EDX, т.е. из старшей части. Другими словами, 64-битное значение из пары регистров EDX:EAX, ка к одно целое, сдвигается на 7 бит и младшие 32 бита результата сохра­ няются в EAX. Старшая часть сдвигается куда более популярной инструкцией SHR: действительно, ведь освободившиеся биты в старшей части нужно просто заполнить нулями.

ARM В ARM нет такой инструкции ка к SHRD в x86, т а к что компилятору Keil приходится всё это делать, используя простые сдвиги и операции «ИЛИ»: Листинг 1.382: ОптимизирующийКеіІ 6/2013 (Режим ARM) ||f||

PROC LSR ORR LSR BX ENDP

r0 , r0,#7 r0 , r0 , r1,LSL #25 r1 , r1,#7 lr

Листинг 1.383: ОптимизирующийКеіІ 6/2013 (Режим Thumb) ||f||

r2,r1,#25 r0 , r0,#7 2r

0r 0r

PROC LSLS LSRS ORRS LSRS BX ENDP

r1 , r1,#7 lr

MIPS GCC для MIPS реализует тот ж е алгоритм, что сделал Keil для режима Thumb: Листинг 1.384: ОптимизирующийGCC 4.4.5 (IDA) f: s ll srl or jr srl

$v0, $v1, $v1, $ra $v0,

$a0, 25 $a1, 7 $v0, $v1 $a0, 7

1.28.5. Конвертирование 32-битного значения в 64-битное #include < std in t.h > in t6 4 _ t f (in t3 2 _ t a) { return a; };

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

406 x86

Листинг 1.385: ОптимизирующийMSVC 2012 a$ = 8 _f PROC mov cdq ret f ENDP

eax, DWORD PTR _a$[esp-4] 0

Здесь появляется необходимость расширить 32-битное знаковое значение в 64-битное знаковое. Конвертировать беззнаковые значения очень просто: нуж но просто выставить в 0 все биты в стар­ шей части. Но для знаковых типов это не подходит: знак числа должен быть скопирован в старшую часть числа-результата. Здесь это делает инструкция CDQ, она берет входное значение в EAX, рас­ ширяет его до 64-битного, и оставляет его в паре регистров EDX:EAX. Иными словами, инструкция CDQ узнает знак числа в EAX (просто берет самый старший бит в EAX) и в зависимости от этого, вы­ ставляет все 32 бита в EDX в 0 или в 1. Её работа в каком-то смысле напоминает работу инструкции MOVSX.

ARM Листинг 1.386: ОптимизирующийКеіІ 6/2013 (Режим ARM) PROC ASR BX ENDP

l|f|

r1 , r0,#31 lr

Keil для ARM работает иначе: он просто сдвигает (арифметически) входное значение на 31 бит вправо. Как мы знаем, бит знака это MSB, и арифметический сдвиг копирует бит знака в «появля­ ющихся» битах. Так что после инструкции ASR r 1 ,r 0 ,# 3 1 , R1 будет содержать 0xFFFFFFFF если входное значение было отрицательным, или 0 в противном случае. R1 содерж ит старшую часть возвращаемого 64­ битного значения. Другими словами, этот код просто копирует MSB (бит знака) из входного значе­ ния в R0 во все биты старшей 32-битной части итогового 64-битного значения.

MIPS GCC для MIPS делает то же, что сделал Keil для режима ARM: Листинг 1.387: ОптимизирующийGCC 4.4.5 (IDA) f: 1 3

$v0, $ra $v1, $a0

0 a $

sra jr move

1.29. SIMD SIMD это акроним: Single Instruction, Multiple Data. Как можно судить по названию, это обработка множества данных исполняя только одну и нструк­ цию. Как и FPU, эта подсистема процессора выглядит т а к ж е отдельным процессором внутри x86. SIMD в x86 начался с MMX. Появилось 8 64-битных регистров MM0-MM7. Каждый MMX-регистр может содержать 2 32-битных значения, 4 16-битных или ж е 8 байт. На­ пример, складывая значения двух MMX-регистров, можно складывать одновременно 8 8-битных значений. (В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

40 7

Простой пример, это некий графический редактор, который хранит открытое изображение ка к двумерный массив. Когда пользователь меняет яркость изображения, редактору нужно, например, прибавить некий коэффициент ко всем пикселям, или отнять. Для простоты можно представить, что изображение у нас бело-серо-черное и кажды й пиксель занимает один байт, то с помощью MMX можно менять яркость сразу у восьми пикселей. Кстати, вот причина почему в SIMD присутствуют инструкции с насыщением (saturation). Когда пользователь в графическом редакторе изменяет яркость, переполнение и антиперепол­ нение (underflow) не нужны, т а к что в SIMD имеются, например, инструкции сложения, которые ничего не будут прибавлять если максимальное значение уж е достигнуто, итд. Когда MMX только появилось, эти регистры на самом деле располагались в FPU-регистрах. Можно было использовать либо FPU либо MMX в одно и то ж е время. Можно подумать, что Intel решило немного сэкономить на транзисторах, но на самом деле причина такого симбиоза проще — более старая ОС не знающая о дополнительных регистрах процессора не будет сохранять их во время пе­ реключения задач, а вот регистры FPU сохранять будет. Таким образом, процессор с MMX + старая ОС + задача, использующая возможности MMX = все это может работать вместе. SSE — это расширение регистров до 128 бит, теперь уж е отдельно от FPU. AVX — расширение регистров до 256 бит. Немного о практическом применении. Конечно же, это копирование блоков в памяти (memcpy), сравнение (memcmp), и подобное. Еще пример: имеется алгоритм шифрования DES, который берет 64-битный блок, 56-битный ключ, шифрует блок с ключом и образуется 64-битный результат. Алгоритм DES можно легко предста­ вить в виде очень большой электронной цифровой схемы, с проводами, элементами И, ИЛИ, НЕ. Идея bitslice DES182 — это обработка сразу группы блоков и ключей одновременно. Скажем, на x86 переменная типа unsigned in t вмещает в себе 32 бита, т а к что там можно хранить промежуточные результаты сразу для 32-х блоков-ключей, используя 64+56 переменных типа unsigned int. Существует утилита для перебора паролей/хешей Oracle RDBMS (которые основаны на алгоритме DES), реализующая алгоритм bitslice DES для SSE2 и AVX — и теперь возможно шифровать одно­ временно 128 или 256 блоков-ключей: h t tp :/ /g o . y u r ic h e v . c o m / 1 7 3 1 3

1.29.1. Векторизация Векторизация183 это когда у вас есть цикл, который берет на вход несколько массивов и выдает, например, один массив данных. Тело цикла берет некоторые элементы из входных массивов, чтото делает с ними и помещает в выходной. Векторизация — это обрабатывать несколько элементов одновременно. Векторизация — это не самая новая технология: автор сих строк видел её по крайней мере на линейке суперкомпьютеров Cray Y-MP от 1988, когда работал на его версии-«лайт» Cray Y-MP EL 184

Например: fo r ( i = 0; i < 1024; i++) { C [i] = A [ i ] * B [ i ] ; } Этот фрагмент кода берет элементы из A и B, перемножает и сохраняет результат в C. Если представить, что кажды й элемент массива — это 32-битный int, то их можно загружать сразу по 4 из А в 128-битный XMM-регистр, из B в другой XMM-регистр и выполнив инструкцию PMULLD (Перемножить запакованные знаковые DWORD и сохранить младшую часть результата) и PMULHW (Перемножить запакованные знаковые DWORD и сохранить старшую часть результата), можно получить 4 64-битных произведения сразу. 182h ttp ://g o .y u ric h e v .c o m /1 7 3 2 9 183Wikipedia: vectorization 184Удаленно. Он находится в музее суперкомпьютеров: h ttp ://g o .yu rich e v .co m /1 7 0 8 1

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

408

Таким образом, тело цикла исполняется 1024/4 раза вместо 1024, что в 4 раза меньше, и, конечно, быстрее.

Пример слож ения Некоторые компиляторы умеют делать автоматическую векторизацию в простых случаях, напри­ мер, Intel C+ + 185. Вот очень простая функция:

In te l C + + Компилируем её при помощи Intel C + + 11.1.051 Win32: i c l i n t e l . c p p /QaxSSE2 / F a in t e l. a s m /Ox Имеем такое (в IDA): ; i n t __cdecl f ( i n t , i n t *, i n t *, i n t *) public ?f@@YAHHPAH00@Z ?f@@YAHHPAH00@Z proc near var_10 sz ar1 ar2 ar3

= = = = =

dword dword dword dword dword

push push push push mov te s t jle mov cmp jle cmp jbe mov sub lea neg cmp jbe

p tr -10h p tr 4 p tr 8 p tr 0Ch p tr 10h edi esi ebx esi edx , [esp+10h+sz] edx, edx lo c _15B eax , [esp+10h+ar3] edx, 6 lo c _143 eax , [esp+10h+ar2] short loc_36 esi , [esp+10h+ar2] esi , eax ecx , ds:0[edx*4] esi ecx , esi short loc_55

loc_36: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+21 cmp eax, [esp+10h+ar2] jnb loc_143 mov e s i, [esp+10h+ar2] sub e s i, eax lea ecx, ds:0[edx*4] cmp e s i, ecx jb loc_143 loc_55: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+34 185Еще о том, как Intel C++ умеет автоматически векторизовать циклы: Excerpt: Effective Automatic Vectorization

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

409

cmp jbe mov sub neg cmp jbe

eax, [esp+10h+ar1] short lo c 67 e si, [esp+10h+ar1] e si, eax esi ecx, esi short loc_7F

lo c 67

; CODE cmp jnb mov sub cmp jb

XREF : f ( i n t , i n t *, i n t * , i n t *)+59 eax, [esp+10h+ar1] lo c_ 143 e si, [esp+10h+ar1] e si, eax e si, ecx lo c_ 143

lo c 7F

; CODE mov and jz te s t jnz neg add shr

XREF : f ( i n t , i n t edi, e a x edi, 0 F h short lo c 9A edi, 3 lo c_ 162 edi edi, 10h edi, 2

*, ; ; ;

i n t * , i n t *)+65 edi = ar3 ar3 выров нен по 16-байтной границе? да

lo c 9A: ; CODE lea cmp jl mov sub and neg add te s t jbe mov mov mov xor

XREF : f ( i n t , i n t *, i n t * , i n t *)+84 ecx, [edi+4] edx, ecx lo c_ 162 ecx, edx ecx, edi ecx, 3 ecx ecx, edx edi, edi short loc_D6 ebx, [esp+10h+ar2] [esp+10h+var_10], ecx ecx, [esp+10h+ar1] e si, esi

lo c C1: ; CODE mov add mov inc cmp jb mov mov

XREF : f ( i n t , i n t *, i n t * , i n t *)+CD edx, [ecx+esi*4] edx, [ebx+esi*4] [eax+esi*4], edx esi e si, edi short loc_C1 ecx, [esp+10h+var_ 10] edx, [esp+10h+sz]

lo c D6

XREF : f ( i n t , i n t *, i n t * , i n t *)+B2 e si, [esp+10h+ar2] e si, [esi+edi*4] ; ar2+i*4 выровнен по 16-байтной границе? e si, 0Fh short loc_109 да! ebx, [esp+10h+ar1] e si, [esp+10h+ar2]

; CODE mov lea te s t jz mov mov

loc_ED: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *' + 105 movdqu xmml, xmmword p tr [ebx+edi*4] ; ar1+i*4 movdqu xmm0, xmmword p tr [esi+edi*4] ; ar2+i*4 не выровнен по 16-байтной границе, так что загружаем это в XMM0 paddd xmm1, xmm0 movdqa xmmword p tr [eax+edi*4], xmml ar3+i*4 add edi, 4 cmp edi, ecx jb short loc_ED jmp short loc_127 (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

410

loc_109: CODE XREF: f ( i n t , i n t * , i n t * , i n t * +E3 mov ebx, [esp+10h+ar1] mov e s i, [esp+10h+ar2] lo c 111: CODE XREF: f ( i n t , i n t * , i n t * , i n t * +125 movdqu xmm0, xmmword p tr [ebx+edi*4] paddd xmm0, xmmword p tr [esi+edi*4] movdqa xmmword p tr [eax+edi*4], xmm0 add edi, 4 cmp edi, ecx short loc_111 jb loc_127:

CODE XREF: f ( i n t , i n t * , i n t * , i n t * +107 » f ( i n t , i n t * , i n t * , i n t *)+164 cmp ecx, edx jnb short loc_15B mov e s i, [esp+10h+ar1] mov edi, [esp+10h+ar2]

loc_133: CODE XREF: f ( i n t , i n t * , i n t * , i n t * +13F mov ebx, [esi+ecx*4] add ebx, [edi+ecx*4] mov [eax+ecx*4], ebx inc ecx cmp ecx, edx short loc_133 jb short loc_15B jmp loc_143:

CODE XREF: f ( i n t , i n t * , i n t * , i n t * +17 » f ( i n t , i n t * , i n t * , i n t *)+3A . . . mov e s i, [esp+10h+ar1] mov edi, [esp+10h+ar2] xor ecx, ecx

loc_14D: CODE XREF: f ( i n t , i n t * , i n t * , i n t * +159 mov ebx, [esi+ecx*4] add ebx, [edi+ecx*4] mov [eax+ecx*4], ebx inc ecx cmp ecx, edx short loc_14D jb loc_15B:

CODE XREF: f ( i n t , i n t * , i n t * , i n t * +A » f ( i n t , i n t * , i n t * , i n t *)+129 . . . xor eax, eax ecx pop ebx pop esi pop edi pop retn

loc_162:

CODE XREF: f ( i n t , i n t * , i n t * , i n t * +8C » f ( i n t , i n t * , i n t * , i n t *)+9F xor ecx, ecx short loc_127 jmp ?f@@YAHHPAH00@Z endp

Инструкции, имеющие отношение к SSE2 это: • MOVDQU (Move Unaligned Double Quadword) — она просто загруж ает 16 байт из памяти в XMMрегистр. • PADDD (Add Packed Integers) — складывает сразу 4 пары 32-битных чисел и оставляет в первом операнде результат. Кстати, если произойдет переполнение, то исключения не произойдет и никакие флаги не установятся, запишутся просто младшие 32 бита результата. Если один из операндов PADDD — адрес значения в памяти, то требуется чтобы адрес был выровнен по 16-байтной границе. Если он не выровнен, произойдет исключение 186. 186О выравнивании данных см. также: Wikipedia: Выравнивание данных

(В целях сбора статистики) если вы дочитали до этого места, пожалуйста, нажмите здесь. Спасибо!

411

• MOVDQA (Move Aligned Double Quadword) — тож е что и MOVDQU, только подразумевает что ад­ рес в памяти выровнен по 16-байтной границе. Если он не выровнен, произойдет исключение. MOVDQA работает быстрее чем MOVDQU, но требует вышеозначенного. Итак, эти SSE2-инструкции исполнятся только в том случае если еще осталось просуммировать 4 пары переменных типа in t плюс если указатель ar3 выровнен по 16-байтной границе. Более того, если еще и ar2 выровнен по 16-байтной границе, то будет выполняться этот фрагмент кода: movdqu paddd movdqa

xmm0, xmmword p tr [ebx+edi*4] ; ar1+i*4 xmm0, xmmword p tr [esi+edi*4] ; ar2+i*4 xmmword p tr [eax+edi*4], xmm0 ; ar3+i*4

А иначе, значение из ar2 загрузится в XMM0 используя инструкцию MOVDQU, которая не требует выровненного указателя, зато может работать чуть медленнее: movdqu xmm1, xmmword p tr [ebx+edi*4] ; ar1+i*4 movdqu xmm0, xmmword p tr [esi+edi*4] ; ar2+i*4 не выровнен по 16-байтной границе, так что загружаем это в XMM0 paddd xmm1, xmm0 movdqa xmmword p tr [eax+edi*4], xmm1 ; ar3+i*4 А во всех остальных случаях, будет исполняться код, который был бы, ка к если бы не была вклю­ чена поддержка SSE2.

GCC Но и GCC умеет кое-что векторизировать187, если компилировать с опциями -O3 и включить под­ д е р ж ку SSE2:-msse2. Вот что вышло (GCC 4.4.1): ; f ( i n t , i n t *, i n t *, i n t *) public _Z1fiPiS_S_ _Z1fiPiS_S_ proc near var_18 var_14 var_10 arg_0 arg_4 arg_8 arg_C

= = = = = = =

dword dword dword dword dword dword dword

push mov push push push sub mov mov mov mov te s t jle cmp lea ja loc_80484C1

p tr -18h p tr -14h p tr -10h p tr 8 p tr 0Ch p tr 10h p tr 14h ebp ebp, esp edi esi ebx esp, 0Ch ecx, [ebp+arg_0] e si, [ebp+arg_4] edi, [ebp+arg_8] ebx, [ebp+arg_C] ecx, ecx short loc_80484D8 ecx, 6 eax, [ebx+10h] short loc_80484E8

; CODE XREF: f ( i n t , i n t * , i n t * , i n t ; f ( i n t , i n t * , i n t * , i n t *)+61 . . . xor eax, eax nop lea e si, [esi+0]

(+4B

7Подробнее о векторизации в GCC: h ttp ://g o .yu rich e v.co m /1 7 0 8 3

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

412

loc_80484C8: ; CODE mov add mov add cmp jnz

XREF: f ( i n t , i n t * , i n t * , i n t *)+36 edx, [edi+eax*4] edx, [esi+eax*4] [ebx+eax*4], edx eax, 1 eax, ecx short loc_80484C8

loc_80484D8: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+17 ; f ( i n t , i n t * , i n t * , i n t *)+A5 add esp, 0Ch xor eax, eax pop ebx pop esi pop edi pop ebp retn a lig n 8 loc_80484E8: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+1F te s t b l, 0Fh jnz short loc_80484C1 lea edx, [esi+10h] cmp ebx, edx jbe loc_8048578 loc_80484F8: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+E0 lea edx, [edi+10h] cmp ebx, edx ja short loc_8048503 cmp edi, eax jbe short loc_80484C1 loc_8048503: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+5D mov eax, ecx shr eax, 2 mov [ebp+var_14], eax shl eax, 2 te s t eax, eax mov [ebp+var_10], eax jz short loc_8048547 mov [ebp+var_18], ecx mov ecx, [ebp+var_14] xor eax, eax xor edx, edx nop loc_8048520: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+9B movdqu xmm1, xmmword p tr [edi+eax] movdqu xmm0, xmmword p tr [esi+eax] add edx, 1 paddd xmm0, xmm1 movdqa xmmword p tr [ebx+eax], xmm0 add eax, 10h cmp edx, ecx jb short loc_8048520 mov ecx, [ebp+var_18] mov eax, [ebp+var_10] cmp ecx, eax jz short loc_80484D8 loc_8048547: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+73 lea edx, ds:0[eax*4] add e si, edx add edi, edx add ebx, edx lea e si, [esi+0] loc_8048558: ; CODE XREF: f ( i n t , i n t * , i n t * , i n t *)+CC (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

413

mov add add add add mov add cmp jg add xor pop pop pop pop retn loc_8048578

; CODE cmp jnb jmp _Z1fiPiS_S_ endp

edx, [e d i] eax, 1 edi, 4 edx, [e s i] e si, 4 [ebx], edx ebx, 4 ecx, eax short loc_8048558 esp, 0Ch eax, eax ebx esi edi ebp XREF: f ( i n t , i n t * , i n t * , i n t *)+52 eax, esi lo c 80484C1 loc_80484F8

Почти то ж е самое, хотя и не т а к дотошно, ка к Intel C+ + .

Пример копирования блоков Вернемся к простому примеру memcpy() (1.17.2 (стр. 196)): #include void my_memcpy (unsigned char* dst, unsigned char* src, size _t cnt) { size _t i ; fo r (i=0; i< c n t; i++) d s t[i]= s rc [i]; }; И вот что делает оптимизирующий GCC 4.9.1: Листинг 1.388: ОптимизирующийGCC 4.9.1 x64 my_memcpy: ; RDI = адрес назначения ; RSI = исходный адрес ; RDX = размер блока te s t rdx, rdx je . L41 lea rax, [ rdi+16] cmp r s i, rax lea rax, [ rsi+16] setae cl cmp rd i, rax setae al or c l, al je . L13 cmp rdx, 22 jbe . L13 mov rcx, r s i push rbp push rbx neg rcx and ecx, 15 cmp rcx, rdx cmova rcx, rdx xor eax, eax te s t rcx, rcx je . L4 movzx eax, BYTE PTR [ r s i ] (В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

414

cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov je movzx cmp mov jne movzx mov mov

rcx, 1 BYTE PTR [ r d i ] , al . L15 eax, BYTE PTR [rs i+ 1 ] rcx, 2 BYTE PTR [rd i+ 1 ], al . L16 eax, BYTE PTR [rs i+ 2 ] rcx, 3 BYTE PTR [rd i+ 2 ], al . L17 eax, BYTE PTR [rs i+ 3 ] rcx, 4 BYTE PTR [rd i+ 3 ], al . L18 eax, BYTE PTR [rs i+ 4 ] rcx, 5 BYTE PTR [rd i+ 4 ], al . L19 eax, BYTE PTR [rs i+ 5 ] rcx, 6 BYTE PTR [rd i+ 5 ], al . L20 eax, BYTE PTR [rs i+ 6 ] rcx, 7 BYTE PTR [rd i+ 6 ], al . L21 eax, BYTE PTR [rs i+ 7 ] rcx, 8 BYTE PTR [rd i+ 7 ], al . L22 eax, BYTE PTR [rs i+ 8 ] rcx, 9 BYTE PTR [rd i+ 8 ], al . L23 eax, BYTE PTR [rs i+ 9 ] rcx, 10 BYTE PTR [rd i+ 9 ], al . L24 eax, BYTE PTR [rsi+10] rcx, 11 BYTE PTR [rd i+ 1 0 ], a l . L25 eax, BYTE PTR [rsi+11] rcx, 12 BYTE PTR [rd i+ 1 1 ], a l . L26 eax, BYTE PTR [rsi+12] rcx, 13 BYTE PTR [rd i+ 1 2 ], a l . L27 eax, BYTE PTR [rsi+13] rcx, 15 BYTE PTR [rd i+ 1 3 ], a l .L28 eax, BYTE PTR [rsi+14] BYTE PTR [rd i+ 1 4 ], a l eax, 15

mov lea sub lea sub shr add mov sal cmp jbe

r10, rdx r9 , [rdx-1] r10, rcx r8, [r10-16] r9, rcx r8 , 4 r8 , 1 r11, r8 r11, 4 r9, 14 .L6

. L4:

(В целях сбора ста тисти ки) если вы дочитали до этого места, пожалуйста, нажмите зд е с ь . Спасибо!

415

lea xor add xor

rbp, r9d, rcx, ebx,

[ rsi+ rcx] r9d rdi ebx

movdqa add movups add cmp jb add cmp je

xmm0, XMMWORD PTR [rbp+0+r9; rbx, 1 XMMWORD PTR [rc x + r9 ], xmm0 r9 , 16 rbx, r8 . L7 rax, r11 r10, r11 . L1

movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe

ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1 ecx, BYTE rcx, rdx, . L1

. L7:

. L6: BYTE PTR [ rsi+ rax] PTR [ r d i+ ra x ], cl [ rax+1] rcx BYTE PTR [rsi+1+rax] PTR [rdi+ 1+ rax], cl [ rax+2] rcx BYTE PTR [rsi+2+rax] PTR [rdi+ 2+ rax], cl [ rax+3] rcx BYTE PTR [rsi+3+rax] PTR [rdi+ 3+ rax], cl [ rax+4] rcx BYTE PTR [rsi+4+rax] PTR [rdi+ 4+ rax], cl [ rax+5] rcx BYTE PTR [rsi+5+rax] PTR [rdi+ 5+ rax], cl [ rax+6] rcx BYTE PTR [rsi+6+rax] PTR [rdi+ 6+ rax], cl [ rax+7] rcx BYTE PTR [rsi+7+rax] PTR [rdi+ 7+ rax], cl [ rax+8] rcx BYTE PTR [rsi+8+rax] PTR [rdi+ 8+ rax], cl [ rax+9] rcx BYTE PTR [rsi+9+rax] PTR [rdi+ 9+ rax], cl [ rax+10] rcx BYTE PTR [rsi+10+rax] PTR [rdi+10+rax], c l [ rax+11] rcx

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

416

movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov lea cmp jbe movzx mov

ecx, BYTE rcx, rdx, .L1 ecx, BYTE rcx, rdx, .L1 ecx, BYTE rcx, rdx, .L1 edx, BYTE

pop pop

rbx rbp

BYTE PTR [rsi+11+rax] PTR [rdi+11+rax], c l [ rax+12] rcx BYTE PTR [rsi+12+rax] PTR [rdi+12+rax], c l [ rax+13] rcx BYTE PTR [rsi+13+rax] PTR [rdi+13+rax], c l [ rax+14] rcx BYTE PTR [rsi+14+rax] PTR [rdi+14+rax], d l

. L1: . L41: rep ret .L13: eax

xor

eax,

movzx mov add cmp jne rep ret

ecx, BYTE PTR [ rsi+ rax] BYTE PTR [ r d i+ ra x ], cl rax, 1 rax, rdx .L3

mov jmp

eax, .L4

14

mov jmp

eax, .L4

1

mov jmp

eax, .L4

2

mov jmp

eax, .L4

3

mov jmp

eax, .L4

4

mov jmp

eax, .L4

5

mov jmp

eax, .L4

6

mov jmp

eax, .L4

7

mov jmp

eax, .L4

8

mov jmp

eax, .L4

9

mov jmp

eax, .L4

10

mov jmp

eax, .L4

11

mov jmp

eax, .L4

12

. L3:

. L28: .L15: .L16: .L17: .L18: .L19: . L20: . L21: . L22: . L23: . L24: . L25: . L26:

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

417

. L27: mov jmp

eax, 13 . L4

1.29.2. Реализация strlen() при помощи SIMD П р е ж д е всего, с л е д у е т з а м е т и т ь , ч т о S I M D - и н с т р у к ц и и м о ж н о в с т а в л я т ь в С и / С и + + к о д п р и п о м о щ и с п е ц и а л ь н ы х м а к р о с о в 188. В M S V C , ч а с т ь н а х о д и т с я в ф а й л е i n t r i n . h . Имеется возможность реализовать ф у н к ц и ю

s t r l e n ( ) 189 п р и п о м о щ и S I M D - и н с т р у к ц и й , р а б о т а ю ­

щ и й в 2- 2. 5 р а з а б ы с т р е е о б ы ч н о й р е а л и з а ц и и . Э т а ф у н к ц и я б у д е т з а г р у ж а т ь в x M M - р е г и с т р с р а з у 16 б а й т и п р о в е р я т ь к а ж д ы й на н о л ь 190

size_t strlen_sse2(const char * s t r ) { re g is te r size_t len = 0; const char *s = str; bool str_is_aligned=(((unsigned int)str)&0xFFFFFFF0) == (unsigned i n t ) s t r ; if

(str_is_aligned==false) return s trle n ( s t r ) ;

_m128i xmm0 = _mm_setzero_si128(); _m128i xmm1; i n t mask = 0; for ( ;;) { xmm1 = _mm_load_si128((_m128i * ) s ) ; xmm1 = _mm_cmpeq_epi8(xmm1, xmm0); i f ((mask = _mm_movemask_epi8(xmm1)) != 0) { unsigned long pos; _BitScanForward(&pos, mask); len += (size_t)pos; break; } s += s iz e o f(_m128i); len += s iz e o f(_m128i); }; return len; } Компилируем в MSVC 2 0 1 0 с опцией /Ox: Л и с т и н г 1.389: Оптим изирую щ ийM SVC 2 0 1 0

_pos$75552 = -4 ; size = 4 _str$ = 8 ; size = 4 ?strlen_sse2@@YAIPBD@Z PROC ; strlen_sse2 push mov and mov sub push mov and xor mov

ebp ebp, esp, eax, esp, esi esi, esi, edx, ecx,

esp -16 ; fffffff0 H DWORD PTR str$[ebp] ; 0000000cH 12 eax -16 edx eax

; fffffff0 H

188MSDN: MMX, SSE, and SSE2 Intrinsics 189strlen() — стандартная функция Си для подсчета длины строки 190Пример базируется на исходнике отсюда: http://go.yurichev.com/17330. (В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

418

cmp je lea npad

n e L_l +J s © rH rH _J _J

e s i, eax SHORT $LN4@strlen sse edx, DWORD PTR [eax+1] 3 ; выровнять следующую метку sse: mov c l, BYTE PTR [eax] inc eax te s t c l, c l jne SHORT $LL11@strlen_sse sub eax, edx esi pop mov esp, ebp ebp pop ret 0 sse: movdqa xmml, XMMWORD PTR [eax] pxor xmm0, xmm0 pcmpeqb xmml, xmm0 pmovmskb eax, xmml te s t eax, eax jne SHORT $LN9@strlen_sse sse: movdqa xmml, XMMWORD PTR [ecx+16] add ecx, 16 ; 00000010H pcmpeqb xmm1, xmm0 add edx, 16 ; 00000010H eax, xmm1 te s t eax, eax SHORT $LL3@strlen_sse je sse: bsf eax, eax mov ecx, eax mov DWORD PTR _pos$75552[esp+16], eax lea eax, DWORD PTR [ecx+edx] esi pop mov esp, ebp ebp pop ret 0 ?strlen_sse2@@YAIPBD@Z ENDP ; strlen_sse2 1 n e lr t s © S _J

СГ

7?

3 CO

3 о
x11;

5 x > 5 a

x12 = x13 = x14 = x15 = x16 = x17 = x18 = x19 = x20 = x21 = x22 = *out2 x23 = x24 = x25 = x26 = x27 = x28 = x29 = x30 = x31 = x32 = x33 = x34 = x35 = *out4 x36 = x37 = x38 = x39 = x40 = x41 = x42 = x43 = x44 = x45 = x46 = *out1 x47 = x48 = x49 = x50 = x51 = x52 = x53 = x54 = x55 = x56 = *out3

x13 & x8; a5 & ~a4; x3 > x14; a6 | x16; x15 > x17; a2 | x18; x14 > x19; a1 & x20; x12 > ~x21 >= x22; x1 | x5; x23 > x8; x18 & ~x2; a2 & ~x25; x24 > x26; x6 | x7; x28 > x25; x9 > x24; x18 & ~x30 a2 & x31; x29 > x32; a1 & x33; x27 > x34; >= x35; a3 & x28; x18 & ~x36 a2 | x3; x37 > x38; a3 | x31; x24 & ~x37 x41 | x3; x42 & ~a2; x40 > x43; a1 & ~x44; x39 > ~x45 >= x46; x33 & ~x9; x47 > x39; x4 > x36; x49 & ~x5; x42 | x18; x51 > a5; a2 & ~x52; x50 > x53; a1 | x54; x48 > ~x55 >= x56;

} Здесь много локальных переменных. Конечно, дал еко не все они б уд ут в локальном стеке . К о м п и ­ лируем обычным MSVC 2 0 0 8 с опцией /Ox: Л и с т и н г 1.390: Оптим изирую щ ийM SVC 2 0 0 8

PUBLIC _s1 ; Function compile fla g s : /Ogtpy _TEXT SEGMENT _x6$ = -20 size = 4 _x3$ = -16 size = 4 _x1$ = -12 size = 4 _x8$ = -8 size = 4 x4$ = -4 size = 4 a1$ = 8 size = 4 a2$ = 12 size = 4 a3$ = 16 size = 4 x33$ 20 size = 4 _x7$ = 20 size = 4 _a4$ = 20 size = 4 (В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

423

2 II $ ш a 1

tv326 = 28 x36$ = 28 x28$ = 28

8 2 = $ 6 a 1

; ; ; ; ; ; ; ;

size size size size size size size size size size

= = = = = = = = = =

4 4 4 4 4 4 4 4 4 4

(В целях сбора с т а т и с т и к и )

xb e

i] d e

out1$ = 32 _x24$ = 36 _out2$ = 36 _out3$ = 40 _out4$ = 44 si PROC sub esp, 20 00000014H mov edx, DWORD PTR a5$[esp+16] push ebx mov ebx, DWORD PTR a4$[esp+20] push ebp push esi mov esi, DWORD PTR a3$[esp+28] push edi mov edi, ebx not edi mov ebp, edi and edi, DWORD PTR a5$[esp+32] mov ecx, edx not ecx and ebp, esi mov eax, ecx and eax, esi and ecx, ebx mov DWORD PTR _x1$[esp+36], eax xor eax, ebx mov e s i, ebp or e s i, edx mov DWORD PTR _x4$[esp+36], esi and e s i, DWORD PTR _a6$[esp+32] mov DWORD PTR _x7$[esp+32], ecx mov edx, esi xor edx, eax mov DWORD PTR _x6$[esp+36], edx mov edx, DWORD PTR _a3$[esp+32] xor edx, ebx mov ebx, esi xor ebx, DWORD PTR _a5$[esp+32] mov DWORD PTR _x8$[esp+36], edx and ebx, edx mov ecx, edx mov edx, ebx xor edx, ebp or edx, DWORD PTR _a6$[esp+32] not ecx and ecx, DWORD PTR _a6$[esp+32] xor edx, edi mov edi, edx or edi, DWORD PTR _a2$[esp+32] mov DWORD PTR _x3$[esp+36], ebp mov ebp, DWORD PTR _a2$[esp+32] xor edi, ebx and edi, DWORD PTR _ai$[esp+32] mov ebx, ecx xor ebx, DWORD PTR _x7$[esp+32] not edi or ebx, ebp xor edi, ebx mov ebx, edi mov edi, DWORD PTR _out2$[esp+32 xor ebx, DWORD PTR [e d i] not eax xor ebx, DWORD PTR _x6$[esp+36] and eax, edx mov DWORD PTR если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

424

mov or mov or mov xor mov xor not and mov and xor xor not mov and and xor mov xor xor mov mov and mov or mov not and or xor not and not or not and or xor mov xor xor mov not and not and and xor or not xor not and xor not mov xor mov xor pop pop xor pop mov pop add ret si

ebx, DWORD PTR _x7$[esp+32] ebx, DWORD PTR _x6$[esp+36] edi, esi edi, DWORD PTR _x1$[esp+36] DWORD PTR _x28$[esp+32], ebx edi, DWORD PTR _x8$[esp+36] DWORD PTR _x24$[esp+32], edi edi, ecx edi edi, edx ebx, edi ebx, ebp ebx, DWORD PTR _x28$[esp+32] ebx, eax eax DWORD PTR _x33$[esp+32], ebx ebx, DWORD PTR _a1$[esp+32] eax, ebp eax, ebx ebx, DWORD PTR _out4$[esp+32] eax, DWORD PTR [ebx] eax, DWORD PTR _x24$[esp+32] DWORD PTR [ebx] , eax eax, DWORD PTR _x28$[esp+32] eax, DWORD PTR _a3$[esp+32] ebx, DWORD PTR _x3$[esp+36] edi, DWORD PTR _a3$[esp+32] DWORD PTR _x36$[esp+32], eax eax eax, edx ebx, ebp ebx, eax eax eax, DWORD PTR _x24$[esp+32] ebp eax, DWORD PTR _x3$[esp+36] esi ebp, eax eax, edx eax, DWORD PTR _a5$[esp+32] edx, DWORD PTR _x36$[esp+32] edx, DWORD PTR _x4$[esp+36] ebp, edi edi, DWORD PTR _out1$[esp+32] eax eax, DWORD PTR _a2$[esp+32] ebp ebp, DWORD PTR _a1$[esp+32] edx, esi eax, edx eax, DWORD PTR _a1$[esp+32] ebp ebp, DWORD PTR [e d i] ecx ecx, DWORD PTR _x33$[esp+32] ebp, ebx eax DWORD PTR [e d i] , ebp eax, ecx ecx, DWORD PTR _out3$[esp+32] eax, DWORD PTR [ecx] edi esi eax, ebx ebp DWORD PTR [ecx] , eax ebx esp, 20 0 ENDP

(В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

425

5 переменны х компилятору пришлось разместить в локальном стеке. Теперь попробуем то ж е самое только в 64-битной версии MSVC 2008: Л и с т и н г 1.391: О п т и м и з и р у ю щ и й І^ Ѵ С 2 0 0 8

p] sr $[ 5 a R T P

a1$ = 56 a2$ = 64 a3$ = 72 a4$ = 80 x36$1$ = 88 a5$ = 88 a6$ = 96 out1$ = 104 out2$ = 112 out3$ = 120 out4$ = 128 si PROC $LN3: mov QWORD PTR [ rsp+24], rbx mov QWORD PTR [rsp+32], rbp mov QWORD PTR [rsp+16], rdx mov QWORD PTR [rsp+8], rcx push rsi push rdi push r12 push r13 push r14 push r15 mov r15, QWORD mov rcx, QWORD mov rbp, r8 mov r10, r9 mov rax, r15 mov rdx, rbp not rax xor rdx, r9 not r10 mov r11, rax and rax, r9 mov r s i, r10 mov QWORD PTR x36$1$[rsp], rax and r11, r8 and r s i, r8 and r10, r15 mov r13, rdx mov rbx, r11 xor rbx, r9 mov r9 , QWORD mov r12, r s i or r12, r15 not r13 and r13, rcx mov r14, r12 and r14, rcx mov rax, r14 mov r8 , r14 xor r8 , rbx xor rax, r15 not rbx and rax, rdx mov r d i, rax xor r d i, r s i or r d i, rcx xor r d i, r10 and rbx, r d i mov rcx, r d i or rcx, r9 xor rcx, rax mov rax, r13 xor rax, QWORD p] sr $[ 6 a R T P

p] sr $[ 2 a R T P

p] sr $[ 1 $ 6 3 x R T P

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

426

rcx, rax, r9 rcx rcx, rax rax, QWORD rcx, QWORD PTR [rax] rcx, r8 QWORD PTR [r a x ], rcx rax, QWORD rcx, r14 rax, r8 rcx, r11 r11, r9 rcx, rdx QWORD PTR x36$1$[rsp], rax r8 , r s i rdx, rcx rdx, r13 rdx rdx, r d i r10, rdx r10, r9 r10, rax r10, rbx rbx rbx, r9 rax, r10 rax, QWORD PTR a1$[rsp] rbx, rax rax, QWORD rbx, QWORD PTR [rax] rbx, rcx QWORD PTR [r a x ], rbx rbx, QWORD rbx, rbp r9 , rbx r9 r9 , rd i r8 , r11 rax, QWORD r8 , r9 r9 r9 , rcx rdx, rbp rbp, QWORD PTR [rsp+80] r9 , r s i rbx, r12 rcx, r11 rcx r14 r13 rcx, r9 r9 , rd i rbx, r14 r9 , r15 rcx, rdx rdx, QWORD r9 rcx r13, r10 r9 , r11 rcx, rdx r9 , rbx rbx, QWORD PTR [rsp+72] rcx rcx, QWORD PTR [rax] r9 , rdx r9 rcx, r8 QWORD PTR [r a x ], rcx p] sr $[ 1 a R T P D R O W Q

p] sr $[ 2 t 3 o R T P

p] sr $[ 1 $ 6 3 x R T P p] sr $[

t 3 o R T P

p] sr $[ 1 $ 6 3 x R T P p] sr $[ 1 t 3 o R T P p] sr $[ 1 a R T P

and or not xor mov xor xor mov mov mov or or mov xor mov mov mov xor not and mov and xor xor not and mov and xor mov xor xor mov mov and mov not and or mov xor not and or mov or xor mov not not not and or and xor xor mov not not and and and xor mov not xor or not xor mov

(В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

427

si

mov xor xor xor mov pop pop pop pop pop pop ret ENDP

rax, QWORD PTR out3$[rsp] r9 , r13 r9 , QWORD PTR [rax] r9, r8 QWORD PTR [r a x ], r9 r15 r14 r13 r12 rdi rsi 0

Компилятор ничего не выделил в локальном стеке , а x36 это синоним для a5. Кстати , сущ еств ую т процессоры с е щ е большим количеством GPR, например, Itanium — 128 р е ги ­ стров.

1.30.2. ARM 64-битны е и нстр укци и появились в ARMv8.

1.30.3. Числа с плавающей запятой О том к а к п роисходит работа с числами с плаваю щ ей запятой в x 8 6 -6 4 , ч и тай те здесь: 1.31 (стр. 4 2 8 ).

1.30.4. Критика 64-битной архитектуры Некоторы е люди иногда сетую т на то что у ка з а т е л и теперь 64-битные: ведь теп ер ь для хр ан ен ия всех у ка з а т е л е й н у ж н о в 2 раза больше места в памяти, в т.ч. и в кэш -п ам яти, не смотря на то что x64-процессоры м огут адресовать только 48 бит внеш ней RAM195. У ка з а те л и у ж е настолько вышли из моды, что мне приходится вступать по этому по­ воду в споры. Если говорить о моем 64-разрядном компьютере, то, если дей ств и тель ­ но заботиться о производительности моего компьютера, мне приходится признать, что лучш е о тказать ся от использования у ка з а те л е й , поскольку на моей м аш и не 6 4 ­ битные регистры, но всего 2 ги га б а й та оперативной памяти. Поэтому у у к а з а те л я никогд а не бывает больше 32 зн ач ащ и х битов. Но к а ж д ы й раз, когда я использую ук азатель , это сто и т мне 64 бита, и это уд в аи вает размер моей структуры данных. Более того, это е щ е и д ет и в кэш -память, и половины кэш -п ам я ти к а к не бывало, а за это приходится платить - кэш пам ять дорогая. Поэтому я на самом деле пытаюсь сейчас пробовать новые варианты, то есть мне приходится вместо у к а з а т е л е й использовать массивы. Я создаю сложны е макросы, то есть создаю видимость использования у к а з а те л е й , хотя на самом деле их не исполь­ зую. ( Дональд К н у т в "Кодеры за работой. Размышления о ремесле программиста". ) Некоторы е люди дел аю т свои аллокаторы памяти. Интересен случай с CryptoMiniSat196. Эта про­ грамма довольно редко использует более 4GiB памяти, но она очень акти в н о использует у к а з а ­ тели. Т а к что, на 32-битной платф орме она требовала меньш е памяти, чем на 64-битной. Чтобы справиться с этой проблемой, автор создал свой алл окатор (в ф айлах clauseallocator.(h\cpp)), ко то ­ рый позволяет иметь доступ к выделенной памяти используя 32-битны е иден ти ф и катор ы вместо 64-битны х у ка з а те л е й . 195Random-Access Memory 196https://github.com/msoos/cryptominisat/

(В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

428

1.31. Работа с числами с плавающей запятой (SIMD) Разумеется, FPU остался в х86-совместимы х процессорах в то время, когда ввели расширения S IM D . SIMD-р асш ирения (SSE2) позволяют удобнее работать с числами с плаваю щ ей запятой. Ф ормат чисел остается т о т ж е (IEEE 754). Т а к что современные компиляторы (включая те, что компилирую т под x 8 6 -6 4 ) обычно используют SIMD-и н стр укц и и вместо FPU-и нструкци й . Это, м ож но сказать, хорош ая новость, потому что работать с ними легче. Примеры будем использовать из секц и и о FPU: 1.20 (стр. 2 1 9 ).

1.31.1. Простой пример 0 1 d t (Л V e d u иl с ■н

h> u o d

a

double f (double ble b) { return a/3.14 + b*4.1; }; i n t main () { p r in t f ( "% f\n", f( 1 .2 , 3 .4 ) ); };

x64 Л и с ти н г 1.392: Оптим изирую щ ийM SVC 20 1 2 x 64

real@4010666666666666 DQ 04010666666666666r _real@40091eb851eb851f DQ 040091eb851eb851fr a$ = 8 b$ = 16 f PROC divsd mulsd addsd ret f ENDP

; 4.1 ; 3.14

xmm0, QWORD PTR real@40091eb851eb851f xmm1, QWORD PTR __real@4010666666666666 xmm0, xmm1 0

Собственно, входные значения с плаваю щей запятой п ередаю тся через регистры XMM0-XMM3, а остальные — через с т е к 197.

а п ередается через XMM0, b — через XMM1. Но X M M -регистры ( к а к мы у ж е знаем из с екц и и о SIM D: 1.29 (стр. 4 0 6 )) 128-битны е, а значения ти п а double— 64-битны е, т а к что используется только младшая половина регистра. DIVSD это SSE-и нструкци я, о зн ач ает «Divide Scalar Double-Precision Floating-Point Values», и просто д е л и т зн ачен ие типа double на другое, л е ж а щ и е в м ладш их половинах операндов. Константы закодированы компилятором в ф ормате IEEE 754. MULSD и ADDSD работаю т т а к ж е , только производят у м н о ж е н и е и сло ж ен и е. Результат работы ф ункц и и типа double ф ункц и я оставляет в р еги стре XMM0. К а к рабо тае т нео п тим и зирую щ и й MSVC: ____________________________________ Л и с ти н г 1.393: MSVC 20 1 2 x6 4 ____________________________________

_real@4010666666666666 DQ 04010666666666666r _real@40091eb851eb851f DQ 040091eb851eb851fr

; 4.1 ; 3.14

197MSDN: Parameter Passing (В целях сбора с т а т и с т и к и )

если вы дочитали до э т ого м е с т а ,

пож алуйста,

нажмите з д е с ь . Спасибо!

429

a$ = 8 b$ = 16 f PROC movsdx movsdx movsdx divsd movsdx mulsd addsd ret f ENDP

QWORD QWORD xmm0, xmm0, xmm1, xmm1, xmm0, 0

PTR [rsp+16], xmm1 PTR [rsp+8], xmm0 QWORD PTR a$[rsp] QWORD PTR real@40091eb851eb851f QWORD PTR b$[rsp] QWORD PTR __real@4010666666666666 xmm1

Чуть более избыточно. Входные аргум ен ты сохраняются в «shadow space» ( 1.10.2 (стр. 1 0 0 )), при­ чем, только м ладш ие половины регистров, т.е. только 64-битны е значения типа double. Результат работы компилятора GCC точно т а ко й ж е.

x86 Скомпилируем этот пример т а к ж е и под x86. MSVC 20 1 2 д а ж е ген ер и руя под x86, использует SSE2инструкции: Л и с т и н г 1.394: Н еоптимизирую щ ийM SV C 20 1 2 x 86

tv70 = -8 a$ = 8 b$ = 16 _f PROC push mov sub movsd divsd movsd mulsd addsd movsd f ld mov pop ret _f ENDP

; size = 8 ; size = 8 ; size = 8 ebp ebp, esp esp, 8 xmm0, QWORD PTR a$[ebp] xmm0, QWORD PTR real@40091eb851eb851f xmm1, QWORD PTR b$[ebp] xmm1, QWORD PTR real@4010666666666666 xmm0, xmm1 QWORD PTR tv70[ebp], xmm0 QWORD PTR tv70[ebp] esp, ebp ebp 0

Л и с ти н г 1.395: Оптим изирую щ ийM SVC 20 1 2 x 86

tv67 = 8 a$ = 8 b$ = 16 _f PROC movsd divsd movsd mulsd addsd movsd fld ret f ENDP

; size = 8 ; size = 8 ; size = 8 xmm1, xmm1, xmm0, xmm0, xmm1, QWORD QWORD 0

QWORD PTR a$[esp-4] QWORD PTR real@40091eb851eb851f QWORD PTR b$[esp-4] QWORD PTR real@4010666666666666 xmm0 PTR tv67[esp-4], xmm1 PTR tv67[esp-4]

Код почти т а ко й ж е , правда есть пара отличий связанных с со глаш ени ям и о вызовах: 1) аргум ен ты передаю тся не в X M M -р еги стр ах, а через стек, к а к и п р е ж д е , в примерах с FPU ( 1.20 (стр. 2 1 9 )); 2) результат работы ф ункц и и возвращается через S T (0 ) — для этого он через с т е к (через локаль­ ную п еременную t v ) копируется из X M M -реги стр а в S T ( 0 ) .

(В целях сбора с т а т и с т и к и ) если вы доч ита ли до эт ог о места, п о ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

430

Попробуем соптимизированный пример в OllyDbg: CPU - main th read, m odule simple

J S ]_

F20F104C24 0' N0USD XMM1f Ql.iJ0RD PTR SS: [ ESF+4] FLOAT F20F5E0D Ш2! □ IUSD XMM1, QWORD F'TR DS: [13320C0] F20F104424 & NOUSD XММ0,Ql.iJОRD PTR SSsСESP+0С ] FLOAT F20F590E. D02i MULSD ХММ0,QWORD PTR DS:[13320D0] HDDSD XMM1,KMM0 F20F58C8 F20F114C24 & MOUSD QWORD PTR SS:[ESP+4],XMM1 DD4424 04 FLD QWORD PTR SS:CESP+4] RETM C3 IMT3 CC INTS CC INTS CC INTS CC INTS CC FLOAT F20F1005 ££2J MOUSD ХММИ,QWORD PTR DS:[13320C8] SUB ESP,10 S3EC 10 F20F114424 & MOUSD QWORD PTR SS:CESP+8]PXMM0 FLOAT F20F1005 В82i MOUSD ХМП0рQWORD PTR DS:[13320B8] F20F110424 MOUSD QWORD PTR SS:СESP],ХММѲ E8 flDFFFFFF CALL 01331000 FSTP QWORD PTR SS:CESP+8] DD5C24 Э8 HDD ESP,8 83C4 08 ASCII 68 еэзеззеі PUSH OFFSET 01333000 FF15 9020330 CALL DWORD PTR DS:[] 83C4 0C HDD ESP,0C XOR ЕнХ,ЕнХ 33C0 RETM C3 INTS CC INTS CC INTS CC INTS CC INTS CC В8 4D5H0000 MOU EAX,5A4D 66:3905 0000 :CMP WORD PTR DS:C select * from V$VERSION; И получаем:

BANNER

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production PL/SQL Release 11.2.0.1.0 - Production CORE 11.2.0.1.0 Production TNS for 32-bit Windows: Version 11.2.0.1.0 - Production NLSRTL Version 11.2.0.1.0 - Production Н а ч н е м . Г д е в с а м о м O r a c le R D B M S м ы м о ж е м н а й т и с т р о к у V$VERSION? Д л я w in 3 2 -в е р с и и , эта с т р о к а и м е е т с я в ф а й л е o r a c l e . e x e , э то л е г к о у в и д е т ь . Но м ы т а к ж е м о ж е м и с п о л ь з о в а т ь о б ъ е к т н ы е (.о) ф а й л ы о т в е р с и и O r a c le R D B M S д л я L in u x , п о т о м у ч т о в н и х с о х р а н я ю т с я и м е н а ф у н к ц и й и г л о б а л ь н ы х п е р е м е н н ы х , а в o r a c l e . e x e д л я W in 3 2 э т о г о нет. И т а к , с т р о к а V$VERSION и м е е т с я в ф а й л е k q f . o , в с а м о й г л а в н о й O r a c l e - б и б л и о т е к е l i b s e r v e r 1 1 . a . С с ы л к а на э т у т е к с т о в у ю с т р о к у и м е е т с я в т а б л и ц е k q f v i w , р а з м е щ е н н о й в э т о м ж е ф а й л е k q f . o : Л и с т и н г 8 .7 : k q f.o

rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata rodata

0800C4A0 kqfviw dd 0Bh ; DATA XREF 0800C4A0 ; kqfgbn+34 0800C4A4 dd offset _2__STRING_ 0800C4A8 dd 4 0800C4AC dd offset _2__STRING_ 0800C4B0 dd 3 0800C4B4 dd 0 0800C4B8 dd 195h 0800C4BC dd 4 0800C4C0 dd 0 0800C4C4 dd 0FFFFC1CBh 0800C4C8 dd 3 0800C4CC dd 0 0800C4D0 dd 0Ah 0800C4D4 dd offset _2__STRING_ 0800C4D8 dd 4 0800C4DC dd offset _2__STRING_ 0800C4E0 dd 3 0800C4E4 dd 0 0800C4E8 dd 4Eh 0800C4EC dd 3 0800C4F0 dd 0 0800C4F4 dd 0FFFFC003h 0800C4F8 dd 4 0800C4FC dd 0 0800C500 dd 5 0800C504 dd offset _2__STRING_ 0800C508 dd 4 0800C50C dd offset _2__STRING_ 0800C510 dd 3 0800C514 dd 0 0800C518 dd 269h 0800C51C dd 15h

"GV$WAITSTAT" "NULL"

"V$WAITSTAT" "NULL"

"GV$BH" "NULL"

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8 60

.rodata:0800C520 .rodata:0800C524 .rodata:0800C528 .rodata:0800C52C .rodata:0800C530 .rodata:0800C534 .rodata:0800C538 .rodata:0800C53C .rodata:0800C540 .rodata:0800C544 .rodata:0800C548 .rodata:0800C54C .rodata:0800C550 .rodata:0800C554 .rodata:0800C558 .rodata:0800C55C

dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd

0 0FFFFC1EDh 8 0 4 offset _2__STRING_10106_0 ; "V$BH" 4 offset _2__STRING_10103_0 ; "NULL" 3 0 0F5h 14h 0 0FFFFC1EEh 5 0

К с т а т и , н е р е д к о , п р и и з у ч е н и и в н у т р е н н о с т е й O r a c le R D BM S, п о я в л я е т с я в о п р о с , п о ч е м у и м е н а ф у н к ц и й и г л о б а л ь н ы х п е р е м е н н ы х т а к и е с т р а н н ы е . В е р о я т н о , д е л о в т о м , ч т о O r a c le R D B M S о ч е н ь с т а р ы й п р о д у к т с а м п о с е б е и п и с а л с я на Си е щ е в 1 9 8 0 - х . А в т е в р е м е н а с т а н д а р т Си г а р а н т и р о в а л п о д д е р ж к у и м е н п е р е м е н н ы х д л и н о й т о л ь к о д о ш е с т и с и м в о л о в в к л ю ч и т е л ь н о : « 6 s i g n i f i c a n t i n itia l c h a r a c t e r s in a n e x t e r n a l i d e n t i f i e r » 35 В е р о я т н о , т а б л и ц а k q f v i w с о д е р ж а щ а я в с е б е м н о г и е (а м о ж е т д а ж е и все ) v i e w с п р е ф и к с о м V $ , э то с л у ж е б н ы е v ie w (fix e d v ie w s ), п р и с у т с т в у ю щ и е в с е гд а . Б е гл о о ц е н и в ц и к л и ч н о с т ь д а н н ы х , мы л е г к о в и д и м , ч т о в к а ж д о м э л е м е н т е т а б л и ц ы k q f v i w 12 п о л е й 3 2 - б и т н ы х п о л е й . В ID A л е г к о с о ­ з д а т ь с т р у к т у р у и з 1 2 - и э л е м е н т о в и п р и м е н и т ь е ё к о в с е м э л е м е н т а м т а б л и ц ы . Д л я в е р с и и O r a c le R D B M S 1 1 .2 , з д е с ь 1 0 2 3 э л е м е н т а в т а б л и ц е , т о е с т ь , з д е с ь о п и с ы в а ю т с я 1 0 2 3 в с е х в о з м о ж н ы х fixed view. П о з ж е , м ы е щ е в е р н е м с я к э т о м у ч и с л у . К а к ви дн о , мы не о че н ь м н о го м о ж е м у з н а т ь чисел в э т и х полях. Самое первое число все гда равно д л и н е с т р о к и - н а з в а н и я v i e w ( б е з т е р м и н и р у ю щ е г о н о л я ). Э т о с п р а в е д л и в о д л я в с е х э л е м е н т о в . Но э т а и н ф о р м а ц и я н е о ч е н ь п о л е з н а . М ы т а к ж е з н а е м , ч т о и н ф о р м а ц и ю о б о в с е х f i x e d v i e w s м о ж н о п о л у ч и т ь и з fixed view п о д н а з в а н и е м V $ F IX E D _ V IE W _ D E F IN IT IO N ( к с т а т и , и н ф о р м а ц и я д л я э т о г о v i e w т а к ж е б е р е т с я и з т а б л и ц k q f v i w и k q f v i p ) . К с т а т и , т а м т о ж е 1 0 2 3 э л е м е н т а . С о в п а д е н и е ? Нет.

SQL> select * from V$FIXED_VIEW_DEFINITION where view_name='V$VERSION'; VIEW NAME VIEW DEFINITION

V$VERSION select BANNER from GV$VERSION where inst_id = USERENV('Instance') И т а к , V$VERSION э т о к а к б ы редь:

thunk view д л я д р у г о г о , с н а з в а н и е м GV$VERSION, к о т о р ы й , в с в о ю о ч е ­

SQL> select * from V$FIXED_VIEW_DEFINITION where view_name='GV$VERSION'; VIEW NAME VIEW DEFINITION

GV$VERSION select inst_id, banner from x$version Т а б л и ц ы с п р е ф и к с о м X $ в O r a c le R D B M S — э т о т а к ж е с л у ж е б н ы е т а б л и ц ы , о н и н е д о к у м е н т и р о в а ­ ны, не м о г у т и з м е н я т с я п о л ь з о в а т е л е м , и о б н о в л я ю т с я д и н а м и ч е с к и . Попробуем поискать т е кс т

35Draft ANSI C Standard (ANSI X3J11/88-090) (May 13, 1988) (yurichev.com)

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

861

select BANNER from GV$VERSION where in s t_ id = USERENV('Instance') ... в ф а й л е k q f . o и н а х о д и м с с ы л к у на н е г о в т а б л и ц е k q f v i p : Л и с т и н г 8 .8 : k q f.o

. rodata:080185A0 .rodata:080185A0 .rodata:080185A0 .rodata:080185A4 .rodata:080185A8 .rodata:080185AC

kqfvip dd o f fs e t _2__STRING_11126_0 ; DATA XREF: kqfgvcn+18 ; kqfgvt+F ; "select in st_ id ,d e c o d e (in d x ,1 ,'d a ta bloc" dd o f fs e t kqfv459_c_0 dd 0 dd 0

rodata:08019570 dd o f fs e t _2__STRING_11378_0 "select BANNER from GV$VERSION where i n " . . . rodata:08019574 dd o f fs e t kqfv133_c_0 rodata:08019578 dd 0 rodata:0801957C dd 0 rodata:08019580 dd o f fs e t _2__STRING_11379_0 "select in s t_ id , d e c o d e ( b ita n d (c fflg ,1 ),0 " ... rodata:08019584 dd o f fs e t kqfv403_c_0 rodata:08019588 dd 0 rodata:0801958C dd 0 rodata:08019590 dd o f fs e t _2__STRING_11380_0 "select STATUS , NAME, IS_REC Y_DEST rodata:08019594 dd o f fs e t kqfv199_c_0 Т а б л и ц а , по всей в и д и м о с т и , и м е е т 4 поля в к а ж д о м э л е м е н т е . К с т а т и , зд е сь т а к ж е 1023 э л е м е н та , у ж е зн а ко м о е нам число. В т о р о е п о л е у к а з ы в а е т на д р у г у ю т а б л и ц у , с о д е р ж а щ у ю п о л я э т о г о

fixed view.

Д л я V$VERSION, э т а т а б л и ц а т о л ь к о и з д в у х э л е м е н т о в , п е р в ы й э т о 6 и в т о р о й э т о с т р о к а BANNER ( ч и с л о 6 э т о д л и н а с т р о к и ) и д а л е е терминирующий э л е м е н т с о д е р ж а щ и й 0 и нулевую С и - с т р о к у : Л и с т и н г 8 .9 : k q f.o

4 7 5 91 0 8 0

. rodata:080BBAC4 kqfv133 c 0 dd 6 DATA XREF .rodata . rodata:080BBAC8 dd o ffs e t 2 STRING "BANNER" . rodata:080BBACC dd 0 . rodata:080BBAD0 o ffs e t _2__STRING_ iО _о

0 _ 71 0 5i

d d

О б ъ е д и н и в д а н н ы е и з т а б л и ц k q f v i w и k q f v i p , м ы п о л у ч и м SQ L- з а п р о с ы , к о т о р ы е и с п о л н я ю т с я , к о г д а п о л ь з о в а т е л ь х о ч е т п о л у ч и т ь и н ф о р м а ц и ю и з к а к о г о - л и б о fixed view . Н а п и ш е м п р о г р а м м у o r a c le t a b l e s 36, к о т о р а я с о б и р а е т в с ю э т у и н ф о р м а ц и ю и з о б ъ е к т н ы х ф а й л о в о т O r a c le R D B M S п о д L in u x . Д л я V$VERSION, м ы м о ж е м н а й т и с л е д у ю щ е е : Л и с т и н г 8 .1 0 : Р е з у л ь т а т р а б о т ы o r a c le t a b l e s

kqfviw_element.viewname: [V$VERSION] ?: 0x3 0x43 0x1 0 xffffc0 8 5 0x4 kqfvip_element.statement: [s e le c t BANNER from GV$VERSION where in s t_ id = USERENV('Instance')] kqfvip_element.params: [BANNER] И: Л и с т и н г 8 .1 1 : Р е з у л ь т а т р а б о т ы o r a c le t a b l e s

kqfviw_element.viewname: [GV$VERSION] ?: 0x3 0x26 0x2 0 x ffffc 1 9 2 0x1 kqfvip_element.statement: [s e le c t in s t_ id , banner from x$version] kqfvip_element.params: [INST_ID] [BANNER] 36yurichev.com

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

8 62

Fixed view GV$VERSION о т л и ч а е т с я о т V$VERSION т е м , ч т о с о д е р ж и т е щ е и п о л е о т р а ж а ю щ е е и д е н ­ т и ф и к а т о р instance. Но т а к и л и и н а ч е , м ы т е п е р ь у п и р а е м с я в т а б л и ц у X$VERSION. К а к и п р о ч и е Х $ - т а б л и ц ы , о н а не д о к у м е н т и р о в а н а , о д н а ко , мы м о ж е м о т т у д а ч то -то п р о ч и т а т ь :

SQL> select * from x$version; ADDR

INDX

INST_ID

BANNER

0DBAF574 0 1 Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

Э т а т а б л и ц а с о д е р ж и т д о п о л н и т е л ь н ы е п о л я в р о д е ADDR и INDX. Б е г л о л и с т а я с о д е р ж и м о е ф а й л а k q f . o в ID A м ы м о ж е м у в и д е т ь е щ е о д н у т а б л и ц у г д е е с т ь с с ы л к а на с т р о к у X$VERSION, э т о k q f t a b : Л и с т и н г 8 .1 2 : k q f.o

.rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata .rodata

0803CAC0 0803CAC4 0803CAC8 0803CACC 0803CAD0 0803CAD4 0803CAD8 0803CADC 0803CAE0 0803CAE4 0803CAE8 0803CAEC 0803CAF0 0803CAF4 0803CAF8 0803CAFC 0803CB00 0803CB04 0803CB08 0803CB0C 0803CB10 0803CB14 0803CB18 0803CB1C

dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd

9 ; element number 0x1f6 offset _2__STRING_13113_0 "X$VERSION" 4 offset _2__STRING_13114_0 "kqvt" 4 4 0 4 0Ch 0FFFFC075h 3 0 7 offset _2__STRING_13115_0 "X$KQFSZ" 5 offset _2__STRING_13116_0 "kqfsz" 1 38h 0 7 0 0FFFFC09Dh 2 0

З д е с ь о ч е н ь м н о г о с с ы л о к на н а з в а н и я Х $ - т а б л и ц , в е р о я т н о , на вс е т е ч т о и м е ю т с я в O r a c le R D BM S этой версии. Но м ы с н о в а у п и р а е м с я в т о ч т о н е и м е е м д о с т а т о ч н о и н ф о р м а ц и и . Не я с н о , ч т о о з н а ч а е т с т р о к а k q v t. В о о б щ е , п р е ф и к с kq м о ж е т о з н а ч а т ь v, м о ж е т б ы т ь ,

kernel и query.

version, а t — type?

С казать труд но. Т а б л и ц у с очень п о х о ж и м назва нием мы м о ж е м н ай ти в k q f.o : Л и с т и н г 8 .1 3 : k q f.o

.rodata:0808C360 kqvt_c_0 kqftap_param .rodata:0808C360 ; DATA XREF: .rodata:08042680 .rodata:0808C360 ; "ADDR" .rodata:0808C384 kqftap_param "INDX" .rodata:0808C3A8 kqftap_param "INST ID"

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8 63

.rodata:0808C3CC W ; "BANNER" .rodata:0808C3F0

kqftap_param / kqftap_param

О н а с о д е р ж и т и н ф о р м а ц и ю об и м е н а х п о л е й в т а б л и ц е X$VERSION. Е д и н с т в е н н а я с с ы л к а на э т у та б л и ц у имеется в таб лице k q fta p : Л и с т и н г 8 .1 4 : k q f.o

.rodata:08042680 4 element 0x1f6

kqftap_element ; /

И н т е р е с н о ч т о з д е с ь э т о т э л е м е н т п р о х о д и т т а к ж е п о д н о м е р о м 0 x 1 f 6 ( 5 0 2 - й ) , к а к и с с ы л к а на с т р о к у X$VERSION в т а б л и ц е k q f t a b . Вероятно, та б л и ц ы k q fta p и k q fta b д о п о л н я ю т д р у г д р уга , к а к и k q f v ip и k q fv iw . М ы т а к ж е в и д и м з д е с ь с с ы л к у на ф у н к ц и ю с н а з в а н и е м k q v r o w ( ) . А в о т э т о у ж е к о е - ч т о ! С д е л а е м т а к ч т о б ы н а ш а п р о г р а м м а o r a c le t a b l e s 37 м о г л а д а м п и т ь и э т и т а б л и ц ы . Д л я X$VERSION получается: Л и с т и н г 8 .1 5 : Р е з у л ь т а т р а б о т ы o r a c le t a b l e s

kqftab_element.name: [X$VERSION] ?: [kqvt] 0x4 0x4 0x4 0xc 0xffffc075 0x3 kqftap_param.name=[ADDR] ?: 0x917 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name=[INDX] ?: 0xb02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name=[INST_ID] ?: 0xb02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name=[BANNER] ?: 0x601 0x0 0x0 0x0 0x50 0x0 0x0 kqftap_element.fn1=kqvrow kqftap_element.fn2=NULL П ри п о м о щ и tr a c e r , м о ж н о л е г к о п р о в е р и т ь , ч то эта ф у н к ц и я в ы з ы в а е т с я 6 раз к р я д у (из ф у н к ц и и q e r f x F e t c h ( ) ) п р и п о л у ч е н и и с т р о к и з X$VERSION. З а п у с т и м t r a c e r в р е ж и м е c c (о н д о б а в и т к о м м е н т а р и й к к а ж д о й и с п о л н е н н о й и н с т р у к ц и и ) :

tracer -a:oracle.exe bpf=oracle.exe!_kqvrow,trace:cc kqvrow_ var_ 7C var_ 18 var_ 14 Dest var_ C var_ 8 var_ 4 arg_ 8 arg_ C arg_ 14 arg_ 18

proc near = = = = = = = = = = =

byte dword dword dword dword dword dword dword dword dword dword

ptr -7Ch ptr -18h ptr -14h ptr -10h ptr - 0Ch ptr -8 ptr -4 ptr 10h ptr 14h ptr 1Ch ptr 20h

; FUNCTION CHUNK AT .text1:056C11A0 SIZE 00000049 BYTES push mov sub mov mov mov mov cmp mov jz mov mov loc_2CE10F6:

ebp ebp, esp esp, 7Ch eax, [ebp+arg_14] ; [EBP+1Ch]=1 ecx, TlsIndex ; [69AEB08h]=0 edx, large fs:2Ch edx, [edx+ecx*4] ;; [EDX+ECX*4]=0xc98c938 eax, 2 ; EAX=1 eax, [ebp+arg 8] ; [EBP+10h]=0xcdfe554 loc_ 2CE1288 ecx, [eax] ; [EAX]=0..5 [ebp+var_4], edi ; EDI=0xc98c938

; CODE XREF: _kqvrow_+10A

37yurichev.com (В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8 64

; _kqvrow_+1A9 cmp ecx, 5 ; ECX=0..5 ja loc_56C11C7 mov edi, [ebp+arg_18] ; [EBP+20h]=0 mov [ebp+var_14], edx ; EDX=0xc98c938 mov [ebp+var_8], ebx ; EBX=0 mov ebx, eax ; EAX=0xcdfe554 mov [ebp+var_C], esi ; ESI=0xcdfe248 loc_2CE110D: ; CODE XREF: _kqvrow_+29E00E6 mov edx, ds:off_628B09C[ecx*4] [ECX*4+628B09Ch]=0x2ce1116, 0x2ce11ac, 0x2ce11db, 0x2ce11f6, 0x2ce1236, 0x2ce127a edx EDX=0x2ce1116, 0x2ce11ac, 0x2ce11db, 0x2ce11f6, 0x2ce1236, jmp 0x2ce127a loc_2CE1116: ; DATA XREF: .rdata:off_628B09C push offset aXKqvvsnBuffer ; "x$kqvvsn buffer" mov ecx, [ebp+arg_C] ; [EBP+14h]=0x8a172b4 xor edx, edx mov esi, [ebp+var_14] ; [EBP-14h]=0xc98c938 push edx EDX=0 push EDX=0 edx push 50h push ecx ; ECX=0x8a172b4 push dword ptr [esi+10494h] ; [ESI+10494h]=0xc98cd58 call _kghalf ; tracing nested maximum level (1) reached, skipping this CALL mov esi, ds:__imp__ vsnnum ; [59771A8h]=0x61bc49e0 mov [ebp+Dest], eax ; EAX=0xce2ffb0 mov [ebx+8], eax ; EAX=0xce2ffb0 mov [ebx+4], eax ; EAX=0xce2ffb0 mov edi, [esi] ; [ESI]=0xb200100 Production" mov esi, ds:_ imp__vsnstr ; [597D6D4h] push esi Production" ; ESI=0x65852148, mov ebx, edi ; EDI=0xb200100 shr ebx, 18h ; EBX=0xb200100 mov ecx, edi ; EDI=0xb200100 shr ecx, 14h ; ECX=0xb200100 and ecx, 0Fh ; ECX=0xb2 mov edx, edi ; EDI=0xb200100 shr edx, 0Ch ; EDX=0xb200100 movzx edx, dl ; DL=0 mov eax, edi ; EDI=0xb200100 shr eax, 8 ; EAX=0xb200100 and eax, 0Fh ; EAX=0xb2001 and edi, 0FFh ; EDI=0xb200100 push edi ; EDI=0 mov edi, [ebp+arg_18] ; [EBP+20h]=0 push eax ; EAX=1 mov eax, ds: imp vsnban [597D6D8h]= 0x65852100, "Oracle Database 11g Ente push edx ; EDX=0 push ecx ; ECX=2 push ebx ; EBX=0xb mov ebx, [ebp+arg_8] ; [EBP+10h]=0xcdf push eax ; EAX=0x65852100, "Oracle Database 11g Enterprise Edition Release %d.%d.%d.%d.%d %s" mov eax, [ebp+Dest] ; [EBP-10h]=0xce2ffb0 push eax ; EAX=0xce2ffb0 call ds:__imp__sprintf ; op1=MSVCR80.dll!sprintf tracing nested maximum level (1) reached, skipping this CALL add esp, 38h mov dword ptr [ebx], 1 loc_2CE1192: ; CODE XREF: _kqvrow_+FB ; _kqvrow_+128 ... test edi, edi ; EDI=0 __VInfreq__kqvrow jnz mov esi, [ebp+var_C] ; [EBP-0Ch]=0xcdfe248 mov edi, [ebp+var_4] ; [EBP-4]=0xc98c938 mov eax, ebx ; EBX=0xcdfe554 mov ebx, [ebp+var_8] ; [EBP-8]=0

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8 65

lea eax, [eax+4] ; [EAX+4]=0xce2ffb0, "NLSRTL Version 11.2.0.1.0 - Production", "Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production", "PL/SQL Release 11.2.0.1.0 Production", "TNS for 32-bit Windows: Version 11.2.0.1.0 - Production" loc_2CE11A8: ; mov pop retn

CODE XREF: _kqvrow_+29E00F6 esp, ebp ebp ; EAX=0xcdfe558

loc_2CE11AC: ; DATA XREF: .rdata:0628B0A0 mov edx, [ebx+8] ; [EBX+8]=0xce2ffb0, "Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production" mov dword ptr [ebx], 2 mov [ebx+4], edx ; EDX=0xce2ffb0, "Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production" push edx ; EDX=0xce2ffb0, "Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production" call _kkxvsn ; tracing nested maximum level (1) reached, skipping this CALL pop ecx mov edx, [ebx+4] ; [EBX+4]=0xce2ffb0, "PL/SQL Release 11.2.0.1.0 - Production" movzx ecx, byte ptr [edx] ; [EDX]=0x50 test ecx, ecx ; ECX=0x50 jnz short loc_2CE1192 mov edx, [ebp+var_14] mov esi, [ebp+var_C] mov eax, ebx mov ebx, [ebp+var_8] mov ecx, [eax] jmp loc_2CE10F6 loc_2CE11DB: ; push push mov mov push call add mov jmp

DATA XREF:

edx, [ebx+8] [ebx+4], edx edx _lmxver

loc_2CE11F6: ; mov mov mov push call _npinli pop test jnz mov lea push push push call _nrtnsvrs add loc_2CE122B: ; mov jmp

.rdata:0628B0A4 0 50h ; [EBX+8]=0xce2ffb0, "PL/SQL Release 11.2.0.1.0 - Production" ; EDX=0xce2ffb0, "PL/SQL Release 11.2.0.1.0 - Production" ; EDX=0xce2ffb0, "PL/SQL Release 11.2.0.1.0 - Production" ; tracing nested maximum level (1) reached, skipping this CALL esp, 0Ch dword ptr [ebx], 3 short loc_2CE1192 DATA XREF: .rdata:0628B0A8 edx, [ebx+8] ; [EBX+8]=0xce2ffb0 [ebp+var_18], 50h [ebx+4], edx ; EDX=0xce2ffb0 0 ; tracing nested maximum level (1) reached, skipping this CALL ecx eax, eax ; EAX=0 loc_56C11DA ecx, [ebp+var_14] ; [EBP-14h]=0xc98c938 edx, [ebp+var_18] ; [EBP-18h]=0x50 edx ; EDX=0xd76c93c dword ptr [ebx+8] ; [EBX+8]=0xce2ffb0 dword ptr [ecx+13278h] ; [ECX+13278h]=0xacce190 ; tracing nested maximum level (1) reached, skipping this CALL esp, 0Ch CODE XREF: _kqvrow_+29E0118 dword ptr [ebx], 4 loc_2CE1192

loc_2CE1236: ; DATA XREF: .rdata:0628B0AC lea edx, [ebp+var_7C] ; [EBP-7Ch]=1 push edx ; EDX=0xd76c8d8 push 0 mov esi, [ebx+8] ; [EBX+8]=0xce2ffb0, "TNS for 32-bit Windows: Version 11.2.0.1.0 Production" mov [ebx+4], esi ; ESI=0xce2ffb0, "TNS for 32-bit Windows: Version 11.2.0.1.0 Production" mov ecx, 50h mov [ebp+var_18], ecx ; ECX=0x50

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

866

push push Production" c a ll add mov mov te s t jnz mov mov mov mov mov jmp

ecx esi

; ECX=0x50 ; ESI=0xce2ffb0, "TNS f o r 3 2 -b it Windows: Version 11.2.0.1.0 -

_lxvers ; tra cin g nested maximum le v e l (1) reached, skipping t h is CALL esp, 10h edx, [ebp+var_18] ; [EBP-18h]=0x50 dword p t r [ebx], 5 edx, edx ; EDX=0x50 loc_2CE1192 edx, [ebp+var_14] e si, [ebp+var_C] eax, ebx ebx, [ebp+var_8] ecx, 5 lo c 2CE10F6

lo c 2CE127A: ; DATA XREF: .rdata:0628B0B0 edx, [ebp+var_14] ; [EBP-14h]=0xc98c938 mov [EBP-0Ch]=0xcdfe248 esi, [ebp+var_C] mov [EBP-4]=0xc98c938 edi, [ebp+var_4] mov ; EBX=0xcdfe554 eax, ebx mov [EBP-8]=0 ebx, [ebp+var_8] mov loc_2CE1288: ; CODE XREF: _kqvrow_+1F mov eax, [eax+8] ; [EAX+8]=0xce2ffb0, "NLSRTL Version 11.2.0.1.0 - Production" te s t eax, eax ; EAX=0xce2ffb0, "NLSRTL Version 11.2.0.1.0 - Production" jz short loc_2CE12A7 push o ffs e t aXKqvvsnBuffer ; "x$kqvvsn buffer" push eax ; EAX=0xce2ffb0, "NLSRTL Version 11.2.0.1.0 - Production" mov eax, [ebp+arg_C] ; [EBP+14h]=0x8a172b4 push eax ; EAX=0x8a172b4 push dword p t r [edx+10494h] ; [EDX+10494h]=0xc98cd58 c a ll _ k g h frf ; tra cin g nested maximum le v e l (1) reached, skipping t h is CALL add esp, 10h loc_2CE12A7: ; CODE XREF: _kqvrow_+1C1 xor eax, eax mov esp, ebp pop ebp retn ; EAX=0 _kqvrow_ endp Т а к м о ж н о л е г к о у в и д е т ь , ч то н о м е р с т р о к и т а б л и ц ы з а д а е т с я и звн е . Сама ф у н к ц и я в о з в р а щ а е т с т р о ку , ф ор м и р уя её та к: С трока 1 С трока С трока С трока С трока

2 3 4 5

И с п о л ь з у е т г л о б а л ь н ы е п е р е м е н н ы е v s n s t r , vsn nu m , v s n b a n . Вызывает s p r i n t f ( ) . Вызывает k k x v s n () . Вызывает lm x v e r ( ) . Вызывает n p i n l i ( ) , n r t n s v r s ( ) . Вызывает l x v e r s ( ) .

Т а к вы зы ваю тся со отве тств ую щ и е ф у н кц и и для опред ел ения номеров версий отд ел ьн ы х модулей.

8.9.2. Таблица X$KSMLRU в Oracle RDBMS В з а м е т к е Diagnosing and Resolving Error ORA-04031 on the Shared Pool or Other Memory Pools [Video] [ID 146599.1] у п о м и н а е т с я н е к а я с л у ж е б н а я т а б л и ц а : T h e r e is a f i x e d t a b l e c a lle d X $ K S M L R U t h a t t r a c k s a ll o c a t i o n s in t h e s h a r e d p o o l t h a t c a u s e o t h e r o b j e c t s in t h e s h a r e d p o o l t o be a g e d o u t. T h is f i x e d t a b l e c a n be u s e d t o i d e n t i f y w h a t is c a u s i n g t h e la r g e a ll o c a t i o n . If m a n y o b j e c t s a r e b e in g p e r i o d i c a l l y f l u s h e d f r o m t h e s h a r e d p o o l t h e n t h i s w ill c a u s e r e s p o n s e t i m e p r o b l e m s a n d w ill l i k e l y c a u s e l i b r a r y c a c h e la t c h c o n t e n t i o n p r o b l e m s w h e n t h e o b j e c t s a re r e l o a d e d i n t o t h e s h a r e d p o o l.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

8 67 O n e u n u s u a l t h i n g a b o u t t h e X $ K S M L R U f i x e d t a b l e is t h a t t h e c o n t e n t s o f t h e f i x e d t a b l e a re e r a s e d w h e n e v e r s o m e o n e s e le c t s f r o m t h e f i x e d t a b l e . T h is is d o n e s in c e t h e f i x e d t a b l e s t o r e s o n l y t h e l a r g e s t a ll o c a t i o n s t h a t h a v e o c c u r r e d . T h e v a lu e s a r e r e s e t a f t e r b e in g s e le c t e d so t h a t s u b s e q u e n t la r g e a ll o c a t i o n s c a n b e n o t e d e v e n i f t h e y w e r e n o t q u i t e as la r g e as o t h e r s t h a t o c c u r r e d p r e v io u s ly . B e c a u s e o f t h i s r e s e t t i n g , t h e o u t p u t o f s e le c t i n g f r o m t h i s t a b l e s h o u ld be c a r e f u l l y k e p t s in c e it c a n n o t b e r e t r i e v e d b a c k a f t e r t h e q u e r y is is s u e d . О д н а к о , к а к м о ж н о л е г к о у б е д и т ь с я , эта с и с т е м н а я т а б л и ц а о ч и щ а е т с я в с я к и й раз, к о г д а к т о - т о д е л а е т з а п р о с к н е й . С м о ж е м л и м ы н а й т и п р и ч и н у , п о ч е м у э т о п р о и с х о д и т ? Е сл и в е р н у т ь с я к у ж е р а с с м о т р е н н ы м т а б л и ц а м k q f t a b и k q f t a p п о л у ч е н н ы х п р и п о м о щ и o r a c le t a b l e s 38, с о д е р ж а щ и м и н ф о р м а ц и ю о X $ -та б л и ц а х, мы у з н а е м что для т о го ч тоб ы п о д го т о в и т ь с т р о к и э то й т а б л и ц ы , вы ­ зывается ф ун кц и я k s m lr s ( ) : Л и с т и н г 8 .1 6 : Р е з у л ь т а т р а б о т ы o r a c le t a b l e s

kqftab element.name: [X$KSMLRU] ?: [ksmlr] 0x4 0x64 0x11 0xc 0xffffc0bb 0x5 kqftap_param.name= ADDR] ?: 0x917 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name= INDX] ?: 0xb02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name= INST_ID] ?: 0xb02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name= KSMLRIDX] ?: 0xb02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name= KSMLRDUR] ?: 0xb02 0x0 0x0 0x0 0x4 0x4 0x0 kqftap_param.name= KSMLRSHRPOOL] ?: 0xb02 0x0 0x0 0x0 0x4 0x8 0x0 kqftap_param.name=[KSMLRCOM] 0x501 0x0 0x0 0x0 0x14 0xc 0x0 kqftap_param.name=[KSMLRSIZ] 0x2 0x0 0x0 0x0 0x4 0x20 0x0 kqftap_param.name=[KSMLRNUM] 0x2 0x0 0x0 0x0 0x4 0x24 0x0 kqftap_param.name=[KSMLRHON] 0x501 0x0 0x0 0x0 0x20 0x28 0x0 kqftap_param.name=[KSMLROHV] 0xb02 0x0 0x0 0x0 0x4 0x48 0x0 kqftap_param.name=[KSMLRSES] 0x17 0x0 0x0 0x0 0x4 0x4c 0x0 kqftap_param.name=[KSMLRADU] 0x2 0x0 0x0 0x0 0x4 0x50 0x0 kqftap_param.name=[KSMLRNID] 0x2 0x0 0x0 0x0 0x4 0x54 0x0 kqftap_param.name=[KSMLRNSD] 0x2 0x0 0x0 0x0 0x4 0x58 0x0 kqftap_param.name=[KSMLRNCD] 0x2 0x0 0x0 0x0 0x4 0x5c 0x0 kqftap_param.name=[KSMLRNED] 0x2 0x0 0x0 0x0 0x4 0x60 0x0 kqftap_element.fn1=ksmlrs kqftap_element.fn2=NULL Д е й с т в и т е л ь н о , п р и п о м о щ и tr a c e r л е г к о у б е д и т ь с я , ч т о эта ф у н к ц и я в ы з ы в а е т с я к а ж д ы й раз, к о ­ г д а м ы о б р а щ а е м с я к т а б л и ц е X$KSMLRU. З д е с ь е с т ь с с ы л к и на ф у н к ц и и k s m s p l u _ s p ( ) и k s m s p l u _ j p ( ) , к а ж д а я и з к о т о р ы х в и т о г е в ы з ы в а е т k s m s p lu ( ) . В ко н ц е ф у н к ц и и k s m s p lu () мы вид им вы зов m e m s e t(): Л и с т и н г 8 .1 7 : k s m .o

00434C50 00434C50 00434C53 00434C55 00434C57 00434C5A 00434C5C 00434C5F 00434C62 00434C68 00434C6B 00434C6E 00434C71 00434C74 00434C7A 00434C7F 00434C81 00434C82 00434C87 00434C8A

0 5 C 4 3 4 _c 0 1

.text .text .text .text .text .text .text .text .text .text .text .text .text .text .text .text .text .text .text .text

mov mov mov mov mov add mov jnz mov mov mov mov lea push push push call add mov

; DATA XREF: .rdata:off_5E50EA8 edx, [ebp-4] [eax], esi esi, [edi] [eax+4], esi [edi], eax edx, 1 [ebp-4], edx loc_434B7D ecx, [ebp+14h] ebx, [ebp-10h] esi, [ebp-0Ch] edi, [ebp-8] eax, [ecx+8Ch] 370h ; Size 0 ; Val eax ; Dst intel fast memset esp, 0Ch esp, ebp

38yurichev.com (В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8 68

.text:00434C8C pop .text:00434C8D retn .text:00434C8D _ksmsplu endp

ebp

Т а к и е к о н с т р у к ц и и (m em set ( b l o c k , 0 , s i z e ) ) о ч е н ь ч а с т о и с п о л ь з у ю т с я д л я п р о с т о г о о б н у л е н и я б л о ка п а м я ти . Мы м о ж е м п о п р о б о в а т ь р и с к н у т ь , з а б л о к и р о в а в вы зов m em se t() и п о с м о тр е ть , что будет? З а п у с к а е м t r a c e r с о с л е д у ю щ е й о п ц и е й : п о с т а в и т ь т о ч к у о с т а н о в а на 0 x 4 3 4 C 7 A ( т а м , г д е н а ч и н а ­ е тся п е ре д ач а п а р а м е тр о в для ф у н к ц и и m e m se t()) т а к , чтобы tra c e r в этом м есте у с т а н о в и л у к а з а ­ т е л ь и н с т р у к ц и й п р о ц е с с о р а (E IP ) на м е с т о , г д е у ж е п р о и з о ш л а о ч и с т к а п е р е д а н н ы х п а р а м е т р о в в m e m s e t ( ) ( п о а д р е с у 0 x43 4C 8A ): М о ж н о с к а з а т ь , п р и п о м о щ и э т о г о , м ы с и м у л и р у е м б е з у с л о в н ы й п е р е х о д с а д р е с а 0 x 4 3 4 C 7 A на 0 x43 4C 8A .

tracer -a:oracle.exe bpx=oracle.exe!0x00434C7A,set(eip,0x00434C8A) ( В а ж н о : все э т и а д р е с а с п р а в е д л и в ы т о л ь к о д л я w i n 3 2 - в е р с и и O r a c le R D B M S 1 1 .2 ) Д е й с т в и т е л ь н о , п о с л е э т о г о м ы м о ж е м о б р а щ а т ь с я к т а б л и ц е X$KSMLRU с к о л ь к о у г о д н о , и о н а у ж е не о ч и щ а е т с я ! На в с я к и й с л у ч а й , н е п о в т о р я й т е э т о г о на с в о и х p r o d u c t i o n - с е р в е р а х . В п р о ч е м , э т о н е о б я з а т е л ь н о п о л е з н о е и л и ж е л а е м о е п о в е д е н и е с и с т е м ы , н о к а к э к с п е р и м е н т по п о и с ку н у ж н о г о код а, нам это под ош ло!

8.9.3. Таблица V$TIMER в Oracle RDBMS V$TIM ER э т о е щ е о д и н с л у ж е б н ы й

fixed view, о т р а ж а ю щ и й к а к о е - т о ч а с т о м е н я ю щ е е с я з н а ч е н и е :

V $ T IM E R d is p l a y s t h e e la p s e d t i m e in h u n d r e d t h s o f a s e c o n d . T i m e is m e a s u r e d s in c e t h e b e g in n i n g o f t h e e p o c h , w h i c h is o p e r a t i n g s y s t e m s p e c ific , a n d w r a p s a r o u n d t o 0 a g a in w h e n e v e r th e v a lu e o v e rflo w s fo u r b y te s (ro u g h ly 4 9 7 d a ys). (И з д о к у м е н т а ц и и O r a c le R D B M S 39) И н т е р е с н о ч т о п е р и о д ы р а з н ы е в O r a c le д л я W i n 3 2 и д л я L in u x . С м о ж е м л и м ы н а й т и ф у н к ц и ю , о т в е ч а ю щ у ю за г е н е р и р о в а н и е э т о г о з н а ч е н и я ? К а к в и д н о , э т а и н ф о р м а ц и я , в и т о г е , б е р е т с я и з с и с т е м н о й т а б л и ц ы X$KSUTM.

SQL> select * from V$FIXED_VIEW_DEFINITION where view_name='V$TIMER'; VIEW NAME VIEW DEFINITION

V$TIMER select HSECS from GV$TIMER where inst_id = USERENV('Instance') SQL> select * from V$FIXED_VIEW_DEFINITION where view_name='GV$TIMER'; VIEW NAME VIEW DEFINITION

GV$TIMER select inst_id,ksutmtim from x$ksutm З д е с ь м ы у п и р а е м с я в н е б о л ь ш у ю п р о б л е м у , в т а б л и ц а х k q f t a b / k q f t a p н е т у к а з а т е л е й на ф у н к ц и ю , ко то р а я бы ге н е р и р о ва л а значение:

39http://go.yurichev.com/17088

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

869 Л и с т и н г 8 .1 8 : Р е з у л ь т а т р а б о т ы o r a c le t a b l e s

kqftab_element.name: [X$KSUTM] ?: [ksutm] 0x1 0x4 0x4 0x0 0 x ffffc 0 9 b 0x3 kqftap_param.name=[ADDR] ?: 0x10917 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name=[INDX] ?: 0x20b02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name=[INST_ID] ?: 0xb02 0x0 0x0 0x0 0x4 0x0 0x0 kqftap_param.name=[KSUTMTIM] ?: 0x1302 0x0 0x0 0x0 0x4 0x0 0x1e kqftap_element.fn1=NULL kqftap_element.fn2=NULL П о п р о б у е м в т а к о м с л у ч а е п р о с т о п о и с к а т ь с т р о к у KSUTMTIM, и н а х о д и м с с ы л к у на н е е в т а к о й ф ункции:

_ tm u s k _N R D _d f q

c proc near

8 r tp dr o w d = = dword p t r p tr

dr o w d =

arg_0 arg_8 arg_C

; DATA XREF: .rodata:0805B4E8

push mov push push push push push c a ll add mov pop retn

10h 14h

ebp ebp, esp [ebp+arg_C] o ffs e t ksugtm o ffs e t _2__STRING_1263_0 ; "KSUTMTIM" [ebp+arg_8] [ebp+arg_0] kqfd_cfui_drain esp, 14h esp, ebp ebp

p d n e c _ tm u s k _N R D _d f q Сама ф у н к ц и я k q fd _ D R N _ k s u tm _ c () у п о м и н а е т с я в т а б л и ц е k q fd _ ta b _ r e g is tr y _ 0 вот так:

o ffs e t 2 STRING 62 0 ; "X$KSUTM" o ffs e t kqfd OPN ksutm c o ffs e t kqfd ta b l fetch 0 0 o ffs e t c 1 m t u s k 1 N R D 1 d f q k

dd dd dd dd dd dd

У п о м и н а е т с я т а к ж е н е к а я ф у н к ц и я k s u g t m ( ) . П о с м о т р и м , ч т о т а м (в L in u x x 8 6 ) : Л и с т и н г 8 .1 9 : k s u .o

proc near

var_1C arg_4

= byte p t r -1Ch p t r 0Ch

ksugtm

dr o w d =

ksugtm

push mov sub lea push c a ll pop mov mov mov mov pop retn endp

ebp ebp, esp esp, 1Ch eax, [ebp+var_1C] eax slgcs ecx edx, [ebp+arg_4] [edx], eax eax, 4 esp, ebp ebp

В w in 3 2 -в е р с и и т о ж е сам ое. И ско м ая ли эта ф у н кц и я ? П о п р о б уе м уз н а т ь :

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

8 70

tracer -a:oracle.exe bpf=oracle.exe!_ksugtm,args:2,dump_args:0x4 П р о б у е м н е с к о л ь к о р а з:

SQL> select * from V$TIMER; HSECS 27294929 SQL> select * from V$TIMER; HSECS 27295006 SQL> select * from V$TIMER; HSECS 27295167 Л и с т и н г 8 .2 0 : в ы в о д t r a c e r

TID=2428|(0) 4 +0xfad Argument 2/2 0D76C5F0: 38 TID=2428|(0) Argument 2/2 00000000: D1 TID=2428|(0) 4 +0xfad Argument 2/2 0D76C5F0: 38 TID=2428|(0) Argument 2/2 00000000: 1E TID=2428|(0) 4 +0xfad Argument 2/2 0D76C5F0: 38 TID=2428|(0) Argument 2/2 00000000: BF

oracle.exe!_ksugtm (0x0, 0xd76c5f0) (0x56bb6d5)) C9 oracle.exe!_ksugtm () -> 0x4 (0x4) difference 7C A0 01 oracle.exe!_ksugtm (0x0, 0xd76c5f0) (0x56bb6d5)) C9 oracle.exe!_ksugtm () -> 0x4 (0x4) difference 7D A0 01 oracle.exe!_ksugtm (0x0, 0xd76c5f0) (0x56bb6d5)) C9 oracle.exe!_ksugtm () -> 0x4 (0x4) difference 7D A0 01

(called from oracle.exe!__VInfreq__qerfxFetch/

"8.

"

" |

"

(called from oracle.exe!__VInfreq__qerfxFetch/

"8.

"

" }

"

(called from oracle.exe!__VInfreq__qerfxFetch/

"8.

"

Д е й с т в и т е л ь н о — з н а ч е н и е т о , ч т о м ы в и д и м в S Q L*P lu s, и о н о в о з в р а щ а е т с я ч е р е з в т о р о й а р г у ­ мент. П о с м о т р и м , ч т о в ф у н к ц и и s l g c s ( ) (L in u x x 8 6 ):

slgcs

proc near

var_4 arg_0

= dword ptr -4 = dword ptr 8 push mov push mov mov call pop nop mov mov call push push

ebp ebp, esp esi [ebp+var_4], ebx eax, [ebp+arg 0] $+5 ebx ; PIC mode ebx, offset _GLOBAL_OFFSET_ dword ptr [eax], 0 sltrgatime64 ; PIC mode 0 0Ah

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

871

slgcs

push push call mov add mov pop retn endp

edx eax __udivdi3 ; PIC mode ebx, [ebp+var_4] esp, 10h esp, ebp ebp

( э т о п р о с т о в ы з о в s l t r g a t i m e 6 4 ( ) и д е л е н и е е г о р е з у л ь т а т а на 10 (3 . 1 0 ( с т р . 5 0 0 ))) И в w in 3 2 -в е р с и и :

_slgcs

proc near

_slgcs

db nop push mov mov mov call mov mov mul shr mov mov pop retn endp

; CODE XREF: _dbgefgHtElResetCount+15 ; _dbgerRunActions+1528

66h ebp ebp, esp eax, [ebp+8] dword ptr [eax], 0 d s :_ imp__GetTickCount@0 ; GetTickCount() edx, eax eax, 0CCCCCCCDh edx edx, 3 eax, edx esp, ebp ebp

Э т о п р о с т о р е з у л ь т а т G e t T i c k C o u n t ( ) 40 п о д е л е н н ы й на 10 (3 . 1 0 ( с т р . 5 0 0 )). В у а л я ! В о т п о ч е м у в w i n 3 2 - в е р с и и и в е р с и и L in u x x 8 6 р а з н ы е р е з у л ь т а т ы , п о т о м у ч т о о н и п о л у ч а ­ ю тся разны м и си сте м н ы м и ф у н кц и я м и О С .

Drain п о - а н г л и й с к и дренаж, отток, водосток. Т а к и м о б р а з о м , в о з м о ж н о и м е е т с я в в и д у подключе­ ние о п р е д е л е н н о г о с т о л б ц а с и с т е м н о й т а б л и ц е к ф у н к ц и и . Д о б а в и м п о д д е р ж к у т а б л и ц ы k q f d _ t a b _ r e g i s t r y _ 0 в o r a c le t a b l e s 41, т е п е р ь м ы м о ж е м в и д е т ь , п р и п о м о щ и к а к и х ф у н к ц и й , с т о л б ц ы в с и с т е м н ы х т а б л и ц а х подключаются к з н а ч е н и я м , н а п р и м е р :

[X$KSUTM] [kqfd_OPN_ksutm_c] [kqfd_tabl_fetch] [NULL] [NULL] [kqfd_DRN_ksutm_c] [X$KSUSGIF] [kqfd_OPN_ksusg_c] [kqfd_tabl_fetch] [NULL] [NULL] [kqfd_DRN_ksusg_c]

OPN, в о з м о ж н о , open, а DRN, в е р о я т н о , о з н а ч а е т drain.

8.10. Вручную написанный на ассемблере код 8.10.1. Тестовый файл EICAR Э т о т .C O M - ф а й л п р е д н а з н а ч е н д л я т е с т и р о в а н и я а н т и в и р у с о в , е г о м о ж н о з а п у с т и т ь в M S -D O S и о н в ы в е д е т т а к у ю с т р о к у : « E IC A R -S T A N D A R D -A N T IV IR U S -T E S T -F IL E !» 42. О н п р и м е ч а т е л е н т е м , ч т о о н п о л н о с т ь ю с о с т о и т т о л ь к о и з п е ч а т н ы х A S C II- с и м в о л о в , с л е д о в а т е л ь ­ но, е г о м о ж н о н а б р а т ь в л ю б о м т е к с т о в о м р е д а к т о р е :

X5O!P%@AP[4\PZX54(P~)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* П опробуем е го разобрать: 40 m s d n

41yurichev.com 42wikipedia

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

872

E F F F =

; изначальное состояние: SP=0FFFEh , SS:[SP]=0 ax 0100 58 pop ; AX==0, SP=0 0101 35 4F 21 xor ax, 214Fh ; AX = 214Fh and SP = 0 0104 50 push ax ; AX = 214Fh, SP and SS:[FFFE] = 214Fh 0105 and ax, 4140h ; AX = 140h, SP = FFFEh and SS:[FFFE] = 214Fh 0108 50 push ax ; AX = 140h, SP = FFFCh, SS:[FFFC] = 140h and SS:[FFFE] = 214Fh 0109 5B pop bx ; AX = 140h, BX = 140h, SP = FFFEh and SS:[FFFE] = 214Fh xor al, 5Ch 010A ; AX = 11Ch, BX = 140h, SP = FFFEh and SS:[FFFE] = 214Fh 010C 50 push ax 010D 5A pop dx ; AX = 11Ch, BX = 140h, DX = 11Ch, SP = FFFEh and SS:[FFFE] = 214Fh 010E 58 pop ax ; AX = 214Fh, BX = 140h, DX = 11Ch and SP = 0 010F 35 34 28 xor ax, 2834h ; AX = 97Bh, BX = 140h, DX = 11Ch and SP = 0 0112 50 push ax 0113 5E pop si ; AX = 97Bh, BX = 140h, DX = 11Ch, SI = 97Bh and SP = 0 sub [bx], si 0114 29 37 0116 43 inc bx 0117 43 inc bx 0118 29 37 sub [bx], si 011A 7D 24 short near ptr word_10140 jge 011C 45 49 43 db 'EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$' 0140 48 2B word _10140 dw 2B48h ; CD 21 (INT 21) будет здесь 0142 48 2A dw 2A48h ; CD 20 (INT 20) будет здесь db 0Dh 0144 0D 0145 0A db 0Ah 1 4 0 4 5 2

C 5 4 3

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

9 0

B4 BA 1C 01 CD 21 CD 20

MOV MOV INT INT

AH, 9 DX, 11Ch 21h 20h

IN T 2 1h с ф у н к ц и е й 9 ( п е р е д а н н о й в AH) п р о с т о в ы в о д и т с т р о к у , а д р е с к о т о р о й п е р е д а н в DS:DX. К с т а т и , с т р о к а д о л ж н а б ы т ь з а в е р ш е н а с и м в о л о м '$ '. Н а д о п о л а г а т ь , э т о н а с л е д и е CP/M и э т а ф у н к ­ ц и я в D OS о с т а л а с ь д л я с о в м е с т и м о с т и . IN T 20h в о з в р а щ а е т у п р а в л е н и е в DOS. Но, к а к в и д н о , д а л е к о н е вс е о п к о д ы э т и х и н с т р у к ц и й п е ч а т н ы е . Т а к ч т о о с н о в н а я ч а с т ь EICARф ай ла это: • п о д г о т о в к а н у ж н ы х з н а ч е н и й р е г и с т р о в (AH и DX); • п о д г о т о в к а в п а м я т и о п к о д о в д л я IN T 2 1 и IN T 20; • и с п о л н е н и е INT 2 1 и IN T 20. Кстати, подобная т е х н и ка ш и р око и спол ьзуется для созд ания ш еллкод ов, гд е н у ж н о созд ать x86код, ко т о р ы й б у д е т н у ж н о п е р е д а т ь в виде т е к с т о в о й с т р о ки . З д е с ь т а к ж е с п и с о к в с е х х 8 6 - и н с т р у к ц и й с п е ч а т а е м ы м и о п к о д а м и : .1 .6 ( с т р . 9 9 8 ).

8.11. Демо Д е м о (или д е м о м е й к и н г ) б ы л и в е л и к о л е п н ы м у п р а ж н е н и е м в м а т е м а т и к е , п р о гр а м м и р о в а н и и к о м ­ п ь ю т е р н о й г р а ф и к и и о ч е н ь п л о т н о м у п р о г р а м м и р о в а н и ю на а с с е м б л е р е в р у ч н у ю . (В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

873

8.11.1. 10 PRINT CHR$(205.5 +RND(1)); : GOTO 10 Все п р и м е р ы з д е с ь д л я .C O M - ф а й л о в п о д M S-DO S. В [ N ic k M o n t f o r t e t al, 10 PRINT CHR$(205.5+RND(1)); : GOTO 10, (T h e M IT P r e s s : 2 0 1 2 ) ] 43 м о ж н о п р о ­ ч и т а т ь об о д н о м и з п р о с т е й ш и х г е н е р а т о р о в с л у ч а й н ы х л а б и р и н т о в . О н п р о с т о б е с к о н е ч н о и с л у ­ чайно п е ча та е т сим вол слэш а или о б р атны й слэш а, вы давая в и то ге что-то вроде:

/\/\/\\///\\/\\\\\/\\////\\/\\\/\/\\/\/\/\\/\\/\//\\ \/\/\/\/\/\\\/\/\/\/\/\\/\\/\/\/\/\/\\/\\\\///\/\\\\

\\\\/\\\\\\\\\\/\\/\\/\\/\/\\/\/\/\\/\///\/\///\\\/\ /Л\\//Л\Л\Л\\//Л\Л/ЛЛЛЛЛЛЛ\Л//ЛЛ\Л/ \\/\\\\/\\\\/\\\\\/\\/\\\\\\\\\\\/\\\/\\\\\\\/\/\\// /\\\/\\/\/\\/\/\\\\/\\\\/\\//\/\//\\/\\\/\\/\\\\/\// \\/\\/\\/\/\\/\/\\/\/\/\\\/\\\/\\\\/\\/\\/\\\\\/\/\/ \/\/\/\\\/\\\\//\\\/\///\/\\//\\/\\\\/\\\\\//\\/\/\/

/\/\\/\\\/\/\\\//\//\/\/\//\\///\//\/\/\//\/\//\/\/\ /л/\/л//\//\//\/л/\//\//\\/\/\\\/л\\\\/\/\/\/\\/ /\//\/\\/\\/\/\\/\/\\//\\//\/\\/\\\/\/\\/\/\\/\/\\\\ \\/\\/\\//\/\\\/\/\//\/\\\\//\//\\//\/\/\////\//\//\ /\/\//\//\//\///\//\/\/\//\//\/\/\\//\\//\//\//\/\/\ //\\\/\/\\/\/\/\//\//\/\\//\\//\/\\/\/\//\//\/\\//\\ \/\//\//\/\\//\\//\/\\/\/\//\//\/\\//\\//\/\\/\//\/\ \/\\\/\\\//\\/\/\\/\\\\/\/\/\\/\/\/\/\//\\\\\\\\//\\ \\\\\\\/\/\\\\\\\\\\/\/\\\\\\/\//\/\\\/\//\\\/\\/\/\ //\/\//\//\/\//\//\//\//\/\//\//\/\/\\/\\\\/\/\\//\/ /\/\/\/\\//\/\/\/\\//\/\/\//\/\//\/\\/\\\\/\/\\//\\\ /л/\\//\//\//\\/\/\/л//\/\/\/\/\/л/\/\\/\//\/\/л /\\//\\/\//\\\/\\/\\/\\/\\\/\\/\\/\\/\\\/\\//\/\\\\/ \\/\/\/ \/\\//\/\\\/\\/\/ \\/\//\///\/\\\\/\//\/\/\/\/ Здесь н е скол ько известны х реализаций для 1 6-битного x86.

Версия 4 2 - х б а й т о т T r i x t e r Л и с т и н г в з я т с е г о с а й т а 44, н о к о м м е н т а р и и — а в т о р а .

h b h b

00000000: B001 al,1 ; установить видеорежим 40x25 mov 00000002: CD10 int 010 00000004: 30FF xor ; установить видеостраницу для вызова int 00000006: B9D007 mov cx,007D0 ; вывод 2000 символов 00000009: 31C0 xor ax,ax 0000000B: 9C pushf ; сохранить флаги ; узнать случайное число из чипа таймера 0000000C: FA cli ; запретить прерывания 0000000D: E643 out 043,al ; записать 0 в порт 43h ; прочитать 16-битное значение из порта 40h 0000000F: E440 in al,040 00000011: 88C4 mov ah,al 00000013: E440 in al,040 00000015: 9D popf ; разрешить прерывания возвращая значение IF 00000016: 86C4 xchg ah,al ; здесь мы имеем псевдослучайное 16-битное значение 00000018: D1E8 shr ax,1 0000001A: D1E8 shr ax,1 ; в CF сейчас находится второй бит из значения 0000001C: B05C mov al,05C ;'' ; если CF=1, пропускаем следующую инструкцию 0000001E: 7202 000000022 jc ; если CF=0, загружаем в регистр AL другой символ 00000020: B02F mov al,02F ;'/' ; вывод символа 00000022: B40E mov ah,00E 00000024: CD10 int 010 00000026: E2E1 loop 000000009 ; цикл в 2000 раз 00000028: CD20 int 020 ; возврат в DOS

43Также доступно здесь: http://go.yurichev.com/17286 44http://go.yurichev.com/17305

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

8 74 П с е в д о с л у ч а й н о е ч и с л о на с а м о м д е л е э т о в р е м я , п р о ш е д ш е е с о с т а р т а с и с т е м ы , п о л у ч а е м о е из ч и п а т а й м е р а 8 2 5 3 , э т о з н а ч е н и е у в е л и ч и в а е т с я на е д и н и ц у 1 8 .2 р а з а в с е к у н д у . З а п и с ы в а я н о л ь в п о р т 4 3 h , м ы и м е е м в в и д у ч т о к о м а н д а э т о « в ы б р а т ь с ч е т ч и к 0», " c o u n t e r l a t c h " , " д в о и ч н ы й с ч е т ч и к " (а н е з н а ч е н и е B C D ). П р е р ы в а н и я с н о в а р а з р е ш а ю т с я п р и п о м о щ и и н с т р у к ц и и POPF, к о т о р а я т а к ж е в о з в р а щ а е т ф л а г IF . И н с т р у к ц и ю IN н е л ь з я и с п о л ь з о в а т ь с д р у г и м и р е г и с т р а м и к р о м е AL, п о э т о м у з д е с ь п е р е т а с о в к а .

Моя п о п ы т к а у к о р о т и т ь в е р с и ю T r ix t e r : 27 б а й т Мы м о ж е м с к а з а т ь , ч то мы и с п о л ь з у е м т а й м е р не д л я т о г о ч то б ы п о л у ч и т ь т о ч н о е вр ем я, но п с е в ­ д о с л у ч а й н о е ч и с л о , т а к ч т о м ы м о ж е м н е т р а т и т ь в р е м я (и к о д ) на з а п р е щ е н и е п р е р ы в а н и й . Е щ е м о ж н о с ка з а т ь , что т а к к а к мы берем б и т из м л а д ш е й 8 -б и т н о й части, то мы м о ж е м с ч и т ы в а т ь т о л ь к о её. Н ем н о го у к о р о т и м ко д и в ы х о д и т 27 байт:

00000000 00000003 00000005 00000007 00000009 0000000B 0000000D 0000000F 00000011 ; вывести 00000013 00000015 00000017 ; выход в 00000019

B9D007 mov 31C0 xor E643 out E440 in D1E8 shr D1E8 shr B05C mov 7202 jc B02F mov символ на экран B40E mov CD10 int E2EA loop DOS CD20 int

cx,007D0 ; ax,ax ; 043,al al,040 ; ax,1 ; ax,1 al,05C ; 000000013 al,02F ;

вывести только 2000 символов команда чипу таймера читать 8 бит из таймера переместить второй бит в флаг CF подготовить '\' подготовить '/'

ah,00E 010 000000003 020

И с п о л ь з о в а н и е с л у ч а й н о г о м усо р а в па м я ти к а к и с т о ч н и к а с л у ч а й н ы х ч и се л Т а к к а к э т о M S-DO S, з а щ и т ы п а м я т и з д е с ь н е т в о в с е , т а к ч т о м ы м о ж е м ч и т а т ь с к а к о г о у г о д н о а д р е с а . И д а ж е б о л е е т о г о : п р о с т а я и н с т р у к ц и я LODSB б у д е т ч и т а т ь б а й т п о а д р е с у D S : S I , н о э т о н е п р о б л е м а е с л и п р а в и л ь н ы е з н а ч е н и я н е у с т а н о в л е н ы в р е г и с т р ы , п у с т ь о н а ч и т а е т 1) с л у ч а й н ы е б а й т ы ; 2) и з с л у ч а й н о г о м е с т а в п а м я т и ! Т а к ч т о на с т р а н и ц е T r i x t e r - а 45м о ж н о н а й т и п р е д л о ж е н и е и с п о л ь з о в а т ь LODSB б е з в с я к о й и н и ц и а ­ лизации. Е с т ь т а к ж е п р е д л о ж е н и е и с п о л ь з о в а т ь и н с т р у к ц и ю SCASB в м е с т о , п о т о м у ч т о о н а в ы с т а в л я е т ф л а ­ ги в с о о т в е т с т в и и с п р о ч и т а н н ы м зн а ч е н и е м . Е щ е о д н а и д е я н а с ч е т м и н и м и з а ц и и к о д а — э т о и с п о л ь з о в а т ь п р е р ы в а н и е D OS IN T 2 9h к о т о р о е п р о с т о п е ч а т а е т с и м в о л на э к р а н е и з р е г и с т р а AL. Э т о т о ч т о с д е л а л P e te r F e rrie 46: Л и с т и н г 8 .2 1 : P e te r F e rrie : 1 0 б а й т

; AL в этом месте имеет случайное значение 00000000: AE scasb ; CF устанавливается по результату вычитания случайного байта памяти из AL. ; так что он здесь случаен, в каком-то смысле 00000001: D6 setalc ; AL выставляется в 0xFF если CF=1 или в 0 если наоборот 00000002: 242D and al,02D ;'-' ; AL здесь 0x2D либо 0 00000004: 042F add al,02F ;'/' ; AL здесь 0x5C либо 0x2F

45http://go.yurichev.com/17305 46http://go.yurichev.com/17087 (В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

875

00000006: CD29 00000008: EBF6

int jmps

029 000000000

вывести AL на экране бесконечный цикл

Т а к ч т о м о ж н о и з б а в и т ь с я и о т у с л о в н ы х п е р е х о д о в . A S C II- к о д о б р а т н о г о с л э ш а (« \» ) э т о 0x5C и 0 x 2 F д л я с л э ш а (« /» ). Т а к ч т о н а м н у ж н о к о н в е р т и р о в а т ь о д и н ( п с е в д о с л у ч а й н ы й ) б и т и з ф л а г а CF в з н а ч е н и е 0x5C и л и 0 x2F . Э т о д е л а е т с я л е г к о : п р и м е н я я о п е р а ц и ю « И » к о в с е м б и т а м в AL ( г д е все 8 б и т л и б о в ы с т а в л е н ы , л и б о с б р о ш е н ы ) с 0x2D м ы и м е е м п р о с т о 0 и л и 0x2D. П р и б а в л я я з н а ч е н и е 0 x 2 F к э т о м у з н а ч е н и ю , м ы п о л у ч а е м 0x5C и л и 0 x 2 F . И п р о с т о в ы в о д и м э т о на экран.

Вывод Т а к ж е с т о и т о т м е т и т ь , ч т о р е з у л ь т а т м о ж е т б ы т ь р а з н ы м в э м у л я т о р е D O S B o x , W i n d o w s NT и д а ­ ж е M S-DO S, и з - з а р а з н ы х у с л о в и й : ч и п т а й м е р а м о ж е т э м у л и р о в а т ь с я п о - р а з н о м у , и з н а ч а л ь н ы е значения р еги стр ов т а к ж е м о гу т бы ть разны ми.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

876

8.11.2. Множество Мандельброта You know, if you magnify the coastline, it still looks like a coastline, and a lot of other things have this property. Nature has recursive algorithms that it uses to generate clouds and Swiss cheese and things like that. Дональд Кнут, интервью (1993) М н о ж е с тв о М андельброта это ф ра кта л , х а р а кте р н о е свойство ко то р о го это сам оподобие. П р и у в е л и ч е н и и к а р т и н к и , вы в и д и т е , ч т о э т о т х а р а к т е р н ы й у з о р п о в т о р я е т с я б е с к о н е ч н о . В о т д е м о 47 н а п и с а н н о е а в т о р о м п о и м е н и « S ir _ L a g s a lo t» в 2 0 0 9 , р и с у ю щ е е м н о ж е с т в о М а н д е л ь ­ брота, и это п р о гр а м м а д ля x8 6 с р азм ером ф айла всего 64 байта. Там т о л ь к о 30 1 6 -б и тн ы х x86инструкций. Вот что она р исует:

П опробуем разоб раться, к а к она работает.

Теория Н е м н о го о к о м п л е к с н ы х ч и с л а х К о м п л е к с н о е ч и с л о с о с т о и т и з д в у х ч и с е л ( в е щ е с т в е н н а я (Re) и м н и м а я (Im ). К о м п л е кс н а я пл о ско сть — это д вухм е р н а я плоскость, где лю бое ко м п л е кс н о е число м о ж е т бы ть р а сп о л о ж е но : ве щ е стве н н а я часть — это одна ко о р д и н а та и м ним ая — вторая. Н е ко то р ы е базовые правила, ко то р ы е нам пона до бятся:

47Можно скачать здесь,

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

8 77 • С л о ж е н и е : (a + bi) + (c + d i) = (a + c) + (b + d )i Д р у ги м и словами: R e(sum ) = Re(a) + Re(b) Im ( s u m ) = Im (a ) + Im(b) • У м н о ж е н и е : (a + b i)(c + d i) = (a c - bd) + (bc + a d )i Д р у ги м и словами: R e (p ro d u c t ) = Re(a) • Re(c) - Re(b) • Re(d) Im (p r o d u c t) = Im(b) • Im (c) + Im (a ) • Im (d )

• В о з в е д е н и е в к в а д р а т : (a + b i)2 = (a + b i)(a + bi) = ( a 2 - b2) + (2 ab )i Д р у ги м и словами: R e (s q u a r e ) = R e(a)2 - I m ( a ) 2 Im (s q u a r e ) =

2 • Re(a) • Im (a )

К а к нарисовать м нож ество М андельброта М н о ж е с т в о М а н д е л ь б р о т а — э т о н а б о р т о ч е к , д л я к о т о р ы х р е к у р с и в н о е с о о т н о ш е н и е zn+1 = zn 2 + c (гд е z и c э то к о м п л е к с н ы е числа и c э то н а ч а л ь н о е з н а ч е н и е ) не с т р е м и т с я к б е с к о н е ч н о с т и . Простым русским язы ком : • П е р е ч и с л я е м в с е т о ч к и на э к р а н е . • П ро ве ряе м , я в л я е тс я ли эта т о ч к а в м н о ж е с т в е М а н д е л ь б р о та . • Вот к а к проверить:

- П р е д ста ви м т о ч к у к а к к о м п л е кс н о е число. - Возведем в квадрат. - П рибавим значен ие т о ч к и в самом начале. - В ы ш л о за п р е д е л ы ? П р е р ы в а е м с я , е с л и д а . - П ер е д в и га ем т о ч к у в новое место, к о о р д и н а т ы ко т о р о го т о л ь к о что вы числили. - П о в т о р я т ь всё э т о н е к о е р а з у м н о е к о л и ч е с т в о и т е р а ц и й . • Д в и г а ю щ а я с я т о ч к а в и т о г е н е в ы ш л а за п р е д е л ы ? Т о г д а р и с у е м т о ч к у . • Д в и г а ю щ а я с я т о ч к а в и т о г е в ы ш л а за п р е д е л ы ?

- (Д ля ч е р н о -б е л о го и з о б р а ж е н и я ) н и ч е г о не рисуем . - (Д ля ц в е т н о г о и з о б р а ж е н и я ) п р е о б р а з у е м к о л и ч е с т в о и т е р а ц и й в к а к о й -н и б у д ь ц вет. Т а к ч т о ц в е т б у д е т п о к а з ы в а т ь , с к а к о й с к о р о с т ь ю т о ч к а в ы ш л а за п р е д е л ы . В о т а л г о р и т м ы д л я к о м п л е к с н ы х и о б ы ч н ы х ц е л о ч и с л е н н ы х ч и с е л (на я з ы к е , о т д а л е н н о н а п о м и н а ­ ю щ е м P y th o n ) : Л и с т и н г 8 .2 2 : Д л я к о м п л е к с н ы х ч и с е л

def check_if_is_in_set(P): P_start=P iterations=0 while True: if (P>bounds): break P=P-2+P_start if iterations > max_iterations: break iterations++ return iterations

(В целях сбора с т а т и с т и к и ) если вы дочитали до э т о го места,

пожалуйста,

нажмите з д е с ь . Спасибо!

8 78

# черно-белое for each point on screen P: if check_if_is_in_set (P) < max_iterations: нарисовать точку # цветное for each point on screen P: iterations = if check_if_is_in_set (P) преобразовать количество итераций в цвет нарисовать цветную точку Ц е л о ч и с л е н н а я в е р с и я , э т о в е р с и я г д е вс е о п е р а ц и и н а д к о м п л е к с н ы м и ч и с л а м и з а м е н е н ы на о п е ­ рации с целочисленны м и, в соответствии с и зл ож е н ны м и ранее правилами. Л и с т и н г 8 .2 3 : Д л я ц е л о ч и с л е н н ы х ч и с е л

def check_if_is_in_set(X, Y): X_start=X Y_start=Y iterations=0 while True: if (X~2 + Y~2 > bounds): break new_X=X~2 - Y~2 + X_start new_Y=2*X*Y + Y_start if iterations > max_iterations: break iterations++ return iterations # черно-белое for X = min_X to max_X: for Y = min_Y to max_Y: if check_if_is_in_set (X,Y) < max_iterations: нарисовать точку на X, Y # цветное for X = min_X to max_X: for Y = min_Y to max_Y: iterations = if check_if_is_in_set (X,Y) преобразовать количество итераций в цвет нарисовать цветную точку на X,Y В о т т а к ж е и с х о д н ы й т е к с т на C # , к о т о р ы й е с т ь в с т а т ь е в W i k i p e d i a 48, н о м ы н е м н о г о и з м е н и м е г о , ч т о б ы о н в ы д а в а л к о л и ч е с т в о и т е р а ц и й , в м е с т о н е к о т о р о г о с и м в о л а 49:

using using using using

System; System.Collections.Generic; System.Linq; System.Text;

namespace Mnoj { class Program { static void Main(string[] args) { double realCoord, imagCoord; double realTemp, imagTemp, realTemp2, arg; int iterations; for (imagCoord = 1.2; imagCoord >= -1.2; imagCoord -= 0.05) { for (realCoord = -0.6; realCoord 0veD~eL0ve[>'yeE0ve[>''ie|pve[>''€eo0veD~TeL0ve[>'4eL0veRichM0ve PE L00 Eb0S p 01000 0 У 20 0 іг} 0 0 0 0 0 0 0 0 0 0 о 00 E @1 0 0 0 0 0 p 0 1й $Й0 10 0 (0 0 Ьн 40 8 050 |S 0 Ё .text Ц00 0 0 '.data 00 0 0 0 (2 rSrC (0 0 0 0 (Э (Э.геіос Ьн IE 0 § В

■S trin g ^ D ire c t^ T a b le

10Leave 11

Рис. 9 .3 : О р и г и н а л ь н ы й ф а й л

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

0

901 Вот о н ж е , но « з а ш и ф р о в а н н ы й » 4 -б а й т н ы м кл ю ч о м :

Рис. 9 .4 : « З а ш и ф р о в а н н ы й » ф а й л

О чень л е гк о у в и д е ть п о в тор я ю щ и еся 4 символа. В е д ь в з а г о л о в к е PE- ф а й л а м н о г о д л и н н ы х н у л е в ы х о б л а с т е й , и з - з а к о т о р ы х к л ю ч с т а н о в и т с я в и д ­ ным.

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

902 В о т н а ч а л о PE- з а г о л о в к а в 1 6 - р и ч н о м в и д е :

і I

C :\tr B p 2 \k e r - re l3 2 .d ll

B F R 0 -----------------

РЕ

.7 0 0 6 0 2 9 9

Рис. 9 .5 : PE- з а г о л о в о к

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

903 И вот он ж е, «заш иф рованны й»: Hiew: kernel32.dll.encrypted

I

C :\t m p 2 \ k e r n e l3 2 . d ll. e r c r y p t e d ~ 000000Е0 8С 61 D2 63-8С 61 D2 63-DC 000000F0 09 FB С7 30-8С 61 D2 63-8С 00000100 87 60 DB 63-8С 61 DF 63-8С 00000110 IF 53 D3 63-8С 61 D3 63-8С 00000120 8С 61 D3 63-8С 61 D3 63-8А 00000130 ВА 61 03 63-8С 61 D2 63-8С 00000140 22 64 СЗ 63-8F 61 92 62-8С 00000150 8С 61 С2 63-8С 71 D2 63-8С 00000160 FC 9Е D9 63-3D С8 D2 63-А8 00000170 8С 61 DD 63-А4 64 D2 63-8С 00000180 8С 61 D2 63-8С 61 D2 63-8С 00000190 В8 66 DF 63-В4 61 D2 63-8С 000001А0 SC 61 02 63-8С 61 D2 63-8С 000001В0 9С 54 DA 63-СС 61 D2 63-8С 000001С0 8 С 61 03 63-7С 6С D2 63-8С 000001D0 8С 61 D2 63-8С 61 D2 63-8С 000001Е0 А2 15 В7 1B-F8 61 D2 63 -1А 000001F0 8С 61 DF 63-8С 61 D3 63-8С 00000200 8С 61 D2 63-АС 61 D2 03-А2 00000210 80 71 02 63-8С 61 DC 63-8С 00000220 8С 61 D2 63-8С 61 D2 63-8С 00000230 А2 13 Д1 11-EF 61 D2 63-А4 00000240 8С 61 D3 63-8С 61 DD 63-8С 00000250 8С 61 02 63-СС 61 D2 23-А2 00000260 10 СС D2 63-8С 61 С2 63-8С 00000270 8С 61 D2 63-8С 61 D2 63-8С 00000280 8С 61 D2 63-8С 61 D2 63-8С 00000290 61 D2 63-8С 61 D2 63-8С ш IG lo b a l 2 F i lB l k З С г у В ] | Д И Я Г

24 61 61 61 61 61 61 61 CS 61 61 61 61 61 61 61 66 61 05 61 61 64 61 13 61 61 61 61

и

D2 63-C0 60 D6 63 D2 63-6C 61 DQ 42 Dl 63-8C 61 D2 63 DF 63-8C 61 04 IE D3 63-8A 61 D3 63 СЗ 63-8C 61 D3 63 D6 63-SC 71 D2 63 D2 63-9C 61 D2 63 DE 63-78 60 D2 63 D2 63-SC 61 D2 63 C2 63-10 c c D2 63 D2 63-SC 61 D2 63 D2 63-SC 61 D2 63 D2 63-SC 61 D2 63 D2 63-SC 61 D2 63 D2 63-SC 61 D2 63 DE 63-SC 61 D3 63 D2 63-SC 61 D2 63 B3 17-ED 61 D2 63 D3 63-SC 61 DC 63 D2 63-CC 61 D2 A3 D2 63-SC 61 DD 63 D2 63-SC 61 D2 63 B7 0F-E3 02 D2 63 D3 63-SC 61 C2 63 D2 63-CC 61 D2 21 D2 63-SC 61 D2 63 D2 63-SC 61 D2 63 s tr in ^ E P irg c t» і И а

Ѳ000929ЭI Ма|-сМатСц$тС ire &4ѳМаусМат с1а1В 3'|сМа"сМатсМа¥ с BS*cMal cMa"cMa® На ^сМа Ч-сКа ^сКа Ка И-сМа-ц-сМа |-сМа *-с "d|^naTbM ar cMqTc Ma-rcMqu-cMaTrc Ьа-ц-с №КН c= Ц-си ^Цсх' ц-с Ma| cfld-||-cHa¥ cMa-i|-c МатгсМа-ц-сМа-рсІЕІ Ij^c =j -F®c-| а-ц-сНа^сМа-ц-с Нац-сНац-сНац-сМац-с ЬТ гс |[а-ц-сМа¥ сМа-ц-с Ма 1Іц-сНа^сНа-ц-с Нац-сНац-сНатгсМа-ігс вШті i 0a-irclEf JcMa Ma*cMa ^сМатгсМа-ц-с На-ц-сма-ц-Ев Е1ІЕэа-ц-с Aq-ц-сМацсМа ^сМацс Ма¥ сМа-ц-сМа¥ с |=ауг вШб[Еяат|-сдйц-сМа| с Ma^cMaj сНац-сМац-с На-ц-с |j=a-|j-#BШц ІБц-с IE|[^cMaTcMa ^сМат с На¥ сНац-сНа¥ с Іра-ц-! Нац-сНац-сНац-сМац-с 5 аусМатсМа^сМатс Я 9 10Leave 11

Рис. 9 .6 : « З а ш и ф р о в а н н ы й » PE- з а г о л о в о к

Л е г к о у в и д е т ь в и з у а л ь н о , ч т о к л ю ч э т о с л е д у ю щ и е 4 б а й т а : 8C 61 D2 63. И с п о л ь з у я э т у и н ф о р м а ­ цию , д о в о л ь н о л е г к о р а с ш и ф р о в а ть весь ф айл. Т а к и м о б р а з о м , в а ж н о п о м н и т ь э т и с в о й с т в а PE- ф а й л о в : 1) в PE- з а г о л о в к е м н о г о н у л е в ы х о б л а с т е й ; 2) в с е PE- с е к ц и и д о п о л н я ю т с я н у л я м и д о г р а н и ц ы с т р а н и ц ы ( 4 0 9 6 б а й т ) , т а к ч т о п о с л е в с е х с е к ц и й обы чно им ею тся д л и н н ы е нулевы е области. Н екото ры е д р у ги е ф орм аты ф айлов м о гу т т а к ж е им еть д л и н н ы е нулевы е области. Э т о о ч е н ь т и п и ч н о д л я ф а й л о в , и с п о л ь з у е м ы х н а у ч н ы м и и н ж е н е р н ы м ПО. Д ля тех, кто с а м о сто я те л ьн о хо ч е т и зуч и ть эти ф айлы, то их м о ж н о ска ч а ть здесь: h ttp ://g o .y u r ic h e v .c o m /1 7 3 5 2 .

Упражнение • h ttp ://c h a lle n g e s .re /5 0

(В целях сбора с т а т и с т и к и ) если вы до чита ли до эт о го места, по ж а л у й с т а , н а ж м и т е з д е с ь . Спасибо!

9 04

9.1.3. Простое шифрование используя XOR-маску Я н а ш е л о д н у с т а р у ю и г р у в с т и л е in te ra c tiv e fic tio n в а р х и в е

if-archive 3:

The New Castle v3.5 - Text/Adventure Game in the style of the original Infocom (tm) type games, Zork, Collosal Cave (Adventure), etc. Can you solve the mystery of the abandoned castle? Shareware from Software Customization. Software Customization [ASP] Version 3.5 Feb. 2000 М ож но скачать з д е с ь :h ttp s ://g ith u b .c o m /D e n n is Y u ric h e v /R E - fo r- b e g in n e r s /b lo b /m a s te r /ff/X O R / m a s k _ 1 /file s /n e w c a s tle .tg z . Т а м в н у т р и е с т ь ф а й л (с н а з в а н и е м castle.dbf), к о т о р ы й я в н о з а ш и ф р о в а н , н о н е н а с т о я щ и м к р и п т о ­ а л г о р и т м о м , и о н е с ж а т , э т о ч т о - т о к у д а п р о щ е . Я б ы д а ж е н е с т а л и з м е р я т ь у р о в е н ь э н т р о п и и (9.2 ( с т р . 9 1 7 )) э т о г о ф а й л а , п о т о м у ч т о я и т а к у в е р е н , ч т о о н н и з к и й . В о т к а к о н в ы г л я д и т в M i d n i g h t C o m m a n d e r:

l/home/denni s/P/RE-book/de ys. Pg.tqfv.с .. uqo. . . .ze.n.. I j . . hu.m.j a Jc^uf. z uP. Jlql3\ 1AQt>9P. (r$K8!L 78;Qfl-.