tgoop.com/cxx95/105
Create:
Last Update:
Last Update:
#creepy
Как выбить из C++ настоящий адрес объекта
Недавно коллега искал причины какого-то бага. У нас система многопоточная с большим количеством объектов, поэтому было решено вместе с логами выводить адрес объекта в памяти, к которому относится лог. Это выглядело примерно так:
std::shared_ptr<CallService> callService = ...;Спустя некоторое время оказалось, что созданные объекты пропадают с концами
LOG_INFO("blah blah blah " << callService); // выведет адрес `callService` в конце
void Foo::Bar(std::weak_ptr<IListener> listener) {где
LOG_INFO("add listener " << listener.lock().get());
}
listener
это указатель на callService
(а класс IListener
- предок класса CallService
), выводит этот же адрес со смещением, в данном случае было на +4 байта вперед Класс
CallService
имел множественное наследование, а из-за этого указатели на базовые типы могли указывать со смещением. На простом примере:struct Foo { int i; };Это логично, потому что указатель должен давать доступ к под-объектам без всяких приколов, то есть вызов
struct Bar { short s; };
struct Baz { char c; };
struct All : Foo, Bar, Baz {};
// ...
All all; // &all == 0x7ffdfa3ce770
Foo* foo = &all; // foo == 0x7ffdfa3ce770
Bar* bar = &all; // bar == 0x7ffdfa3ce774
Baz* baz = &all; // baz == 0x7ffdfa3ce776
bar->s
должен работать одинаково, без разницы куда указывает bar
. Таким образом, затея коллеги полностью провалилась, полностью.Но есть тот факт, что виртуальные классы нормально работают со смещенными указателями. Если указатель когда-то начнет указывать не на тот адрес, то он все равно разрушится по правильному адресу при вызове виртуального деструктора.
// IEdible - виртуальный класс, (с `virtual ~IEdible()`)То же самое верно для других виртуальных методов. Если взять указатель на предок, и он будет смещенным, то вызов метода все равно отработает корректно, а точнее - неявный параметр
// struct Mango : IFruit, IEdible
std::unique_ptr<IEdible> edible;
{
std::unique_ptr<Mango> mango = std::make_unique<Mango>();
std::cout << mango << std::endl; // вывод "0x56366c90beb0"
edible = std::move(mango);
}
std::cout << edible << std::endl; // вывод "0x56366c90beb8"
// удаляется объект именно по правильному адресу "0x56366c90beb0"!
// с вызовом ~Mango()
this
будет пофикшен.struct IEdible {Таким образом можно попробовать найти "настоящий" адрес объекта виртуального класса
virtual ~IEdible() = default;
virtual void Eat() = 0;
};
struct Mango : IFruit, IEdible {
void Eat() override { Eaten = true; }; // используется неявный `this`
bool Eaten = false;
};
// ...
IEdible& e = ...; // смещенный указатель
e.Eat(); // работает корректно!
C++ vtables - Part 1 - Basics
C++ vtables - Part 2 - Multiple Inheritance
Оттуда узнаем такие факты (верные для 64-битного Linux с включенным rtti):
top_offset
(смещение относительно реального объекта), вторые 8 байт это указатель на объект typeinfo
this
на реальный и потом вызывают нужный метод. В примере выше e.Eat()
на самом деле вызовет thunk, который сместит this
на -8
(это top_offset
) и потом вызовет Mango::Eat()
.Продолжение в комментарии - нахождение реального адреса!