Сколько можно переименовывать переменные?(заметки реверсера)

Разговоры на любые темы: вы можете обсудить здесь какой-либо сайт, найти единомышленников или просто пообщаться...
Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сколько можно переименовывать переменные?(заметки реверсера)

Сообщение Хакер » 19.10.2018 (Пт) 15:27

Допустим, вам не нравится имя какой-то переменной, константы или функции, и вы решаете её переименовать. Сколько раз можно можно совершать подобные переименования?

Казалось бы, вопрос глупый и провокационный — сколько угодно. Но в VB/VBA вы не сможете сделать это, больше чем 32 тысячи раз с копейками :tongue: И причина не в undo-history.

Причина этого в том, что внутри IDE все идентификаторы (имена переменных, констант, процедур, типов, меток, DLL-файлов, модулей, проектов) хранятся в специальном объекте-словаре. Каждый загруженный проект имеет собственный экземпляр этого объекта-хранилища.

Вся соль в том, что единожды попав в хранилище, идентификатор оттуда не удаляется. Если в коде впервые где-то всплывает идентификатор, то он сразу же заносится в словарь. При этом нет механизма, который бы следил и удалял идентификаторы, которые полностью исчезают из кода. Поэтому и сам словарь предполагает только добавление в него новых идентификаторов и не предполагает удаление. Сам словарь содержит только уникальные идентификаторы, и создатели VB предположили, что ограничение в 32 тысячи уникальных идентификаторов в коде на один проект вряд ли кого-то затронет.

Однако, если постоянно переименовывать сущности, то даже делая это много раз с одной сущностью можно упереться в лимит, и тогда IDE просто не даст вам написать ни одной новой строки, использующей новый идентификатор (то есть даст написать Exit Sub, но не даст GoTo new_label) — будет выдана ошибка «Out of memory» и правка будет отменена. Чтобы продолжить работу придётся или перезапустить IDE, или просто выгрузить и загрузить заново проект.

На самом деле, проблема вовсе не в отсутствующей памяти — ограничение обусловлено вовсе не тем, что под хранение всех идентификаторов отведён какой-то лимитированный объём памяти. Ограничение вызвано тем, что каждому идентификатору в этом словаре-хранилище выделяется особый 16-битный порядковый номер, используемый в компактном бинарном представлении кода.

Напомню ещё раз на всякий случай, что VB/VBA является, пожалуй, уникальным примером IDE, которая исходный код, загруженный в неё, вообще не хранит внутри себя в виде собственно исходного текста. Компиляция кода, если можно так сказать, начинается в момент его набора: сразу в момент после ввода каждой новой строки* кода эта строка анализируется, разбирается и трансформируется из текста (последовательности символов) в ряд бинарных структур. Весь код всех модулей проектов внутри IDE содержится уже в предварительно обработанном бинарном виде, и превращается обратно в текст лишь в 3 случаях: при сохранении в файл, при осуществлении поиска по коду или в момент отрисовки кода (при этом в текст превращается только та часть кода, которая в данный момент непосредственно видна пользователю и должна быть отрисована). Одна из таких форм бинарного представления кода — представление в виде компактной цепочки 16-битных элементов. Именно этот вариант представления используется превращении «обработанного» кода обратно в текст (например при отрисовке), при этом не надо думать что элементы цепочки соответствуют ключевым словами или токенам — элементы соответствуют скорее логическим сущностям из кода.

Так, например, вот такой statement: On Local Error GoTo error_handler
представляется как 10C9 xxxx (где xxxx — как раз 16-битный номер идентификатора «error_handler»).

А вот такой statement: On Error Resume Next — как 04С9 0000.

Именно исчерпанием диапазона этих 16-битных номеров для идентификаторов и объясняется ограничение.

Примечания:
  1. строки* — имелась в виду «суперстрока», то есть либо 1 горизонтальный ряд символов, если возможность переноса строки не использовалась, либо несколько горизонтальных рядов, объединённых символов переноса строки в конце всех рядов, кроме последнего.
  2. Внимательный читатель заметил, что 16-битные номера имели бы ограничение в 65536 идентификаторов, а здесь говорится о 32 тысячах с копейками. На самом деле, хотя значения номеров 16-битные, используется в значениях только левые 15 бит и нумерация начинается с 1, а не с нуля.
  3. 32 тысячи с копейками — это не 32767, и не потому, что нумерация начинается с единицы, а потому, что встроенные в VB ключевые слова заносятся в этот словарь-хранилище изначально, съедая около 200 свободных номеров для пользовательских идентификаторов.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение ger_kar » 19.10.2018 (Пт) 18:29

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

