Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Низкоуровневое программирование > Assembler - Ассемблер (FASM, MASM, WASM, NASM, GoASM, Gas, RosAsm, HLA) и не рекомендуем TASM
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 06.07.2016, 20:23   #1
R71MT
Участник клуба
 
Аватар для R71MT
 
Регистрация: 16.06.2011
Сообщений: 1,428
По умолчанию Полиморфизм и самомодификация

Заинтересовала эта тема..
Интригует то, что можно писАть мутирующий код, который при каждом запуске будет отличаться содержимым (опкодами), при той-же функциональности. Хороший фундамент для защиты приложений, но непонятен один вопрос! Что значит "команды в конвейере"?

В источниках пишут, что "Если команда находится уже в конвейере, то её нельзя менять/изменить!". Почему? Что значит это определение? Например, я подготовил регистры перед вызовом прерывания. Это они что-ли "..в конвейере"? Объясните, кто в курсе.. И может у кого-нибудь есть примеры самомодифицирующихся кодов, чтоб накопить идей.

Вот пара примеров, как сбить с толку начинающих крэкеров:
Код:
org 100h
jmp start
mess   db   'Hello World!$'

start: push  3
       pop   ax
       int   10h
       mov   sp,begin
       mov   dx,mess
       push  dx
       ;.......             
       int   20h

begin: sub   ah,-9
       pop   dx
       int   21h
       int   20h
;---------------------------------------------------
org 100h
jmp start
mess   db   'Hello World!'
len    equ   $ - mess

start: mov   si,mess
       mov   bx,len
       inc   byte[fuck]

print: lodsb
       int   29h
       dec   bx
fuck:  jz    print
       ret
Для полиморфа, нужно создать несколько таблиц с разнообразными/такими финтами и подставлять их в код выбирая рандомом. Получается прикольная штука. Вот только не советуют совать "эти глюки" в цепочку команд, которые уже находятся в конвейере. От сюда и вопрос..
Нашедшего выход - затаптывают первым..
R71MT вне форума Ответить с цитированием
Старый 07.07.2016, 00:35   #2
waleri
Старожил
 
Регистрация: 13.07.2012
Сообщений: 6,493
По умолчанию

Цитата:
Сообщение от R71MT Посмотреть сообщение
"Если команда находится уже в конвейере, то её нельзя менять/изменить!". Почему?
Потому что процессор уже эту инструкцию прочитал. Каждая инструкция выполняется в несколько шагов и один из первых это считывание из памяти. Потом может пройти довольно много времени до того, как эта инструкция реально выполнится. В этот момент менять уже бесполезно.
waleri вне форума Ответить с цитированием
Старый 07.07.2016, 08:56   #3
R71MT
Участник клуба
 
Аватар для R71MT
 
Регистрация: 16.06.2011
Сообщений: 1,428
По умолчанию

waleri, а как узнать какие команды находятся в конвейере на данный момент? Есть какая-нить закономерность, или это напрямую зависит от архитектуры ЦП (скольки уровневый у него конвейер)?
Нашедшего выход - затаптывают первым..
R71MT вне форума Ответить с цитированием
Старый 07.07.2016, 09:07   #4
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

Это в 386 - 586 где кэш был внешней микросхемой были такие проблемы. А после ЦПу научился нормально отслеживает изменение команды в кэше. Заботиться об конвейре не надо. Единственное требование это чтобы изменение команды происходило атомарно. Т.е. модифицируемая команда не должна попадать на границу кэш линии. А если попала, то использовать атомарные инструкции для модификации CMPXCHG/CMPXCHG8B/CMPXCHG16B.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .
Pavia вне форума Ответить с цитированием
Старый 07.07.2016, 11:06   #5
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

Перечитал маны. Пишут что на новых ядрах могут не отследить в случае если изменяют по одному линейному адресу а выполняют по другому. Т.е если разные страницы ссылаются на одну физическую память и вы поменяете код в одной странице, а выполнять будете в другой. То процессор не отследит. Но честно пример надуманный. Так как в любом отладчике прежде чем вернуть управления мы выполним ret или jmp, что является десерализационной командой.
И на будущее Intel рекомендует, так как система кешей становятся более запутанной рекомендуют использовать jmp или cpuid(или любую десерализационной) для предотвращения проблем. От будущего этот совет не спасёт. Либо все также и останется медленным либо jmp и ret не будут десериализационными командами.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .

Последний раз редактировалось Pavia; 07.07.2016 в 11:10.
Pavia вне форума Ответить с цитированием
Старый 07.07.2016, 11:24   #6
R71MT
Участник клуба
 
Аватар для R71MT
 
Регистрация: 16.06.2011
Сообщений: 1,428
По умолчанию

Pavia, спасибо!
Значит если размер кэш-линии равен 16..64 байт (один параграф памяти), то я не должен менять значения регистров из участка кода, лежащего в этим диапазоне. Я правильно понял? Это в идеале, ..без применения атомарных инструкций.

Если-же моя команда модификации лежит внутри одной кэш-линии от изменяемой инструкции, то я должен искусственно превратить инструкцию в атомарную, добавив префикс "LOCK" (опкод F0h), т.к. на одних стандартных/атомарных инструкциях типа CMPXCHG8B далеко не уедешь. Просто каша в голове, а хочется чтобы код был портабельным.

Пусть у меня есть такой участок кода..
По-моему мнению здесь ошибки нет, хотя команды лежат внутри кэш-линии, но они не находятся в конвейере:
Код:
use16                          ; кэш-линия = 16 байт
org 100h          
start:  mov   di,fuck          ; +3 байта
        mov   ax,0cd21h        ; +3 байта
        stosw                  ; +1 байт     (!)команда модификации
        mov   bx,bx            ; +2 байта
        xor   bp,bp            ; +2 байта
        xchg  bx,ax            ; +1 байт
        inc   ah               ; +2 байта
fuck:   add   ax,dx            ; +2 байта    (!)изменяемая инструкция,
        db    90h,0c3h         ; +2 байта         ..лежит в диапазоне 10 байт
А вот во-втором примере, изменяемый код находится в конвейере, и его менять нельзя:
Код:
        mov   bx,90FFh         ; уровень(1) конвейера
        mov   ax,[sp]          ; уровень(2)
        add   bx,ax            ; упс! Пузырь в конвейере! Ждём АХ..
        mov   sp,bx            ; Пузырь! Ждём ВХ..
Могу-ли я менять участок кода, где образовался ступор/пузырь, хотя он и находится в конвейере команд?
Нашедшего выход - затаптывают первым..
R71MT вне форума Ответить с цитированием
Старый 07.07.2016, 12:36   #7
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

R71MT
Цитата:
Просто каша в голове, а хочется чтобы код был портабельным.
И не говори, система кэшей AI-32 и архитектура очень сложна. Не даром там 4 тома в 7 книгах.

Цитата:
16..64 байт
Линейка давно 64 байта. В последних 128 байт. Нет смысла поддерживать всякое старьё.
Цитата:
Если-же моя команда модификации лежит внутри одной кэш-линии от изменяемой инструкции, то я должен искусственно превратить инструкцию в атомарную, добавив префикс "LOCK" (опкод F0h), т.к. на одних стандартных/атомарных инструкциях типа CMPXCHG8B далеко не уедешь.
Нет двух. Суть в том что если у вас инструкция по подает на линию между двух кэш-линейками. То запись или изменение выполняется в 2 этапа вначале в 1 кэш-линейку затем во 2 кэш-линейку. При модификации возможна ситуация 1 планка обновилась, а вторая взялась из кэша и попала в конвейр. По сути у вас может обновится только пол инструкции. А вы ождали, что вся.

LOCK - бесполезный прёфикс. Он работает далеко не всегда:Не применима к MOV. Поэтому все и использую xchg или CMPXCHG16B. 16 байт максимальная длина инструкции.

Как решить проблему? Очевидно что выронить инструкции. В разных ассемблерах по разному. Где макрос align, там подставлялись NOP нужной длины.

К примеру точно не помню, но в MASM метки автоматически выравнивались по указанному значению
_TEXT segment para public 'CODE'
para- парагроф 16 байт.
А TASM это значение использовал только для функций.


Цитата:
Могу-ли я менять участок кода, где образовался ступор/пузырь, хотя он и находится в конвейере команд?
Можете. Процессор это отслеживает начиная с P6 (Pentium II). Вернее даже и раньше что-то было.
Просто есть рекомендации для будущих процессоров.

