Получение списка всех существующих vtables

В моем приложении есть довольно много указателей на пустоту (это из-за исторических причин, приложение изначально было написано на чистом C). В одном из моих модулей я знаю, что указатели void указывают на экземпляры классов, которые могут наследоваться от известного базового класса, но я не могу быть в этом уверен на 100%. Следовательно, выполнение dynamic_cast для указателя void может привести к проблемам. Возможно, указатель void указывает даже на простую структуру (поэтому в структуре нет vptr).

Я хотел бы исследовать первые 4 байта памяти, на которые указывает указатель void, чтобы узнать, является ли это адресом действительной vtable. Я знаю, что это зависит от платформы, возможно, даже от версии компилятора, но это может помочь мне в продвижении приложения и избавлении от всех указателей на пустоту за ограниченный период времени (скажем, 3 года).

Есть ли способ получить список всех виртуальных таблиц в приложении или способ проверить, указывает ли указатель на действительную виртуальную таблицу и наследуется ли этот экземпляр, указывающий на виртуальную таблицу, от известного базового класса?


person Patrick    schedule 14.06.2010    source источник


Ответы (4)


Я хотел бы исследовать первые 4 байта памяти, на которые указывает указатель void, чтобы узнать, является ли это адресом действительной vtable.

Вы можете это сделать, но у вас нет никаких гарантий, что это сработает. Y даже не знает, будет ли void* указывать на vtable. В прошлый раз, когда я изучал это (более 5 лет назад), я полагаю, что какой-то компилятор сохранил указатель vtable перед адресом, на который указывает экземпляр *.

Я знаю, что это платформа, может быть, даже зависит от версии компилятора,

Это также может быть связано с параметрами компилятора, в зависимости от того, какие оптимизации вы используете и так далее.

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

Это единственный вариант, который вы видите для продвижения приложения вперед? Вы рассматривали других?

Есть ли способ получить список всех vtables в приложении,

No :(

или способ проверить, указывает ли указатель на действительную виртуальную таблицу,

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

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

Нет снова.

Вот несколько вопросов (возможно, вы уже обдумывали их). Ответы на них могут дать вам больше вариантов или могут дать нам другие идеи, чтобы предложить:

  • насколько велика кодовая база? Возможно ли внести глобальные изменения, или для этого нужно распространить функциональность?

  • относитесь ли вы ко всем указателям единообразно (то есть: есть ли в вашем исходном коде общие точки, в которые вы могли бы вставлять и добавлять свои собственные метаданные?)

  • что вы можете изменить в исходном коде? (Если у вас есть доступ к вашим подпрограммам распределения памяти или вы можете подключить свои собственные, например, вы можете подключить свои собственные метаданные).

  • Если разные типы данных приводятся к void* в разных частях вашего кода, как вы потом решаете, что находится в этих указателях? Можете ли вы использовать код, различающий void*, чтобы решить, являются ли они классами или нет?

  • Поддерживает ли ваша кодовая база методологии рефакторинга? (рефакторинг небольшими итерациями, путем добавления альтернативных реализаций для частей вашего кода, затем удаления исходной реализации и тестирования всего)

Изменить (предлагаемое решение):

Выполните следующие действия:

  • определить класс метаданных (базовый)

  • замените свои процедуры выделения памяти пользовательскими, которые просто ссылаются на стандартные/старые процедуры (и убедитесь, что ваш код все еще работает с пользовательскими процедурами).

  • при каждом выделении выделяйте the requested size + sizeof(Metadata*) (и убедитесь, что ваш код все еще работает).

  • замените первые sizeof(Metadata*) байта вашего распределения стандартной последовательностью байтов, которую вы можете легко проверить (я неравнодушен к 0xDEADBEEF :D). Затем верните [allocated address] + sizeof(Metadata*) в приложение. При освобождении возьмите полученный указатель, уменьшите его на `sizeof(Metadata*), затем вызовите системную/предыдущую подпрограмму для выполнения освобождения. Теперь у вас есть дополнительный буфер, выделенный в вашем коде, специально для метаданных при каждом выделении.

  • В тех случаях, когда вам нужны метаданные, создайте/получите указатель класса метаданных, а затем установите его в зоне 0xDEADBEEF. Когда вам нужно проверить метаданные, reinterpret_cast<Metadata*>([your void* here]), уменьшить их, а затем проверить, равно ли значение указателя 0xDEADBEEF (нет метаданных) или чему-то еще.

Обратите внимание, что этот код должен быть там только для рефакторинга — для производственного кода он медленный, подвержен ошибкам и, как правило, другим плохим вещам, которых вы не хотите, чтобы ваш производственный код был. Я бы сделал весь этот код зависимым от некоторого макроса REFACTORING_SUPPORT_ENABLED, который никогда не позволит вашему классу метаданных увидеть свет производственной версии (за исключением, может быть, тестовых сборок).

person utnapistim    schedule 14.06.2010
comment
На все ваши вопросы: да, я могу многое рефакторить и изменить. Но на самом деле я искал приемы, чтобы части программного обеспечения могли все еще работать с указателями пустоты (во время перехода) или способы найти проблемы (на случай, если некоторые указатели пустоты были «забыты»). - person Patrick; 14.06.2010
comment
Отличная идея. К счастью, у меня даже есть опыт работы с пользовательскими процедурами распределения, так что это должно быть довольно легко реализовать. Спасибо. - person Patrick; 14.06.2010
comment
Извините, мне пришлось отредактировать это. SO, похоже, отменил мой +1 и отказался добавить +1 снова, пока он не будет отредактирован. Поэтому я просто добавил пустую строку в конце. - person Patrick; 14.06.2010
comment
Я думаю, что этот трюк не работает, если вы хотите выделить вектор экземпляров и обращаться к каждому элементу в нем как к пустому указателю, но в моем случае это не проблема. - person Patrick; 14.06.2010
comment
Нет, это не сработает для выделения нескольких элементов. Это не полное решение, но оно работает для других случаев. - person utnapistim; 14.06.2010

Я бы сказал, что это невозможно без соответствующей ссылки (объявление заголовка).

person YeenFei    schedule 14.06.2010

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

  1. Просмотрите свою кодовую базу, чтобы получить список всех классов, которые имеют виртуальные функции, вы можете сделать это быстро, написав скрипт, например Perl.

  2. Напишите функцию, которая принимает указатель void* в качестве входных данных и выполняет итерацию по этим классам, пытается применить его dynamic_cast и в случае успеха регистрирует информацию, такую ​​как тип интерфейса, строку кода.

  3. Вызовите эту функцию в любом месте, где вы использовали указатель void*, возможно, вы могли бы обернуть его макросом, чтобы вы могли легко получить информацию о файле, строке

  4. Запустите полную автоматизацию (если она у вас есть) и проанализируйте результат.

person baye    schedule 14.06.2010
comment
В самом деле? Я даже не заметил этого, поэтому, если я держу указатель на void, что мне делать, если я знаю, что он указывает на какой-то интерфейс и хочу его привести? - person baye; 14.06.2010
comment
Зачем вообще нужен указатель void? Конечно, вы можете (динамически) привести указатель void к базовому указателю (или к чему-либо еще), но это противоречит цели данного конкретного упражнения. - person ; 14.06.2010
comment
@Neil Butterworth: я думаю, динамический означает нединамический :-) - person Gorpik; 14.06.2010

Более простым способом было бы перегрузить operator new для вашего конкретного базового класса. Таким образом, если вы знаете, что ваши указатели void* относятся к объектам в куче, вы также можете со 100% уверенностью определить, указывают ли они на ваш объект.

person MSalters    schedule 14.06.2010