Хакер писал(а): Весь код всех модулей проектов внутри IDE содержится уже в предварительно обработанном бинарном виде, и превращается обратно в текст лишь в 3 случаях: при сохранении в файл, при осуществлении поиска по коду или в момент отрисовки кода
Для такого преобразования в текст необходимо ведь и наименования тоже хранить. Т.е. словарь представляет по сути пары "идентификатор - наименование", так?

Хакер писал(а):Напомню ещё раз на всякий случай, что VB/VBA является, пожалуй, уникальным примером IDE, которая исходный код, загруженный в неё, вообще не хранит внутри себя в виде собственно исходного текста. Компиляция кода, если можно так сказать, начинается в момент его набора: сразу в момент после ввода каждой новой строки* кода эта строка анализируется, разбирается и трансформируется из текста (последовательности символов) в ряд бинарных структур.
При вводе это понятно. И тоже самое происходит при загрузке проекта? Ведь код проекта в файлах проекта храниться все таки в виде текста.

Хакер писал(а):Вся соль в том, что единожды попав в хранилище, идентификатор оттуда не удаляется. Если в коде впервые где-то всплывает идентификатор, то он сразу же заносится в словарь. При этом нет механизма, который бы следил и удалял идентификаторы, которые полностью исчезают из кода. Поэтому и сам словарь предполагает только добавление в него новых идентификаторов и не предполагает удаление. Сам словарь содержит только уникальные идентификаторы, и создатели VB предположили, что ограничение в 32 тысячи уникальных идентификаторов в коде на один проект вряд ли кого-то затронет.Однако, если постоянно переименовывать сущности, то даже делая это много раз с одной сущностью можно упереться в лимит
На самом деле этот лимит, а точнее его исчерпание многократным переименованием можно обойти достаточно просто, нужно перезагрузить проект. В результате будут использованы только идентификаторы для реально существующих объектов, а все остальное уйдет в небытие. Так получается?
Бороться и искать, найти и перепрятать

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение ger_kar » 19.10.2018 (Пт) 18:33

Кстати этот самый словарь очень удобно использовать с целью обфускации кода (особенно это актуально для VBA). Т.е. для того, чтобы переименовать некую сущность, не нужно перелопачивать весь проект, а достаточно только поменять это наименование в словаре. И все!
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение Хакер » 20.10.2018 (Сб) 0:20

ger_kar писал(а):Для такого преобразования в текст необходимо ведь и наименования тоже хранить. Т.е. словарь представляет по сути пары "идентификатор - наименование", так?


В словаре имён содержатся записи, в каждой записи содержится:
  • Сама строчка имени (по этой причине записи имеют переменную длину).
  • Длина имени.
  • 16-битный идентификатор имени, который является по сути индексом в таблице указателей на записи имён.
  • L-хеш, вычисленный этой функцией, по которому собственно и осуществляется поиск имён в словаре.
  • Некоторые служебные флаги и данные.

При парсинге нового кода парсер обращается к словарю с запросами найти запись имени по предоставленному имени — функция поиска возвращает указатель на существующую запись, если есть, а если нет, добавляет новую запись и возвращает указатель.

ger_kar писал(а):И тоже самое происходит при загрузке проекта?

Загрузка почти эквивалентна мгновенному копипасту всего кода в модуль.

ger_kar писал(а):достаточно просто, нужно перезагрузить проект.

О чём я и написал в посте.

ger_kar писал(а):Кстати этот самый словарь очень удобно использовать с целью обфускации кода (особенно это актуально для VBA). Т.е. для того, чтобы переименовать некую сущность, не нужно перелопачивать весь проект, а достаточно только поменять это наименование в словаре. И все!


Так оно и есть, но здесь есть ряд подводных камней. Фактически, мы не имеем права переименовывать только строчку в словаре, не пересчитав хеш. Если пересчитывать хеши и менять их только в записях имён, словарь (по сути — хеш-таблица) становится недействительным, потому что теряется корреляция между хеш-корзинами и содержимым корзин. Поэтому придётся и хеш-таблицу перестраивать.

К тому же правка имён в словаре приведёт к тому, кто перестанет работать позднее связывание по имени в отношении любых внешних сущностей (сторонних объектов), а правка имён и хешей приведёт к тому, что перестанет работать позднее связывание по disp-id'ам (потому что ITypeLib::FindName и прочие используют всё тот же LHash для поиска).