Цитата:
но они не находятся в конвейере:
Конвейр это сложная вещь. Интел не раскрывает деталей. Однозначно сказать что в конвейер, а что нет нельзя.
Но есть инструкции которые сбрасывают конвейр. Такие как CPUID. После них процессор ждет пока все стадии конвейра снова загрузятся новыми данными.
JMP опять таки.
Не всякий JMP годится. Как минимум надо - JMP FAR [mem]
Хотя для реального режима там были исключения.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .

Последний раз редактировалось Pavia; 07.07.2016 в 12:39.
Pavia вне форума Ответить с цитированием
Старый 07.07.2016, 15:24   #8
waleri
Старожил
 
Регистрация: 13.07.2012
Сообщений: 6,493
По умолчанию

LOCK неприменима к MOV потому что MOV инструкции это не нужно - она атомарна сама по себе. Для XCHG нужно, потому что на деле есть две операции - чтение, потом запись.

Я не уверен, что конвейр вообще следит за кешем но это легко проверить запустив самомодифицрующийся код без отладчика.
waleri вне форума Ответить с цитированием
Старый 14.07.2016, 14:29   #9
R71MT
Участник клуба
 
Аватар для R71MT
 
Регистрация: 16.06.2011
Сообщений: 1,428
По умолчанию

Накачал себе инфы и вроде с конвейером картинка чуть прояснилась..
Чтобы вопрос не повис в воздухе, попытаюсь отпоститься по результу из того, что нашёл.

Современные ЦП начиная с "Pentium Pro" вполне могут сами разобраться с содержимым конвейера. В них применяются нелинейные алгоритмы с ветвлением, в которых обработкой инструкций параллельно занимаются 6 независимых/исполнительных блоков. При надобности, один из них может перезагрузиться, не мешая работе другого. Задержки всё-же имеются, но они не столь значительны.

Значит конвейер..
Процессоры 8086 до 386 не имели встроенного конвейера, и в каждый момент времени выполняли только одну инструкцию. Этот алгоритм дошёл до наших дней и имеет такой вид: загрузка(Fetch), декодирование(D1), вычисление операндов(D2), выполнение(EX) и запись результата в регистры/память, т.н. отставка(WB):
Код:
  FE-D1-D2-EX-WB
Прогресс требовал новых решений и в процессор i486 был добавлен 5-уровневый конвейер команд. Теперь, ЦП мог выполнять сразу 5 инструкций, которые считывались блоками с текущего указателя(IP) и помещались в 'кэш инструкций'. Каждый этап конвейера мог содержать по инструкции и имел линейный вид:
Код:
  FE-D1-D2-EX-WB
     FE-D1-D2-EX-WB
        FE-D1-D2-EX-WB
           FE-D1-D2-EX-WB
              FE-D1-D2-EX-WB
Здесь видно, что первая инструкция загружается из кэша в уровень(FE). Далее, эта инструкция отправляется в уровень(D1) для декодирования, а уровень(FE) при этом освобождается и в него можно загрузить очередную инструкцию. Пока первая команда выполнится и появится на выходе(WB), в уровень(FE) можно положить ещё 4 инструкции, которые будут выполняться паралельно с первой.

Такая схема повышает производительность, но имеет и свои недостатки. Они проявляются когда, например, в конвейер попадает блок(If-Else), или-же когда одна инструкция требует результата другой. Такое явление называется ступор, или пузырь в конвейере (Pipeline Bubble). Декодер останавливается и ждёт до тех пор, пока первая инструкция не дойдёт до уровня(WB) и вернёт ему свой результат. Только после этого вторая инструкция может продолжить свой путь по конвейеру.

Решить эту проблему удалось в процессорах начиная с 'Pentium Pro' и выше. Инженеры применили в них 12-уровневый конвейер. Очевидно, что с такой длинной проблема ступора стала более актуальной, если-бы не введение принципиально нового ядра с внеочерёдным (Out-of-Order, OOO) и упреждающим (Speculative) исполнением команд.

