Здравствуйте, B0FEE664, Вы писали:
BFE>порождают самомодифицирующийся код для инициализации n
и для этого отключают защиту памяти?
Одно дело, когда приложение генерирует JIT код, и там может делать что хочет.
И другое, когда надо существующую страницу кода на ходу поправить.
Для этого страница из расшаренной превращается в персональную, — сколько процессов, столько будет копий страницы.
В такую страницу можно пострелять, — например, из другого потока. А если защита выключается и тут же включается обратно, это дорогое удовольствие. И всё равно, остаётся маленькое окошко для возможности стрельбы.
Перекуём баги на фичи!
Re: Инициализация static'ов и самомодифицирующийся код
Здравствуйте, B0FEE664, Вы писали:
BFE>Существуют ли компиляторы, (или опции оптимизации), которые для кода вида: BFE>порождают самомодифицирующийся код для инициализации n или все используют условный переход?
Кажется, что на современных машинах таких нет. Слишком много недостатков. Выше уже упомянули про проблемы с защитой памяти и с возрастанием требования к объёмам памяти. Последние, впрочем, можно снизить если собрать из всех функций весь код, отвечающий за инициализацию static, в несколько плотных страниц, что позволит копировать только их, а не кучу размазанных по всей программе в случайных местах (скорость, правда, несколько от этого снизится из-за пары безусловных переходов).
Но если и ещё одна неприятная проблема — многопоточное исполнение кода. Это только в одном потоке происходит самомодификация кода, в других же наблюдается кросс-модификация. А исполнение кросс-модифицированного кода без синхронизации — это уже неопределённое поведение на уровне процессора. Способы исполнять кросс-модифицированный код, конечно, есть. Но если посмотреть в документацию (например, на x86), то все они оказываются дороже банального условного перехода из классической реализации.
Так что получается, что быстрый вариант с самомодификацией не дружит с защитой памяти, с расшариванием кода и с многопоточностью. В общем, не очень хорошая реализация получается.
Более перспективным в этой области кажется другой подход: когда компилятор делает в ходе анализа два варианта функции: с read-write static и c read-only static — и вызывает второй вариант в точках программы, куда выполнение не может попасть не пройдя успешно через первый вариант. На намного более примитивном уровне так уже делается — если значение получается вычислить в compile-time, то компилятор не будет создавать код синхронизации, а сразу проинициализирует память нужным значением.
Впрочем отлично предсказываемое ветвление в классической реализации настолько дёшево, что, видимо, будет не очень много случаев, когда такая замена с двумя функциями окупится (в смысле отношения скорость/объём программы).
Re: Инициализация static'ов и самомодифицирующийся код
Здравствуйте, B0FEE664, Вы писали:
BFE>Существуют ли компиляторы, (или опции оптимизации), которые для кода вида:
Сомневаюсь, что для C/C++ вообще существуют мало-мальски серьезные (не любительские и не экспериментальные) компиляторы, порождающие самомодифицирующийся код для любого исходного.
Инициализация static'ов и самомодифицирующийся код
Здравствуйте, Кодт, Вы писали:
К> А если защита выключается и тут же включается обратно
Забавно, но ведь просто так тут же включать обратно нельзя — может произойти гонка. Ведь вдруг за это время какой-нибудь другой поток пришёл в другую функцию со своим static и тоже начал аналогичную последовательность. Тогда если обе функции случайно попадут в одну страницу, то эта страница может быть закрыта на запись раньше чем второй поток закончит свои изменения. Так что придётся ещё и синхронизировать потоки, которые работают с разными статическими переменными — налицо ещё одно ухудшение по сравнению с текущей ситуацией, где разные статические переменные имеют независимые друг от друга объекты синхронизации.
Re[2]: Инициализация static'ов и самомодифицирующийся код
Здравствуйте, Кодт, Вы писали:
К>и для этого отключают защиту памяти?
А вот как в сишке устроены указатели на вложенные функции? Причём они ещё и совместимы с указателями на нормальные функции, то есть никакой структуры, хранящей контекст, там нету.
То, что я о них слышал — это самая настоящая самомодификация.
То есть указатель на вложенную функцию — это указатель на такой код:
mov ecx, VALUE ; в валуе пишем ссылку на контекст перед тем, как передать указатель на эту функцию
jmp real_start_of_function
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[3]: Инициализация static'ов и самомодифицирующийся код
Здравствуйте, T4r4sB, Вы писали:
TB>А вот как в сишке устроены указатели на вложенные функции? Причём они ещё и совместимы с указателями на нормальные функции, то есть никакой структуры, хранящей контекст, там нету. TB>То, что я о них слышал — это самая настоящая самомодификация.
В стандартном С таких функций нет. Если же это про расширения вроде Nested Functions в gcc, то, как уже написано в документации, используются трамплины. То есть это не совсем самомодификация (она опять же не дружит с многопоточностью), но вместо этого есть стек динамически создаваемых функций, адреса которых и передаются во вложенные вызовы, и которые выглядят в общем-то именно так как у тебя и написано: TB>mov ecx, VALUE ; в валуе пишем ссылку на контекст перед тем, как передать указатель на эту функцию TB>jmp real_start_of_function
Разумеется, в такой программе возникает уже указанный недостаток с защитой памяти: появляются области, которые доступны как для записи, так и для исполнения. А если для скорости, такая память выделяется не в отдельном блоке, а в общем стеке, то, соответственно, весь стек потока становится доступным для исполнения.
Re[2]: Инициализация static'ов и самомодифицирующийся код
Здравствуйте, watchmaker, Вы писали:
W>Впрочем отлично предсказываемое ветвление в классической реализации настолько дёшево, что, видимо, будет не очень много случаев, когда такая замена с двумя функциями окупится (в смысле отношения скорость/объём программы).
Здравствуйте, B0FEE664, Вы писали:
BFE>Существуют ли компиляторы, (или опции оптимизации), которые для кода вида:
BFE>
BFE>void Fun(int a)
BFE>{
BFE> ...
BFE> static int n = a + SomeFunction();
BFE> ...
BFE>}
BFE>
BFE>порождают самомодифицирующийся код для инициализации n или все используют условный переход?
Если гнаться за оптимизацией, то такую переменную можно расположить на отдельной странице памяти.
При первом обращении получим SEH, который можем обработать и поменять свойства страницы.
Конечно первое обращение — сильно тормозит, но последующие, не будут содержать ветвления.
Никакой самомодификации страниц исполняемого кода.
С другой стороны — одна переменная на страницу — не будет дружить ни с кешем, ни с экономией памяти.
С третьей стороны, классическое решение с флаговой переменной: ветвление очень хорошо предсказывается процессором, и дружит с кешем...