Зато у словаря есть метод, который по 16-битному идентификатору имени возвращает строковое представление имени. Если этот метод перехватывать на этапе рендеринга кода и подставлять пустое место, в отображаемом коде пропадут все идентификаторы. Можно подставлять не пустое место, а строчку i — жертва применения обфускации увидит бредовый код, где всё крутится вокруг переменной «i», все действия совершаются с нею. В таком виде код будет не только отрисовываться, но и копироваться в буфер обмена.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение The trick » 20.10.2018 (Сб) 6:52

Интересная информация. А область видимости хранится с идентификатором? К примеру, можно ли получить то что выдает нам Ctrl+Space для конкретной области кода? Также можно ли получить этот список через объектную модель VB (EbGetExecutingProj->QueryInterface(xxx)->EnumIds())?
UA6527P

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение Хакер » 20.10.2018 (Сб) 10:36

The trick писал(а):А область видимости хранится с идентификатором?

Нет.

Потому что область видимости — это не свойство идентификатора как такового, а свойство какой-то сущности вроде переменной, константы, функции. Разные сущности, имеющие разные зоны видимости, могут при этом использовать разные идентификаторы, а могут и один и тот же идентификатор использовать.

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

Таблица имён — это именно таблица имён, а не таблица поименованных сущностей. Сами по себе имена не знают, для каких целей и в каком контексте они используются в коде: для объявления ли переменной или же для объявления метки.

Зато в хранилище имён у каждого имени есть флаг, определяющий, имеет ли конкретное имя каноническое написание. Речь идёт о том, что имена идентификаторов в VB регистронезависимые, поэтому одно и то же имя может быть написано множеством разных способов с учётом возможности написания каждой буквы в верхнем или нижнем регистре.

С момента, как имя введено в оборот, оно навечно попадает в хранилище имён, но не имеет там флага «каноничное написание» до тех пор, пока идентификатор не был использован в объявлении какой-то сущности (идентификатор может фигурировать либо в объявлении какой-то сущности, либо в использовании какой-то сущности, объявленной где-то в другом месте или вообще нигде не объявленнй).

Пока у нас нет объявления переменной «Tundra» (не обязательно переменной — любой сущности, лишь бы она была в явном виде декларирована), при встрече с каждым новым вариантом написания VB заменяет имя в таблице имён на этот новый вариант, а значит написание имени меняется сразу во всех местах по всему коду:
Изображение

Причём приоритет за последним встреченным написанием не в порядке следования строк, а в хронологическом порядке изменения написания:
Изображение

Но стоит идентификатор хоть раз применить в объявлении какой-то сущности (не обязательно это должна быть переменная — метка, функция, константа, что угодно), имя в хранилище имён получает флаг «каноничное написание», и все попытки написать имя как-то по другому после этого приводят к тому, что всюду будет использован каноничный вариант:
Изображение

Причём, как не существует механизма, который бы понимал, что не осталось больше ни одного случая применения идентификатора в коде, и удалял бы имя из хранилища имён совсем, так и не существует механизма, который бы понимал, что из кода пропали все деклараторы, использовавшие данный идентификатор, и можно хотя бы снять флаг «каноническое написание» можно снять. Поэтому и после удаления строчки Dim DinDon, да и после удаления вообще всего кода, имя «DinDon» остаётся в хранилище с флагом «каноническое написание», и все попытки написать это имя как-то по другому приводят к тому, что написание исправляется на каноническое:
Изображение
(«dInDoN» и «DINDON» продолжают исправляться на «DinDon» даже после удаления объявления переменной и удаления вообще всего кода).

Разумеется, поменять запомненное каноническое написание можно написав в коде объявление какой-то сущности (любого рода) с новым вариантом написания — новый вариант написания станет новым каноническим написанием и заменит старый вариант написания в таблице имён.

