CXX95 Telegram 105
#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



tgoop.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


Share with your friend now:
tgoop.com/cxx95/105

View MORE
Open in Telegram


Telegram News

Date: |

There have been several contributions to the group with members posting voice notes of screaming, yelling, groaning, and wailing in different rhythms and pitches. Calling out the “degenerate” community or the crypto obsessives that engage in high-risk trading, Co-founder of NFT renting protocol Rentable World emiliano.eth shared this group on his Twitter. He wrote: “hey degen, are you stressed? Just let it out all out. Voice only tg channel for screaming”. On Tuesday, some local media outlets included Sing Tao Daily cited sources as saying the Hong Kong government was considering restricting access to Telegram. Privacy Commissioner for Personal Data Ada Chung told to the Legislative Council on Monday that government officials, police and lawmakers remain the targets of “doxxing” despite a privacy law amendment last year that criminalised the malicious disclosure of personal information. Joined by Telegram's representative in Brazil, Alan Campos, Perekopsky noted the platform was unable to cater to some of the TSE requests due to the company's operational setup. But Perekopsky added that these requests could be studied for future implementation. Each account can create up to 10 public channels
from us


Telegram C++95
FROM American