Конвейер так-же подвергся переработки. Раньше он работал по линейному алгоритму, в котором все инструкции шли одна-за-другой. Теперь-же, в него ввели нелинейные пути, и проблема ступора стала не так заметна. Суть была в том, что декодер новых ЦП может предсказать появление ветвления (например вызов функции) и начать загрузку инструкций заранее. Вместо обработки лишь одной инструкции по указателю(IP), сегодняшние ЦП могут декодировать 4- и более инструкций за такт, разбивая одну инструкцию на несколько микроопераций (micro-ops).

Теперь, множество операций выполнялись одновременно. Одна инструкция могла читать из регистра, пока другая в него пишет. Но запись в регистр может изменить значение, нужное другой инструкции! Чтобы избежать подобных ошибок, были введены внутренние (скрытые от программиста) регистры, которыми подменялись оригинальные РОН. Такой подход позволял выполнять сразу 7 микроопераций за такт, с помощью которых и вычислялся нелинейный путь.

Если все нужные данные доступны, то они обрабатываются одновременно на 6-ти исполнительных блоках (execution unit). Если данные недоступны, выполнение откладывается до их готовности, пока выполняются другие/готовые микрооперации. Таким образом долгие операции не блокируют быстрые и последствия ступора конвейера уже не так печальны.

Сегодняшнее OOO-ядро так-же содержит 6 исполнительных блоков, но они немного изменились. Теперь каждый блок специализирован для конкретных микроопераций, позволяя выполнять работу быстрее, по сравнению с блоками общего назначения. В идеальных условиях нынешнее OOO-ядро может обрабатывать 11 микроопераций за такт.

*********************************** *************

Меня интересовало: не вызовет-ли подмена инструкций в конвейере, крах всей программы? Оказывается нет. Лишь-бы поинтеры не указывали после этого в космос. В наше время ЦП обладают достаточным интелектом, чтоб разобраться в таких ситуациях.
Нашедшего выход - затаптывают первым..
R71MT вне форума Ответить с цитированием
Старый 14.07.2016, 14:31   #10
R71MT
Участник клуба
 
Аватар для R71MT
 
Регистрация: 16.06.2011
Сообщений: 1,428
По умолчанию

С кэшем инструкций дела (на мой взгляд) обстоят по-проще..
ЦП читает инструкции блоками, чуть дальше текущего указателя(IP) и помещает их в свой кэш. Кэши современных процессоров имеют размер 32К, куда вполне может вместиться вся DOS-программа. Эти 32 кило разбиваются на линейки по 64 байт (cache line), в каждой из которых имеется поле(tag) с адресом в ОЗУ, от куда была скопирована эта линейка.

Смотрю на свойства своего ЦП: 32К/64 = 512.
Именно столько кэш-линеек (и столько-же тэгов к ним) присутствует в кэше моего процессора:
Код:
  Тип ЦП:              Mobile DualCore Intel Celeron 847, 1100 MHz
  ------------------------------------------------------------------------
  Кэш L1 кода          32 КБ    (8-way Set-Associative, 64-byte line size)
  Кэш L1 данных        32 КБ    (8-way Set-Associative, 64-byte line size)
  Кэш L2               256 КБ   (On-Die, ECC, Full-Speed)
  Кэш L3               2 МБ     (On-Die, ECC, Full-Speed)
Кэш-линейки всегда загружаются/выгружаются полность. Когда ЦП запрашивает данные, кэш-контроллёр ищет их адреса в тэгах линеек, и запоминает номер найденного тэга (обращение к линейке). Теперь, если нужно будет загрузить в кэш новую порцию данных, контроллёр выгрузит не весь кэш, а только те линейки, к которым было меньше всего обращений. Такой подход сокращает кол-во обращений к медленной ОЗУ.

Подскажите, в ту-ли степь меня несёт?
Буду благодарен, если укажите на ошибки, т.к. это то, как я понимаю весь/этот процесс.
Нашедшего выход - затаптывают первым..
R71MT вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Полиморфизм Anubys Помощь студентам 1 26.12.2011 20:42
Полиморфизм Zorgan Visual C++ 22 29.08.2011 12:23
Полиморфизм MasterSporta Общие вопросы C/C++ 3 10.04.2011 23:46
полиморфизм slayerblya Общие вопросы C/C++ 1 27.02.2011 01:43
Полиморфизм mister2010 Общие вопросы C/C++ 30 24.05.2010 01:07