Telegram Group & Telegram Channel
#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):
1️⃣ vtable pointer указывает не на начало vtable, а смещен на 16 байт от начала. Первые 8 байт это число top_offset (смещение относительно реального объекта), вторые 8 байт это указатель на объект typeinfo
2️⃣ Компилятор генерирует особые методы под названием thunk, которые фиксят смещение указателя this на реальный и потом вызывают нужный метод. В примере выше e.Eat() на самом деле вызовет thunk, который сместит this на -8 (это top_offset) и потом вызовет Mango::Eat().

Продолжение в комментарии - нахождение реального адреса!
Please open Telegram to view this post
VIEW IN TELEGRAM



group-telegram.com/cxx95/105
Create:
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):
1️⃣ vtable pointer указывает не на начало vtable, а смещен на 16 байт от начала. Первые 8 байт это число top_offset (смещение относительно реального объекта), вторые 8 байт это указатель на объект typeinfo
2️⃣ Компилятор генерирует особые методы под названием thunk, которые фиксят смещение указателя this на реальный и потом вызывают нужный метод. В примере выше e.Eat() на самом деле вызовет thunk, который сместит this на -8 (это top_offset) и потом вызовет Mango::Eat().

Продолжение в комментарии - нахождение реального адреса!

BY C++95


Warning: Undefined variable $i in /var/www/group-telegram/post.php on line 260

Share with your friend now:
group-telegram.com/cxx95/105

View MORE
Open in Telegram


Telegram | DID YOU KNOW?

Date: |

The Dow Jones Industrial Average fell 230 points, or 0.7%. Meanwhile, the S&P 500 and the Nasdaq Composite dropped 1.3% and 2.2%, respectively. All three indexes began the day with gains before selling off. In addition, Telegram's architecture limits the ability to slow the spread of false information: the lack of a central public feed, and the fact that comments are easily disabled in channels, reduce the space for public pushback. Telegram does offer end-to-end encrypted communications through Secret Chats, but this is not the default setting. Standard conversations use the MTProto method, enabling server-client encryption but with them stored on the server for ease-of-access. This makes using Telegram across multiple devices simple, but also means that the regular Telegram chats you’re having with folks are not as secure as you may believe. False news often spreads via public groups, or chats, with potentially fatal effects. The fake Zelenskiy account reached 20,000 followers on Telegram before it was shut down, a remedial action that experts say is all too rare.
from hk


Telegram C++95
FROM American