Отсюда есть два интересных наблюдения и вывода:
  1. Все замечали, что в отличие от констант и переменных, члены Enum-ов, объявленных в нашем коде, после объявления нифига не становятся каноническими:
    Изображение
    Использование энумных констант с «неправильным» написанием приводят к изменению написания констант в самом объявлении энума (что является нежелательным). «BAD» меняется на «bad», а «NEUTRAL» на «nEuTrAl». Что характерно, на само название энума это не распространяется.

    Причина явления в том, что с точки зрения парсера (синтаксического анализатора) объявление энумной константы с явно указанным значением синтаксически абсолютно не отличимо от присвоения какой-то переменной какого-то значения, а объявление энумной константы без явного указания значения — синтаксически неотличимо от вызова процедуры без параметров и без ключевого слова Call.

    И хотя ввод новой строчки или правка существующей приводит к череде действий, не ограниченной одним лишь синтаксическим разбором — проводится и некий семантический анализ новой порции кода, работа с хранилищем имён относится к зоне ответственности парсера, а парсер не имеет никакой об контексте, о другом коде, окружающем разбираемый код, а поэтому не может понять, то ли в данном случае объявляется член энума, то ли внутри процедуры происходит обычное присвоение переменной значения. А значит и не может знать, просить ли при обращении к хранилищу имён текущий вариант написания пометить как «каноничный».

    Если бы синтаксис объявления членов энума был чуть другой и предполагал какой-то отличительный признак (ключевое слово Const в начале стейтмента или запятую в конце), проблемы бы не было. Объявление самого энума синтаксически не совпадает ни с чем другом, поэтому имя «EMood» парсер заносит в таблицу имён сразу с нужным флагом, и правильное написание не перебить неправильным, всплывшим где-то помимо объявления этого энума.

  2. Однако этот позорный недочёт можно перебить знанием о том, что попадание имени в хранилище имён и присвоение флага «каноничное написание» — это навсегда. Временно объявив какие-нибудь сущности с нужными именами, можно канонизировать нужные нам написания, и тогда неправильное употребление имени члена энума не испортит объявление самого энума.

    Например, сделаем объявление 3 переменных с нужными именами и сразу же удалим это объявление — имена статут каноничными и останутся таковыми и после удаления Dim-стейтмента. А далее мы можем упоминая члены энума не беспокоиться о правильном написании, оно будет автоматически приведено к правильному:
    Изображение

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

При использовании, например, немецкой локали, имена
GroßerKönig
и
GrosserKoenig
могут считаться одинаковыми, несмотря на разную длину.

И к переменной, объявленной как Dim GroßerKönig As Object можно будет обращаться как к GrosserKoenig, при этом IDE будет автоматически исправлять GrosserKoenig на GroßerKönig.

На практике — не проверял, но теоретически должно быть именно так.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение Mikle » 20.10.2018 (Сб) 11:28

Хакер писал(а):Однако этот позорный недочёт можно перебить знанием о том, что попадание имени в хранилище имён и присвоение флага «каноничное написание» — это навсегда. Временно объявив какие-нибудь сущности с нужными именами, можно канонизировать нужные нам написания, и тогда неправильное употребление имени члена энума не испортит объявление самого энума.

До первого пересохранения проекта?
Я делаю по-другому - создаю модуль, в котором объявляю приватные переменные с именами, которые нужно сделать каноничными.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение Хакер » 20.10.2018 (Сб) 11:36

Mikle писал(а):До первого пересохранения проекта?

Не пересохранения, а переоткрытия.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение ger_kar » 20.10.2018 (Сб) 14:00

Хакер писал(а):Например, сделаем объявление 3 переменных с нужными именами и сразу же удалим это объявление — имена статут каноничными и останутся таковыми и после удаления Dim-стейтмента. А далее мы можем упоминая члены энума не беспокоиться о правильном написании, оно будет автоматически приведено к правильному:
Проблема в том, что это временное решение, действительное в течении одного сеанса работы, а посему практически бесполезное. Даже работу (полностью) над самым мало мальский проектом не получится закончить за один сеанс работы. Все равно такой проект будет в дальнейшем открываться, правиться и т.д.
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение Хакер » 20.10.2018 (Сб) 14:03

У меня вообще не было цели решать проблему, разговор затронул её для демонстрации внутренней кухни работы с кодом.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение ger_kar » 20.10.2018 (Сб) 14:12

Хакер писал(а):У меня вообще не было цели решать проблему, разговор затронул её для демонстрации внутренней кухни работы с кодом.
Ну это понятно, тем более, что это явление, и проблемой то особо не является, особенно если над проектом трудится один человек.
Бороться и искать, найти и перепрятать

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сколько можно переименовывать переменные?(заметки реверс

Сообщение Mikle » 20.10.2018 (Сб) 14:17

Согласен, не такая уж это серьёзная проблема, чтобы волноваться. Вот если бы можно было сделать написание имён в зависимости от зоны видимости, это было бы приятно, чтобы от того, что я генерирую процедуру Form_MouseMove все переменные x и y в проекте не меняли регистр.


Вернуться в Народный треп

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 45

    TopList