Что нужно учитывать при выполнении записи vftable в windbg "x / 2"? - PullRequest
0 голосов
/ 01 февраля 2019

(Это довольно большой вопрос о разработке программного обеспечения. Если он не подходит для StackOverflow, я хочу скопировать его в сообщество разработчиков программного обеспечения)

Я работаю с сценарием heap_stat, который исследует отвалы.Этот сценарий основан на идее, что для любого объекта, имеющего виртуальную функцию, поле vftable всегда является первым (оно позволяет найти адрес в памяти класса объекта).

InВ моих приложениях есть некоторые объекты, имеющие vftable записей (как правило, каждый объект STL имеет его), но есть и некоторые объекты, которые этого не делают.

Для того, чтобы вызвать наличие vftable поле, я сделал следующий тест:

Создайте бессмысленный класс, имеющий виртуальную функцию, и позвольте моему классу наследовать от этого бессмысленного класса:

class NONSENSE {
    virtual int nonsense() { return 0; }
};

class Own_Class : public NONSENSE, ...

Это, как и ожидалось, создал запись vftable в символах, которую я смог найти (используя команду Windbg s x /2 *!Own_Class*vftable*):

00000000`012da1e0 Own_Application!Own_Class::`vftable'

Я также увидел разницу в использовании памяти:

sizeof(an normal Own_Class object) = 2928
sizeof(inherited Own_Class object) = 2936

=> 8 байт были добавлены для этого объекта.

Есть одна загвоздка: очевидно, некоторые объекты определены как:

class ATL_NO_VTABLE Own_Class

Этот ATL_NO_VTABLE блокирует созданиезаписи vftable, что означает следующее (ATL_NO_VTABLE равно__declspec(novtable)):

// __declspec(novtable) is used on a class declaration to prevent the vtable
// pointer from being initialized in the constructor and destructor for the
// class.  This has many benefits because the linker can now eliminate the
// vtable and all the functions pointed to by the vtable.  Also, the actual
// constructor and destructor code are now smaller.

На мой взгляд, это означает, что vftable не создается, из-за чего методы объекта вызываются более напрямую, что влияет на скорость выполнения методаи обработка стека.Разрешение на создание vftable имеет следующее влияние:

Не следует принимать во внимание:

  • В стеке есть еще один вызов, который имеет влияние только в случаесистемы, которые уже на пределе использования их памяти.(Я понятия не имею, как компоновщик указывает на конкретный метод)
  • Увеличение использования процессора будет слишком маленьким, чтобы его можно было увидеть.
  • Уменьшение скорости будет слишком маленьким, чтобы его можно было увидеть.

Следует учитывать:

  • Как упоминалось ранее, использование памяти приложением увеличивается на 8 байт на объект.Когда обычный объект имеет размер около 1000 байт, это означает увеличение использования памяти на ± 1%, но для объектов с объемом памяти менее 80 байт это может привести к увеличению использования памяти на + 10%.

Теперь у меня есть следующие вопросы:

  1. Правильный ли мой анализ воздействия?
  2. Есть ли лучший способ форсировать создание vftableполе, имеющее меньшее влияние?
  3. Я что-то пропустил?

Заранее спасибо

Ответы [ 2 ]

0 голосов
/ 29 мая 2019

А пока я нашел ужасно простой способ заставить vftable' записей для каждого класса: просто объявить каждый деструктор виртуальным.

Чтобы найти всех деструкторов, которые еще не являются виртуальнымиЯ запустил следующую команду в своем приложении Ubuntu в моем каталоге разработки:

find ./ -name "*.h" -exec fgrep "~" {} /dev/null \; | grep -v "virtual"

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

0 голосов
/ 04 февраля 2019

Правильно ли проведен мой анализ воздействия?

Нет.__declspec(novtable) не включает генерацию самой vtable для данного класса, указатель на vtable все равно будет существовать, поэтому sizeof не изменится.

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

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

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

Отказ от вызовов виртуальных функций, делая их не виртуальными, - это отдельная история.Это называется девиртуализация .Когда компилятор может быть уверен, что экземпляр того или иного класса используется, он заменяет виртуальные вызовы не виртуальными.

__declspec(novtable) никак не может помочь девиртуализации.final / sealed ключевые слова могут помочь девиртуализации, так как говорят, что больше нет производных классов / методов.

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

Для программного анализа структур в дампе, я бы рекомендовал использовать соответствующий API.Существует два API: DIA SDK и Функции dbghelp .Они похожи, но первый - объектно-ориентированный (COM), а второй - просто плоский API, поэтому первый может быть проще в использовании.

Поскольку подход со сценарием heap_stat по своей сути ограничен, я бы порекомендовал для heapвместо анализа используется UMDH , который вообще не использует vtable и показывает все виды объектов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...