В мире ПО существует огромное количество программ, забытых своими разработчиками. Хорошо, когда уже есть хорошая альтернатива. А если ее нет? В программе может катастрофически не хватать каких-то мелочей, некоторые досадные ошибки могут годами доставлять массу неудобств пользователям, а на новых версиях ОС программа и вовсе может отказаться работать.
Далеко не всегда имеются исходные коды, чтобы привести программу в порядок. Если программа простая — не составит труда за короткий срок создать альтернативу. Но если программа большая и сложная, что же делать в таком случае? Не всегда рационально тратить время и деньги на разработку полного аналога, ведь расширить в разумных рамках функциональность и исправить большинство ошибок можно уже в готовом исполняемом файле.
В этой статье будут продемонстрированы методики модификации исполняемых файлов на примере расширения функциональности легендарной игры Age of Empires II (стратегия реального времени).
Итак, что же мы имеем:
Krita. Основные функции и настройки
- игра вышла в 1999 году и имеет статус abandonware — то есть она более не выставляется на продажу производителем и не приносит прибыли
- последнее обновление выпущено в 2000 году, доработка не производится
- игра является хорошим примером сложного ПО, полный аналог которого практически невозможно создать заново
- имеются досадные ошибки, которые не были исправлены разработчиками
Отказ от ответственности
Работа проведена исключительно в образовательных целях, и ни в коем случае не является призывом к нарушению действующего законодательства. Автор не несёт никакой ответственности за незаконное использование представленных материалов.
Сведения о «пациенте»
Исследуемая игра написана на движке Genie, разработкой которого с 1997 до 2000 года занималась компания Ensemble Studios. За это время на данном движке было выпущено 4 игры из серии Age of Empires. Далее Genie был лицензирован компании LucasArts, которая в 2001-2002 годах выпустила еще 2 игры на этом движке, но уже из серии Star Wars. К этому времени движок уже сильно устарел, поскольку работал в режиме 256 цветов, что очень скромно по сравнению с играми даже 2000 года. Именно поэтому доработка движка была остановлена, новых игр на его основе не выпускалось.
Игровой движок Genie загружает всю игровую графику, музыку, а главное — списки юнитов и их характеристики — из внешних файлов. То есть все игры на базе этого движка по большей части имеют очень похожие исполняемые файлы, и практически все модификации для одной игры без особых проблем портируются на другие.
Чего мы хотим добиться?
1. Отключить проверку наличия CD
В современных ноутбуках (нетбуках и т.д.) уже не редкость, когда нет встроенного CD привода. Более того, поскольку игра очень стара, оригинальный CD может перестать читаться, а кто-то и вовсе мог потерять его. В итоге владельцы вполне легальной копии игры теряют возможность запускать игру. Так что, пожалуй, разрешим игре запускаться без диска.
Программируйте мозг пока Вы спите
2. Добавить поддержку оконного режима
Age of Empires поддерживает 3 фиксированных разрешения: 800×600, 1024×768 и 1280×1024. С такими разрешениями картинка на широкоформатных мониторах выглядит растянутой, что доставляет дискомфорт. Добавить поддержку новых разрешений в игру можно, но так как в игре для каждого разрешения используются своя графика интерфейса, оставим это на потом, и ограничимся пока что добавлением оконного режима работы в игру.
3. Добавить поддержку конфигурационных файлов
Для включения каких-то системных игровых опций, их приходится передавать через командную строку при каждом запуске программы. Можно поместить все параметры в ярлык, но после перемещения игры в другой каталог ярлык перестанет работать. Выход из ситуации — добавление поддержки конфигурационных файлов.
4. Сделать игру переносимой
Игра не переносима. Для запуска на другом компьютере недостаточно скопировать игровые файлы, необходимо пройти весь процесс установки игры. В связи с широким распространением внешних накопителей хотелось бы наделить игру этим свойством.
5. Исправить известные ошибки
В интерфейсе игры имеются неприятные ошибки. Например, в диалоге подключения к IP игре в списке последних игр обрезаются ранее введенные адреса, что сводит удобство всей функции быстрого подключения на нет, приходится каждый раз вводить адрес сервера вручную.
Что нам для этого понадобится?
IDA Pro — интерактивный дизассемблер и отладчик, широко используется для реверс-инжиниринга. Умеет строить понятные даже новичкам блок-схемы. Вся его сила проявляется в интерактивном взаимодействии с пользователем. После автоматического анализа пользователь может давать функциям и переменным осмысленные имена, комментировать код и т.д. Достаточно немного понимать язык ассемблера x86 для того, чтобы начать заниматься исследованием программ прямо сейчас!
Flat Assembler — свободно распространяемый многопроходной ассемблер. FASM обладает небольшими размерами и очень высокой скоростью компиляции, имеет богатый и ёмкий макро-синтаксис, позволяющий автоматизировать множество рутинных задач. Одна из полезных возможностей — генерация чистого машинного кода без заголовков и т.д. Все это пригодится нам для генерации ассемблерных вставок.
Hexplorer — бесплатный HEX-редактор. Имеет простой дизассемблер x86, что позволяет удобнее ориентироваться внутри исполняемого файла. Используется непосредственно для ручной модификации двоичного файла без изменения его размера.
Отключение проверки наличия CD
Найти код, который отвечает за проверку наличия CD, нам поможет IDA. Это оказалось очень просто сделать. Достаточно проследить вызовы функций GetDriveTypeA и GetVolumeInformationA, которые используются для получения информации о приводе, как мы обнаружим функцию проверки наличия CD по адресу 4485A0h (базовый адрес 400000h, то есть физически в файле машинный код находится по адресу 485A0h), которая при удачной проверке возвращает в eax единицу.
CheckCD proc near sub esp, 214h push ebx push esi ; . ; множество проверок ; . test eax, eax jz cd_check_fail cd_check_ok: pop esi mov al, 1 pop ebx add esp, 214h retn 4 cd_check_fail: pop esi pop ebx add esp, 214h retn 4 CheckCD endp
При этом стоит заметить, что эта функция запрашивает значение CDPath из реестра, где хранится буква диска, с которого была установлена игра. То есть игра привязывается конкретно к той букве диска, с которого она была установлена, и при наличии оригинального диска в другом приводе игра все равно не запустится. Наиболее простое решение в данном случае — это заменить команду jz cd_check_fail, которая занимает 2 байта, на 2 операции nop, чтобы после проверки диска в eax всегда возвращалась единица. Однако, функция устроена таким образом, что при отсутсвии значения CDPath в реестре, функция сразу возвращает 0, что мешает переносимости программы. Поэтому всю функцию мы заменим на 3 простые команды:
use32 xor eax, eax inc eax retn 4
Для получения машинных кодов этих команд нам пригодится FASM.
fasm src.asm dst.bin
Основной плюс этого ассемблера для нас сейчас — это то, что он позволяет генерировать чистый машинный код без заголовков. Получаем (по строчке на команду):
31 C0 40 C2 04 00
То есть нам нужно поместить эти 6 байт в начало функции определения наличия CD, а остальные команды заменить на nop (код 90h).
8B 44 24 04 81 EC 14 02 00 00 85 C0 53 56 8B D9 .
33 С0 40 С2 04 00 90 90 90 90 90 90 90 90 90 90 .
В обычном HEX редакторе заменяем начиная с смещения 485A0h указанные выше машинные коды, после чего игра больше никогда не требует для запуска CD, даже если в реестре нет необходимого ключа. При этом мы освободили 288 байт для дополнительного машинного кода, что пригодится нам в дальнейшем.
Исправление ошибки
При подключении к сетевой игре по IP игра выводит список IP и названий игр, к которым игрок подключался ранее. Однако, по каким-то причинам IP адрес часто сохранялся не полностью, обрезалось несколько его последних символов. Необходимо разобраться в чем дело и исправить ошибку.
Займемся поиском кода, отвечающего за сохранение IP адресов последних игр в реестре. Нам известно, что игра сохраняет список последних IP игр в ключах с именами вида RecentGameName%d и RecentGameAddr%d. При помощи IDA находим по 3 ссылки на эти строки. Определить где нужный код очень просто — в коде со смещением 0512116h происходит запись этих ключей, в остальных случаях — считывание. Не будем приводить найденный участок машинного кода — там около 50 инструкций, общий смысл которых можно представить в виде маленького фрагмента кода на C.
for(int i = 0; i
Для установки значения в реестре игрой используется собственная функция-обертка RegSetVal(int, char* ValueName, char* Data, int DataSize) — название функции и параметров конечно же придуманы, в машинном коде их нет. Как видно, игра сохраняет в реестре ровно столько символов IP адреса, сколько их в названии сетевой игры. Судя по всему, программист скопировал 2 строчки сохранения названия игры для сохранения адреса, но при этом забыл исправить название массива в вызове strlen.
Для исправления ошибки у нас есть 2 варианта: подставить в strlen правильную строку или вместо strlen помещать константу 16 (максимальная длина строки адреса, например «255.255.255.255»).
Как же выглядит вызов strlen(gamename[i]) в машинном коде?
call _sprintf ; +0x1022CF mov edi, ebp or ecx, 0FFFFFFFFh xor eax, eax add esp, 0Ch repne scasb not ecx push ecx
Компилятор оптимизировал код и заменил вызов функции strlen() вычислением длины на месте. В предыдущем вызове strlen() использовалась та же строка, в вот опять оптимизация — указатель помещается в ebp один раз при первом вызове. То есть у нас нет драгоценных пары байт для того, чтобы поместить правильный указатель в ebp. Можно, конечно, сделать jmp за пределы функции, потом обратно — но это не самый красивый метод. Поэтому остановимся пока на самом простом варианте исправления ошибки — будем всегда сохранять 16 байт адреса. Для этого заменим приведенный выше машинный код на следующий:
call _sprintf ; +0x1022CF add esp, 0Ch mov ecx, 0Fh
E8 CF 22 10 00 83 С4 0С B9 0F 00 00 00
Поддержка файла конфигурации
Игра поддерживает множество полезных параметров командной строки, которые было бы удобно сохранить где-нибудь в файле, чтобы не передавать их каждый раз при запуске. Лучший способ реализовать подобное — это заставить игру при каждом старте загружать внешнюю библиотеку и выполнять функцию инициализации. При такой организации можно добавить множество полезных вещей в конфигурационные файлы, такие как: загрузка дополнительных библиотек (модулей) без необходимости модификации исполняемого файла, перехват функций работы с реестром для организации хранения всех игровых данных в конфигурационном файле игры, а не в реестре. Не лишним будет поддержка множества профилей с различными наборами настроек с возможностью быстрого их переключения. Итак, приступим.
Наиболее удобным местом подключения внешней библиотеки оказалась функция загрузки библиотеки ebueulax.dll, отвечающей за отображение лицензионного соглашения. Она как раз вызывается практически сразу после начала работы WinMain, однако помимо загрузки библиотеки ebueulax.dll и выполнения функции EBUEula она делает много лишних действий, поэтому мы полностью перепишем ее, чтобы она загружала библиотеку config.dll и выполняла библиотечную функцию LoadConfig с указателем на расположение командной строки в памяти (для её изменения) в качестве параметра.
LoadConfigLib proc near sub esp, 208h push esi push edi push ebx push ecx push edx mov esi, ds:LoadLibraryA push 67B8C4h; lpLibFileName call esi ; LoadLibraryA mov ebx, eax test ebx, ebx jz short loc_587443 push offset aLoadconfig push ebx ; hModule call ds:GetProcAddress mov edi, eax test edi, edi jz short loc_58743C mov eax, [esp+220h];**args push eax call edi jmp short loc_587443 loc_58743C: push ebx ; hLibModule call ds:FreeLibrary loc_587443: pop edx pop ecx pop ebx pop edi pop esi add esp, 208h retn LoadConfigLib endp
81 EC 08 02 00 00 56 57 53 51 52 8B 35 E0 51 63 00 68 C4 B8 67 00 FF D6 8B D8 85 DB 74 25 68 B8 B8 67 00 53 FF 15 C8 50 63 00 8B F8 85 FF 74 0C 8B 84 24 20 02 00 00 50 FF D7 EB 07 53 FF 15 20 51 63 00 5A 59 5B 5F 5E 81 C4 08 02 00 00 C3
Приведенный код делает несколько вызовов библиотечных функций. Их адреса были подставлены вручную в машинный код. В следующей статье (если вы не будете против 🙂 ) я постараюсь рассказать как при помощи FASM можно генерировать код, полностью готовый к вставке в исполняемый файл.
Заменяем исходную функцию (начинается со смещения 187400h) новой при помощи обычного HEX-редактора. Поскольку новая функция значительно короче исходной, не забываем заменить оставшиеся байты кодом операции nop — 90h. Мы получили еще 192 байта свободных байта для дополнительного машинного кода. Это место можно будет использовать в дальнейшем.
81 EC 08 02 00 00 53 56 8B 35 E0 51 63 00 57 68 CC B8 67 00 FF D6 8B D8 85 DB 75 0A 5F 5E 5B 81 C4 08 02 00 00 C3 68 C4 B8 67 00 53 FF 15 C8 50 63 00 8B F8 85 FF 75 13 53 FF 15 20 51 63 00 5F 5E 33 C0 5B 81 C4 08 02 00 00 C3 8B 84 24 18 02 00 00 50 FF D6 8B F0 85 .
81 EC 08 02 00 00 56 57 53 51 52 8B 35 E0 51 63 00 68 C4 B8 67 00 FF D6 8B D8 85 DB 74 25 68 B8 B8 67 00 53 FF 15 C8 50 63 00 8B F8 85 FF 74 0C 8B 84 24 20 02 00 00 50 FF D7 EB 07 53 FF 15 20 51 63 00 5A 59 5B 5F 5E 81 C4 08 02 00 00 C3 90 90 90 90 90 90 90 90 90 .
Всю остальную работу берет на себя модуль config.dll. Его можно написать на C/C++/Delphi.
Его задача проста — принимать от исполняемого файла указатель на командную строку приложения и в зависимости от настроек в конфигурационном файле изменять ее. Как бонус — нам пригодится возможность подгружать дополнительные модули, настраивая это в конфигурационном файле. Для этого нам нужно просто вызывать WinAPI функцию LoadLibrary именем файла модуля, которое необходимо загрузить в адресное пространство игры.
Поддержка оконного режима
Поддержка оконного режима организована в виде отдельного модуля wndmode.dll, загрузкой которого занимается config.dll. Этот модуль занимается тем, что перехватывает все вызовы DirectDraw, изменяет параметры таким образом, чтобы программа работала в окне, и только после этого передаёт управление оригинальным функциям DirectDraw.
Это реализуется путем изменения перехватываемой функции. Первые несколько байт перехватываемой функции заменяются на команду безусловного перехода к функции перехвата. Этот трюк достаточно просто реализуется в WinNT (поскольку в WinNT для каждого процесса создается своя копия образов системных библиотек), но практически нереализуем в Win9X (так как в Win9X если и можно внести изменения в образ системной библиотеки, то только в адресных пространствах всех процессов сразу).
Сам по себе модуль wndmode.dll является сильно модифицированной версией библиотеки d3dhook.dll, где реализовано:
- Полная независимость от программы D3D Windower
- Настройки загружаются из секции [WINDOWMODE] файла wndmode.ini
- Настройки по умолчанию заменены для совместимости с Genie
- Добавлен параметр Border, который включает/выключает рамку вокруг окна
- Если игровое разрешение равно системному, автоматически убирается рамка
Что получилось?
Работа по модификации проделана большая, здесь мы рассмотрели лишь некоторые изменения, поскольку описание даже небольших изменений требует огромных усилий. Остальные модификации при желании вы сможете исследовать в IDA, скачав готовый модифицированный файл.
В целом удалось добиться таких результатов:
- Вместо ebueulax.dll (отображение лицензии) загружается config.dll (поддержка конфигурационных файлов)
- Оконный режим работы (модуль wndmode.dll — модифицированный d3dhook.dll)
- Код функций работы с курсором адаптирован для работы в оконном режиме (решает много неприятных проблем, вызванных тем, что игра не рассчитана на работу в окне)
- Игра корректно работает без ее предварительной установки (достаточно скопировать файлы игры), для игры не нужен CD
- Исправлена ошибка оригинальной игры, когда при игре по IP в списке последних игр обрезались IP адреса
- Изменен порядок и расположение кнопок в меню «Один игрок» и «Редактор», исправлена ошибка с выделением кнопок в меню «Один игрок» (переписан внушительный участок кода)
- Исполняемый файл может запускаться из любого подкаталога, а не только из age2_x1 или корня (практически полностью переписано начало процедуры WinMain)
- Нет проверки наличия файла empires2.exe (его можно удалить)
- При включенном параметре MIDIMUSIC после сворачивания мелодия начинается заново (не сбрасываются инструменты)
Заключение
Если вам интересно попробовать в действии то, что у меня получилось, можете скачать Age of Empires II: The Conquerors (Lite Edition) (всего 94МБ) с моего сайта.
Простите, что здесь написано обо всем и сразу, и не сильно подробно. Я старался описать общую схему. В следующих статьях можно рассматривать каждый этап модификации в отдельности, давать какие-то советы и т.д.
- age of empires
- asm
- reverse engineering
- исследование программ
- exe mod
- модификация исполняемых файлов
- Assembler
- Системное программирование
- Реверс-инжиниринг
Источник: habr.com
Поддерживаемые изменения кода (C++)
Область применения:Visual Studio
Visual Studio для Mac
Visual Studio Code
В C++ большинство типов изменений, вносимых в код, обрабатывается в режиме «Изменить и продолжить». Некоторые изменения, однако, не могут быть применены во время выполнения программы. Чтобы применить эти изменения, необходимо остановить выполнение и собрать обновленную версию кода.
Сведения о работе с компонентом «Изменить и продолжить» для C++ в Visual Studio см. в разделе Изменить и продолжить (C++).
Требования
Параметры сборки (Проект > Свойства):
- C/C++ > Общие > Формат отладочной информации: база данных программы для «Изменить и продолжить» ( /ZI )
- Компоновщик > Общие > Включить инкрементную компоновку: да ( /INCREMENTAL ) Любые несовместимые параметры компоновщика (такие как /SAFESEH или /OPT: . ) должны вызвать предупреждение LNK4075 во время сборки.
Пример: LINK : warning LNK4075: ignoring ‘/INCREMENTAL’ due to ‘/OPT:ICF’ specification
Параметры отладчика (Отладка > Параметры > Общие):
- Включение функции «Изменить машинный код и продолжить» Любые несовместимые параметры компилятора или компоновщика вызывают ошибку во время операции «Изменить и продолжить».
Пример: Edit and Continue : error : ‘file.cpp’ in ‘MyApp.exe’ was not compiled with Edit and Continue enabled. Ensure that the file is compiled with the Program Database for Edit and Continue (/ZI) option.
Неподдерживаемые изменения
Следующие изменения в коде C/C++ не могут быть применены во время сеанса отладки. Если внести любое из таких изменений и попытаться применить его, в окне Вывод отобразится сообщение об ошибке или предупреждение.
- Большинство изменений в глобальных или статических данных.
- Изменения в исполняемых файлах, которые были скопированы с другого компьютера, а не собраны локально.
- Изменения в типах данных, которые влияют на структуру объекта, например изменения в данных-членах класса.
- Добавление более 64 Кбайт нового кода или данных.
- Добавление переменных, требующих конструктор в точке, предшествующей указателю инструкции.
- Изменения, которые влияют на код, требующий инициализации во время выполнения.
- Добавление обработчиков исключений в некоторых экземплярах.
- Изменения в файлах ресурсов.
- Изменения в коде файлов, доступных только для чтения.
- Изменения в коде при отсутствии соответствующего PDB-файла.
- Изменения в коде, для которого отсутствует объектный файл.
- Изменение лямбда-выражений, которые:
- имеют статический или глобальный член;
- передаются в функцию std::function; это приводит к нарушению подлинности ODR и к ошибке C1092.
- Операция «Изменить и продолжить» не обновляет статические библиотеки. При внесении изменения в статическую библиотеку выполнение будет продолжено со старой версией без выдачи предупреждения.
Неподдерживаемые сценарии
Операция «Изменить и продолжить» для C/C++ не доступна в следующих сценариях отладки:
- Отладка собственных приложений, скомпилированных с помощью компилятора /Zo (улучшение оптимизированного процесса отладки)
- В версиях Visual Studio до Visual Studio 2015 с обновлением 1 отладка приложений или компонентов UWP. Начиная с Visual Studio 2015 с обновлением 1 операцию «Изменить и продолжить» можно использовать в приложениях DirectX и приложениях UWP C++, так как теперь поддерживается параметр компилятора /ZI с параметром /bigobj . Функцию «Изменить и продолжить» можно также использовать с двоичными файлами, скомпилированными с параметром /FASTLINK .
- Отладка приложений Магазина 8/8.1 В этих проектах используется набор инструментов VC 120 и параметр C/ C++ /bigobj . Компонент «Изменить и продолжить» с /bigobj поддерживается только в наборе инструментов VC 140.
- отладка в Windows 98;
- отладка в смешанном режиме (машинный код/управляемый код);
- отладка JavaScript;
- отладка SQL;
- отладка с использованием файла дампа;
- изменение кода после необработанного исключения, когда не включен параметр Очищать стек вызовов от кадров необработанных исключений ;
- отладка приложения с использованием команды Присоединиться к вместо запуска приложения кнопкой Пуск в меню Отладка ;
- отладка оптимизированного кода;
- отладка старой версии кода после того, как новую версию не удалось собрать из-за ошибок сборки.
- С помощью настраиваемого пути компилятора (CL. exe). Из соображений безопасности для перекомпиляции файла во время операции «Изменить и продолжить» в Visual Studio всегда используется установленный компилятор. Если вы используете пользовательский путь компилятора (например, через пользовательскую переменную $(ExecutablePath) в файле *.props ), появится предупреждение, и Visual Studio возвращается к использованию установленного компилятора той же версии или архитектуры.
- Устаревшие архитектуры и наборы инструментов VC. При использовании набора инструментов VC 140 отладчик по умолчанию поддерживает операции «Изменить и продолжить» в приложениях x86 и x64. Устаревшие наборы инструментов поддерживают только приложения x86. Наборы инструментов старше VC 120 должны использовать устаревший отладчик. Для использования функции «Изменить и продолжить» установите флажок «Отладка > Параметры > Общие > Использовать режим совместимости машинного кода».
Ограничения компоновки
Параметры компоновщика, отключающие режим «Изменить и продолжить»
Следующие параметры компоновщика отключают режим «Изменить и продолжить».
- Параметры /OPT:REF, /OPT:ICFи /INCREMENTAL:NO отключают режим «Изменить и продолжить» со следующим предупреждением:
LINK : warning LNK4075: ignoring /EDITANDCONTINUE due to /OPT specification - Параметры /ORDER, /RELEASEи /FORCE отключают режим «Изменить и продолжить» со следующим предупреждением:
LINK : warning LNK4075: ignoring /INCREMENTAL due to /option specification - Установка любых параметров, предотвращающих создание файла программной базы данных (.PDB), отключает режим «Изменить и продолжить» без предупреждения.
Ограничения автоматического повторного связывания
По умолчанию функция «Изменить и продолжить» повторно компонует программу в конце сеанса отладки, чтобы создать новый исполняемый файл.
Функция «Изменить и продолжить» не может осуществлять перекомпоновку программы, если отладка выполняется не из расположения исходного построения. Сообщение говорит о том, что необходимо вручную заново осуществить построение.
Функция «Изменить и продолжить» не осуществляет повторное построение статических библиотек. При внесении изменений в статическую библиотеку в режиме «Изменить и продолжить» необходимо вручную повторно осуществить построение библиотеки и повторно скомпоновать использующие ее приложения.
Функция «Изменить и продолжить» не вызывает шаги пользовательского построения. Если программа использует пользовательские шаги построения, может возникнуть необходимость произвести построение вручную, чтобы можно было вызывать пользовательские шаги построения. В этом случае можно отключить перекомпоновку после выполнения «Изменить и продолжить», чтобы предлагалось построение заново вручную.
Отключение перекомпоновки после выполнения «Изменить и продолжить»
- В меню Отладка выберите Параметры и настройки.
- В диалоговом окне Параметры откройте узел Отладка и выберите узел Изменить и продолжить .
- Снимите флажок Перекомпоновка изменений кода после отладки .
Ограничения предварительно откомпилированных заголовков
По умолчанию в режиме «Изменить и продолжить» загружаются и обрабатываются предкомпилированные заголовки в фоновом режиме для ускорения обработки изменений кода. Загрузка предкомпилированных заголовков требует выделения физической памяти, что может быть проблемой в случае компиляции на компьютере с ограниченной RAM. Будет ли это являться проблемой, можно определить с помощью диспетчера задач Windows, выяснив объем доступной физической памяти во время отладки. Если это количество больше размера предварительно скомпилированных заголовков, то в режиме «Изменить и продолжить» не должно возникнуть проблем. Если объем свободной памяти меньше объема, необходимого для прекомпилированных заголовков, можно запретить средству «Изменить и продолжить» загружать прекомпилированные заголовки в фоновом режиме.
Чтобы отключить фоновую загрузку прекомпилированных заголовков для средства «Изменить и продолжить»
- В меню Отладка выберите Параметры и настройки.
- В диалоговом окне Параметры откройте узел Отладка и выберите узел Изменить и продолжить .
- Снимите флажок Разрешить прекомпиляцию .
Ограничения атрибутов IDL
В режиме «Изменить и продолжить» не создаются повторно файлы определения интерфейса (IDL). Как следствие, изменения в атрибутах IDL не отображаются в ходе отладки. Для того чтобы увидеть изменения в атрибутах IDL, необходимо остановить процесс отладки и заново осуществить построение приложения. При режиме «Изменить и продолжить» не выводится сообщение об ошибке или предупреждение при изменении атрибутов IDL. Дополнительные сведения см. в разделе Атрибуты IDL.
Диагностические проблемы
Если ваш сценарий не соответствует ни одному из указанных выше условий, можно собрать дополнительные сведения, задав следующий параметр реестра DWORD:
- Откройте командную строку разработчика.
- Выполните следующую команду:
VsRegEdit.exe set “C:Program FilesMicrosoft Visual Studio[Version][YOUR EDITION]” HKCU Debugger NativeEncDiagnosticLoggingLevel DWORD 1
VsRegEdit.exe set “C:Program Files (x86)Microsoft Visual Studio[Version][YOUR EDITION]” HKCU Debugger NativeEncDiagnosticLoggingLevel DWORD 1
Установка этого значения в начале сеанса отладки приводит к тому, что различные компоненты функции «Изменить и продолжить» выдают подробные сведения о ведении журнала на панели Окно вывода>Отладка.
См. также
Источник: learn.microsoft.com
Декораторы: Изменение поведения функции — Часть 2
Добро пожаловать на второй урок курса о декораторах в Python. В первой статье был показан самый простой стиль декораторов, которые используются для регистрации функций в качестве обработчиков или обратных вызовов для событий. В данной части будут представлены более интересные декораторы, которые изменяют или дополняют поведение декорированной функции.
Ниже представлен список статей данного курса о декораторах в Python:
- Часть 1: Регистрация функции;
- Часть 2: Изменение поведения функции (данная статья);
- Часть 3: Декораторы с аргументами.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат function my_function at 0x10ae241e0 >
>>> my_function ( 1 , 2 )
Обратите внимание, как текст decorating . появляется при определении функции, а не при ее вызове.
Причина этого в том, что Python вызывает функцию декоратора во время объявления декорированной функции.
Этот декоратор ничего не делает, поэтому функция my_function() не меняется и может вызываться обычным способом.
Декораторы, которые заменяют декорированные функции
Более продвинутые декораторы, которые представлены далее, не будут возвращать ту же функцию, что и приведена выше. Они будут возвращать другую функцию, и это позволит реализовать множество очень интересных трюков, с которыми более простые декораторы не справятся. Почти всегда такие декораторы реализуются с помощью внутренних функций, что для многих разработчиков является странной и непонятной особенностью языка Python. Для более понятного представления темы мы можем преобразовать вышеуказанный «ничего не делающий» декоратор в более мощный инструмент. Это будет сделано поэтапно.
Начнем с изменения оператора return , чтобы он возвращал функцию, отличную от f . Вернемся к оболочке Python:
def forty_two ( ) :
def my_decorator ( f ) :
print ( ‘decorating’ , f )
return forty_two
Здесь была определена функция forty_two() , и затем декоратор возвращал ссылку на данную функцию вместо f , которая была использована выше. Давайте используем данный декоратор, как было сделано выше, чтобы понять, чего именно мы добились данным изменением:
def my_function ( a , b ) :
return a + b
decorating < function my_function at 0x10ae24268 >
>>> my_function ( 1 , 2 )
Traceback ( most recent call last ) :
File «» , line 1 , in
TypeError : forty_two ( ) takes 0 positional arguments but 2 were given
Что случилось? Каким-то образом оказалось, что декорированная функция повреждена и не может быть вызвана, как было сделано ранее.
Напомним, что функция, возвращаемая декоратором, заменяет исходную функцию. Декоратор по сути изменяет функцию my_function , чтобы она была ссылкой на forty_two , поэтому при вызове my_function(1, 2) была получена ошибка. В действительности произошел вызов forty_two(1, 2) , что недопустимо, поскольку эта функция не принимает аргументов.
Обратите внимание, сообщении об ошибке называет функцию forty_two , хотя мы определили ее с названием my_function . Данный побочный эффект именования декораторов, возвращающих другую функцию, может сбивать с толку. У этой проблемы есть решение, но это тема для будущей статьи данного курса.
Как устранить ошибку? Поскольку my_function теперь является названием для forty_two , нам нужно вызвать эту функцию без аргументов:
>>> my_function ( )
Вам может быть интересно, где сейчас исходная функция. В этом примере, к сожалению, исходной функции больше нет, поскольку декоратор заменил ее на функцию forty_two() .
Очевидно, что это ужасный декоратор, который совершенно бесполезен. Идея состоит в том, что возвращаемая функция должна действовать как оболочка для исходной функции, а не как полная её замена.
В такой момент становится актуальным понятие внутренних функций. Взгляните на следующий декоратор:
def my_decorator ( f ) :
def wrapped ( ) :
return f ( 1 , 2 )
print ( ‘decorating’ , f )
return wrapped
Здесь функция wrapped() является внутренней функцией, потому что она определена внутри тела другой функции.
Важной особенностью внутренних функций является тот факт, что они они переносят с собой любые переменные в области видимости родительской функции, который в программировании называется замыканием. Обратите внимание, как внутри тела функции wrapped() можно вызвать f(1, 2) , хотя f не определена внутри функции и вместо этого становится аргументом родительской функции my_decorator() .
Проверим новый декоратор:
def my_function ( a , b ) :
return a + b
decorating < function my_function at 0x10ae24378 >
>>> my_function ( 1 , 2 )
Traceback ( most recent call last ) :
File «» , line 1 , in
TypeError : wrapped ( ) takes 0 positional arguments but 2 were given
>>> my_function ( )
Интересно, правда? Мы по-прежнему не можем передать аргументы a и b оригинальной функции my_function() , но при ее вызове без аргументов мы получаем в результате 3 , потому что функция wrapped() внедряет аргументы 1 и 2 .
Чтобы полностью восстановить два аргумента из исходной функции, можно определить функцию wrapped() также с двумя аргументами и передать эти аргументы в f :
def my_decorator ( f ) :
def wrapped ( a , b ) :
return f ( a , b )
print ( ‘decorating’ , f )
return wrapped
Конечно, данный декоратор можно применить к функциям, у которых по два аргумента. Чтобы функция wrapped() работала в качестве обертки для всех функций, нужно написать ее код таким образом, чтобы она принимала любые аргументы и затем передавала их f .
В Python можно создать функцию для принятия любых аргументов со специальными аргументами *args и **kwargs, которые представляют позиционные аргументы и аргументы ключевых слов соответственно.
def my_decorator ( f ) :
def wrapped ( * args , * * kwargs ) :
return f ( * args , * * kwargs )
print ( ‘decorating’ , f )
return wrapped
Наконец это «ничего не делающая» функция, которая совместима со всеми функциями вне зависимости от их аргументов, имплементированна во внутренней функцией.
Декоратор, структурированный подобным образом, может вставить дополнительное поведение, которое расширяет возможности функции либо перед или после вызова данной функции внутри функции wrapped() . Также можно решить не вызывать декорированную функцию в определенных случаях, например, если она определяет, что переданные аргументы неверны.
Посмотрите комментарии к следующему примеру, которые показывают, где в декораторе можно вставить эти новые варианты поведения:
def my_decorator ( f ) :
def wrapped ( * args , * * kwargs ) :
# вставляется код, который запускается перед декорированной функцией
# (и опционально можно не вызывать данную функцию)
response = f ( * args , * * kwargs )
# вставляется код, который запускается после декорированной функцией
# (и опционально решается, менять ли ответ)
return response
return wrapped
Чтобы помочь визуализировать, как данный стиль декоратора работает, далее дан пример использования операторов вывода print в важных фрагментах кода:
def my_decorator ( f ) :
def wrapped ( * args , * * kwargs ) :
print ( ‘До функции’ )
response = f ( * args , * * kwargs )
print ( ‘После функции’ )
return response
print ( ‘декорируем’ , f )
return wrapped
def my_function ( a , b ) :
print ( ‘В функции’ )
return a + b
декорируем < function my_function at 0x10ae24268 >
>>> my_function ( 1 , 2 )
До функции
После функции
Внедрение новых аргументов для функции через декоратор
Очевидный трюк, который можно реализовать с помощью такого типа декораторов, на который был дан намек выше — изменить список аргументов, который отправляется декорированной функции при ее вызове.
В качестве примера рассмотрим следующий декоратор, который вставляет текущее время в качестве первого аргумента в любые функции, которую он декорирует:
from datetime import datetime
def add_current_time ( f ) :
def wrapped ( * args , * * kwargs ) :
return f ( datetime . utcnow ( ) , * args , * * kwargs )
return wrapped
Далее дан пример использования:
def test ( time , a , b ) :
print ( ‘Я получил аргументы’ , a , b , ‘at’ , time )
>>> test ( 1 , 2 )
Я получил аргументы 1 2 at 2019 — 10 — 10 21 : 38 : 35.582887
Как видите, декорированная функция написана для принятия первого аргумента time , но данный аргумент автоматически добавляется декоратором, так что функция вызывается с оставшимися аргументами, в данном случае с a и b .
Меняем результат функции через декоратор
Еще одна очень распространенная задача, для которой подходят декораторы это изменение возвращаемого значения декорированной функции.
Если у вас есть много функций, которые вызывают функцию конвертации данных перед возвратом, вы можете переместить эту задачу конвертации в декоратор , чтобы сделать код в функциях более простым, менее повторяющимся и более удобным для чтения.
Хороший пример этой техники можно применить к веб-фреймворку Flask. Рассмотрим следующую функцию представления Flask:
return jsonify ( < ‘hello’ : ‘world’ >)
Если приложение на Flask имплементирует API, скорее всего у вас есть много путей, которые заканчиваются возвращением результата в формате JSON, сгенерированного с помощью функции jsonify() . Было бы неплохо, если бы была возможность возвращения словаря вместо необходимости использования функции jsonify() в конце каждой функции?
Декоратор to_json может сделать конвертацию в JSON формате за вас:
Здесь функция index() возвращает словарь, который во Flask является недействительным типом для ответа. Однако декоратор to_json оборачивает функцию, и возвращает ответ в JSON. Далее дан полный код приложения, включая имплементацию данного декоратора:
from flask import Flask , jsonify
app = Flask ( __name__ )
def to_json ( f ) :
def wrapped ( * args , * * kwargs ) :
response = f ( * args , * * kwargs )
if isinstance ( response , ( dict , list ) ) :
response = jsonify ( response )
return response
return wrapped
Функция wrapped() просто вызывает оригинальную функцию, в данном контексте она называется f , и затем проверяет, если возвращаемое значение является словарем или списком. Если так, то вызывается функция jsonify() , эффективно интерпретируя и исправляя возвращаемое значение перед его возвращением во фреймворк.
На заметку: В версии Flask 1.1 функция может вернуть словарь, и Flask автоматически конвертирует его в JSON. Вышеуказанный декоратор больше не нужен. Но, это по-прежнему хороший пример создания фильтров, использующих паттерн декоратора.
Проверка данных при помощи декораторов
Еще один полезный метод, который может быть реализован с помощью декораторов, заключается в проверке данных до запуска декорированной функции. Очень распространенный этому пример в веб-приложении — это аутентификация пользователя. Если задача проверки/аутентификации завершается неудачно, то декорированная функция не вызывается, и вместо нее появляется ошибка.
Далее представлен пример этой техники для Flask:
from flask import request , abort
ADMIN_TOKEN = ‘fheje3$93m*fe!’
def only_admins ( f ) :
def wrapped ( * args , * * kwargs ) :
token = request . headers . get ( ‘X-Auth-Token’ )
if token != ADMIN_TOKEN :
abort ( 401 ) # не авторизован
return f ( * args , * * kwargs )
return wrapped
def admin_route ( ) :
return «только администраторы могут получить доступ к этому маршруту!»
В данном примере, декоратор only_admins ищет HTTP заголовок X-Auth-Token во входящем запросе и затем проверяет, если он совпадает с секретным токеном администратора, который для простоты мы сделали константой. Если нет заголовка токена, или если он есть, но не совпадает, то функция abort() из Flask выполняется для генерации ответа 401 и остановки дальнейших запросов. В противном случае запрос может пройти, вызвав при этом декорированную функцию.
Обратите внимание, как в примере функции представления admin_route() используются декораторы app.route и only_admins . Это называется цепью декораторов. Цепь декораторов является сложной темой, о которой мы поговорим в будущих статьях. При работе с Flask вы должны понимать, что почти всегда декоратор app.route будет первым декоратором в цепи.
Пока мы рассматривали только те декораторы, которые сами не принимают никаких аргументов, аргументы всегда передаются декорированной функции. Это было сделано специально, потому что декораторы, которые принимают собственные аргументы в дополнение к тем, которые передаются в декорированную функцию, создавать сложнее. Это будет темой следующих статей, поэтому ознакомьтесь с концепциями, которые были рассмотрены в этой и предыдущей статьях, и будьте готовы к еще новому уровню сложности в следующей части.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
Источник: python-scripts.com