Warning: Undefined array key 0 in /var/www/tgoop/function.php on line 65

Warning: Trying to access array offset on value of type null in /var/www/tgoop/function.php on line 65
115 - Telegram Web
Telegram Web
#story

Информационное насилие: курс молодого бойца 🔫

Я долго думал, стоит ли писать такой пост, но решил это сделать. Ведь обсуждаться будет не человек, а идеи, которые несутся в массы, а также условия, которые позволяют это делать. Этот текст - не какой-то "лох-патруль", а серьезный пост.

Есть такой деятель, как Андрей Викторович Столяров. Дело в том, что многие люди, которым доводится контактировать с ним, испытывают спектр различных эмоций. Неполный список групп людей, постигших мудрость:
1️⃣ Жители linux.org.ru и прочих форумов. Запомнился розыском CMS на C/C++, критикой термина GNU/Linux, и просто комментариями с нестандартными речевыми оборотами в адрес тех, с кем он несогласен.
2️⃣ Студенты ВМК МГУ. Запомнился ведением занятий по Jabber во время ковида (об этом позже), неодобрительными отзывами на дипломные работы, неуважением к студентам с Windows и/или Code::Blocks на ноутбуке. Также благодаря ему первокурсники учат Pascal.
3️⃣ Его ученики. Запомнились пранкерским разоблачением Rust (rustmustdie.com), в ответ на что неравнодушные люди выпустили разоблачение разоблачения Rust (habr). Затем джихад был объявлен Сишке (cmustdie.com), но это уже было никому не интересно.
4️⃣ Зрители АйТиБороды, посмотревшие 4-часовое интервью. К сожалению, интервьюер не развел его на срач ИРЛ, а то бы просмотров было в 10 раз больше.

У Столярова есть idée fixe - теория информационного насилия. Можно считать, что это новая философская школа мысли, но по факту это просто другое агрегатное состояние "философии" Александра Гельевича Дугина - эклектичный микс разных аксиом. Это рационализаторство собственных предпочтений, оформленное в виде "как бы" философии.

Интересно, что, так сказать, magnum opus Столярова можно посмотреть в формате видеороликов - ссылка на YouTube.

Арсенал цитат из этих видеороликов:
"Пилотный выпуск"
- Информационное насилие - такая штука, исследованием которого я занимаюсь с 97 года.
- Браузер вообще не предназначен для просмотра видео, для этого есть другие программы, а браузером нужно смотреть гипертекст.

"О запрещенных книгах"
- Я признаюсь, я даже "Mein Kampf" читал, и знаете, книжка-то так себе, не умел Гитлер книжки писать.
- Какая-то непонятная шушера за меня решает, что мне читать, а что не читать.

"I don't like spam!"
- Был такой сервис - телеконференция NNTP. Нету больше этих конференций, они в районе 2000 года благополучно загнулись, потому что там оказалось невозможно устроить защиту от спама.
- Тут речь идет не только о рекламе. Многие пытались посылать такое всем получателям своей адресной книги: "А котеночек никому не нужен?" - да не нужен, блин, мне котеночек, и никому из них не нужен котеночек!

"История с телеграмом"
- Я вам открою один секрет. Меня нет в телеграме, меня нет в вотсапе, меня нет вконтактике, меня нет в фейсбуке. В этом зловредном ютубе я появился только потому, что Навальный объявил конкурс, иначе бы меня и здесь не было.
- Мне интересно вот что: когда ВКонтакте отжали у Павла Дурова, хоть бы кто-нибудь стёр свой аккаунт?
- Я ничего не имею против Павла Дурова, ну кроме того что он, зараза, уже вторую проприетарную систему поднимает. <...> Выглядит он красиво, презентабельно, хороший парень такой. Айтишник, опять же.

"В чью пользу авторское право?"
- Информация как таковая не может иметь денежной стоимости.

"Про DRM, Всемирную паутину, ..."
- Техническая стандартизация это особый и особо опасный вид международного терроризма. <...> Такие организации, как ISO, это на самом деле террористические группы.

Некоторые видео лучше посмотреть самому, так как они просто необычные:
"I don't like spam" - исторический обзор возникновения спама.
"Об основателях рунета" - про каких-то ноунеймов, с которым личные счета из-за смерти ЖЖ.
"Спам из мэрии Москвы" - про чудо спамерской инженерной мысли и письмо в суд.
"В чью пользу авторское право?" - жуткий срыв покровов над книжными издательствами.
"Безобидный гипертекст и коварный джаваскрипт" - просто бриллиант, можно разбирать на цитаты.

(ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ)
Please open Telegram to view this post
VIEW IN TELEGRAM
#story

Как сделать программу для читов в играх ⌨️

«Я буду устанавливать сейчас все игры»
- Сашко «Компьютерный Монстр»

Когда-то давно я познакомился с программой ArtMoney. Эта программа шла на диске к журналу "Лучшие Компьютерные Игры". Она была очень простой, и я быстро смог настроить бесконечную валюту в игре Spore.

В ArtMoney нужно было указывать процесс игры, затем ввести значение игровой валюты. Программа проверяла всю память и запоминала адреса, где было это значение.
Потом надо было вернуться в игру, что-нибудь сделать, чтобы значение валюты изменилось, и указать новое значение в ArtMoney.
Адреса фильтровались (оставлялись только равные новому значению), и процесс повторялся, пока не останется единственный адрес, в котором можно поменять значение на нужное.

Я решил поисследовать, как работают такие программы. Так как я не пишу под Windows, то исследую аналог для Linux, особой разницы не должно быть 😁 Это программа scanmem.
В этой программе также можно искать нужный адрес с переменной. Можно искать не только конкретное значение, но еще промежуток значений, а также "значение по адресу больше/меньше/равно/не равно чем было в прошлый раз" и так далее.
Такое бывает нужно, если например в игре есть "полоска здоровья" без числового значения, про которое известно, что оно становится больше/меньше чем в предыдущий раз.

Проверяется, что scanmem был запущен из-под суперюзера (sudo scanmem), без этого нельзя "следить" за другим процессом (если он не является процессом-"ребенком").
Проверка происходит через getuid() == 0 - uid суперюзера равен 0.

У процесса игры надо увидеть его виртуальное адресное пространство. Это память, которую игра использует и где надо будет фильтровать адреса. Увидеть регионы непрерывно занимаемой памяти может любой (в смысле не только суперюзер), прочитав файл /proc/<pid>/maps:
    cat /proc/<pid>/maps  # вывод в терминал
Вывод примерно такой:
559b8c5d1000-559b8c5f2000 rw-p 00000000 00:00 0                          [heap]
7faa72001000-7faa72023000 rw-p 00000000 00:00 0
7faa72023000-7faa72055000 r--p 00000000 08:30 3023 /usr/lib/locale/C.UTF-8/LC_CTYPE
7faa726e0000-7faa726e1000 rw-p 0002d000 08:30 11854 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7faa726e1000-7faa726e2000 rw-p 00000000 00:00 0
7ffeb2f53000-7ffeb2f74000 rw-p 00000000 00:00 0 [stack]
(подробнее об этих файлах можно прочитать тут)

scanmem просто открывает этот файл (через fopen) и парсит каждую строку. Оставляются только регионы с возможностью записи (флаг w). Обычно этого флага нет у загруженных секций .text (исполняемый код бинарника) и .rodata (константные данные).

Те, кто не программируют активно под Linux, могут удивиться двум специфичным вещам:
1️⃣ Очень многие данные о системе/процессе можно увидеть, прочитав особые файлы, хотя видно что это НЕ "стандартные" файлы, данные в них генерируются на ходу! Это концепция "everything is a file". 😐
2️⃣ Программы реально завязываются на формат вывода линуксовых тулз/файлов, в этом ничего страшного нет. Например, весь Android завязан на вывод тулзы wpa_supplicant (для поиска WiFi).

scanmem ждет, пока юзер введет текущее значение из игры. После ввода значения вызывается ptrace для слежки за другим процессом (target - process id процесса игры), из-за этого процесс игры остановится (кинет сигнал SIGSTOP):
ptrace(PTRACE_ATTACH, target, NULL, NULL)

Затем можно читать память процесса. Делать это можно двумя способами.
В первом способе (как бы вы думали?) снова открывается файл, на этот раз /proc/<pid>/mem.
Во втором способе можно читать память через ptrace: ptrace(PTRACE_PEEKDATA, target, cur_address, NULL)

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

ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ
Please open Telegram to view this post
VIEW IN TELEGRAM
#madskillz

Нестандартные представления строк

В "стандартном" C++ есть три основных представления для строк. Не будем учитывать "составные" классы (как std::stringstream), у которых нет уникальных концепций.

=====
1️⃣ const char* - просто указатель на начало строки где-то в памяти. Обычно если итерироваться по указателю, то когда-то достигнем нулевой байт \0 (нуль-терминатор), который указывает на конец строки. Все строковые алгоритмы Си завязаны на признак \0 как на конец строки.

=====
2️⃣ std::string - класс строки, владеющий памятью для нее в куче. Запись std::string s = "abcd"; значит, что где-то в куче занята память под байты abcd\0. Известно, std::string гарантированно нуль-терминирован (начиная с C++11).
Маленькие строки полностью помещаются на стек (это называется small string optimization), но пока проигнорируем это.

=====
3️⃣ std::string_view - класс строки, не владеющий памятью. Представляет собой пару const char* s (начало строки) и size_t len (длину строки).
Не обязательно верно то, что *(s + len) == '\0'. 😁 Ведь std::string_view указывает не на всю строку, а только на какую-то ее часть.

=====
Класс std::string поведением похож на контейнер std::vector<char>. Можно посмотреть на какие-нибудь неклассические контейнеры, чтобы создать новые строковые классы, которых нет в стандартном C++.
4️⃣ SmallString - класс строки, владеющий памятью для нее, с поведением как у SmallVector<char>. Реализован в LLVM.
Запись
    std::string s1;
SmallString<256> s2;
Дает два объекта s1 и s2, у которых одинаковый набор методов, но s2 хранится на стеке, если размер строки не превышает 256 символов (планируется, что так будет в 99.9% случаев). Если размер все-таки превысили, то строку начинают хранить в куче.

=====
В бизнес-логике со строками есть проблема. Иногда в коде надо делать много составных строк. Например, для создания строки - "версии" программы нужно сложить несколько строк-частей:

Major + "." + Minor + "." + VersionPatch

В этом случае происходит создание 3 (!) лишних "временных" строк с аллокациями памяти, то есть делается строка Major + ".", потом строка (Major + ".") + Minor и так далее. Более того, итоговая строка (4-я по счету) тоже по сути лишняя, если мы хотели сразу записать итог в какой-нибудь файл, а не хранить результат сложения.

В кодовой базе LLVM есть решение, которое сложно для понимания, но мы его разберем:
5️⃣ Twine - класс "сумма строк". Документация по Twine, но больше информации в исходнике.

Трудности начинаются на уровне названия класса, как у не-носителя английского 😁 Я так и не понял смысл названия.
Вообще, трудности сначала были со словом string. До того, как я начал программировать, у меня это ассоциировалось со стрингами, которые носил Борат. У этого слова куча значений, пусть в нашем случае это будет шнур.
Теперь посмотрим на слово twine. У него тоже вагон значений, пусть в нашем случае это будет бечёвка, пнятненько?
</конец бесполезного абзаца>

Этот класс опасный: он полагается на стремное правило Reference Lifetime Extension, а также на не менее стремное правило, что объекты, созданные для использования в full-expression, не удаляются до конца выполнения этого full-expression (сформулировал как смог).

Функция должна принимать Twine по константной ссылке:
void foo(const Twine& T);
А подавать туда Twine нужно не отходя от кассы, чтобы сработало правило RLE:
foo(Twine(Major) + "." + Minor + "." + VersionPatch);

Благодаря правилу про full-expression, все составные части строки "живы" на стеке, пока не выполнится вызов foo.

Twine внутри себя выглядит как бинарное дерево. У него два "ребенка":
    Child LHS;
Child RHS;
Каждый ребенок это указатель на какой-нибудь строковой объект: const char, или std::string, или std::string_view, или другой Twine ("поддерево"). Также для удобства поддерживаются числа 😁
    union Child
{
const Twine *twine;
const char *cString;
const std::string *stdString;
/* ... */
int decI;
/* ... */
};

ПРОДОЛЖЕНИЕ В ПЕРВОМ КОММЕНТАРИИ (у телеграма ограничение по размеру постов 😟)
Please open Telegram to view this post
VIEW IN TELEGRAM
#opensource

Обзор на Boost 🆘

Boost это широко известный набор библиотек для C++. Boost оказал большое влияние на развитие C++, но что осталось от его влияния в 2023 году?

Чтобы не копаться в библиотеках самому, можно почитать про разработку в Boost в крутой книге крутого Антона Полухина, которой уже прилично лет, но ее держат в актуальном состоянии.

Надеюсь, не проспойлерю книгу (на сайте все равно есть исходники примеров), но очень, очень много чего в Boost вошло в стандарты C++, и вы половину книги будете читать про то, как работают классы boost::shared_ptr<T> и boost::string_view. Работают они почти так же, как канонические std::XXX, но иногда отличия бывают (в книге рассказано, какие именно).

Эта часть Boost отдала свои жизненные соки Стандарту C++ и перестала быть интересной (кроме как тем, кто пишет на C++ старого стандарта и не может перейти на более новый стандарт).

Далеко не все куски Boost находятся в ажурном состоянии. Сейчас в Boost состоит 169 библиотек, во многом независимых друг от друга. Практически у всех библиотек есть какие-то реальные проблемы из этих:

1️⃣ Не обновлялась с ~2006 года. Да, некоторые библиотеки просто написаны сумрачным гением тысячу лун назад и заброшены.

2️⃣ Стала неактуальной после вхождения в Стандарт C++. Это описал выше.

3️⃣ Повторяет другие библиотеки по функциональности. Бывают приколы как тупо Boost.Variant и Boost.Variant2.

4️⃣ Есть нишевая библиотека вне Boost с лучшим функционалом. Можно сравнить Boost.JSON и nlohmann/json.

В целом Boost так себе в нишевых библиотеках, количество контрибьюторов в отдельную библиотеку намного ниже, чем в популярный проект.
Иногда кто-то хочет усугубить проблему и добавить библиотеку по типу Boost.Lua (еще одну к овер9000 библиотекам про Lua), но к счастью количество библиотек растет не так быстро.
Видимо, делаются попытки с уверенностью, что Boost сам по себе типа как бы бренд, и библиотека становится лучше как бы самим фактом наличия в Boost... Что не так.

5️⃣ Лютая дичь и эрзац-компилятор. Это легендарные библиотеки-монстры, которые выглядят очень странно, потому что нестандартными способами обходят ограничения компиляторов, или просто "чем хуже тем лучше". Я бы отнес их использование к ненормальному программированию.

Например, Boost.Hana для метапрограммирования
struct Person {
BOOST_HANA_DEFINE_STRUCT(Person,
(std::string, name),
(int, age)
);
};

Boost.Spirit как LL-парсер, который притом header-only (поэтому собирается по 10 минут).

Есть библиотеки для имитации std::move до C++11 и прочие попытки перепрыгнуть выше крыши.

6️⃣ Не лучшая техническая реализация. Некоторые фанаты open source верят в миф, что стоит проекту бытоваться открытым, как тут же появляются "тысячи глаз", которые следят за его качеством. На деле никому это нафиг не нужно это вряд ли так, и большой вопрос, где качество кода в среднем лучше.

Из тех библиотек, что я активно исследовал:

Boost.ScopeExit - в другом опенсорсном проекте есть реализация подобной штуки без необходимости писать BOOST_SCOPE_EXIT_END в конце.

Boost.SmallVector - официально стырен из LLVM, а не придуман уникально. Почему бы тогда не использовать библиотеку LLVM?

Boost.DynamicBitset - по состоянию на 2018 год оно использовало захардкоженные таблицы, чтобы искать количество бит в числе или типа того.
Я туда сделал коммиты (github) и ускорил некоторые методы в 2 раза, если система поддерживает интринсики как __builtin_popcount.

Offtop: в 2018 году я исследовал dynamic bitset, потому что программировал тогда шахматы с кастомным размером доски (не 8x8) и std::bitset<64> не подходил. Но в итоге забросил идею, а запрограммировал шахматы только в 2022 году без dynamic bitset (мой лонгрид на хабре).

🤔 Таким образом, в 2023 году Boost может быть не лучшим выбором для активного использования!
Please open Telegram to view this post
VIEW IN TELEGRAM
Channel photo updated
#story

Сделай свой std::flat_set из C++23 📦

В C++23 были приняты новые контейнеры std::flat_set, std::flat_map (и их multi-варианты), аналогично уже существующим std::set и std::map.

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

API flat_set почти не отличается от set. Добавлены новые конструкторы и перегрузки insert. Заменены редкие методы:
node_type extract(...)
insert_return_type insert(node_type&& nh);
то есть те, где юзер должен был работать с вершинами красно-черного дерева.
Можно просто заменить тип на новый и почти наверняка это скомпилируется.

Новые контейнеры являются адаптерами. Это такие контейнеры, которые сами не делают ничего "тяжелого", а только держат у себя другой контейнер и все методы пробрасывают туда. В C++ уже было три таких контейнера - stack, queue, priority_queue:
    template<class T, class Container = std::deque<T>> class stack;
template<class T, class Container = std::deque<T>> class queue;
flat_set (и ему подобные) такой же адаптер:
    template<class Key, class Compare = less<Key>, class KeyContainer = vector<Key>> class flat_set;
То есть множество представляется (по умолчанию) в виде отсортированного вектора (но можно дать и другой класс - static_vector, small_vector).

Пусть у нас есть std::vector<T> data. Попробуем реализовать для него все основные методы flat_set сами, в упрощенном виде (без кастомных компараторов и прочего) 😝

0️⃣Итераторы begin/end/..., методы empty()/size()/max_size()
Подобные методы не рассматриваем, они просто перенаправляют вызовы в сам контейнер
decltype(auto) begin() noexcept { return data.begin(); }

1️⃣ Конструктор flat_set(container_type cont);
Конструкторов там примерно 24 штуки на разные случаи жизни. Можно реализовать случай, когда дается контейнер, который изначально не отсортирован и там могут быть повторяющиеся элементы - с использованием std::unique:
    data = std::move(cont);
std::sort(data.begin(), data.end());
auto last = std::unique(data.begin(), data.end());
data.erase(last, data.end());
Сложность этого метода O(NlogN)

2️⃣ Вставка insert(const value_type& x)
Нужно найти место, куда вставить новый элемент. Если он уже существует, то вставлять не нужно. Это можно делать бинарным поиском через std::lower_bound и вставкой в вектор в этом место.
    auto lower = std::lower_bound(data.begin(), data.end(), x);
if (lower == data.end() || *lower != x) {
data.insert(lower, x);
}
Сложность этого метода O(N) в общем случае и амортизированный O(logN) в случае вставки в конец. "Амортизированный" - потому что можно попасть на реаллокацию вектора 😁

3️⃣ Удаление erase(const key_type& x) и прочие методы (find, contains, ...)
Действуют по одинаковому принципу с insert - найти место этого элемента бинарным поиском и что-то с этим сделать.
    auto lower = std::lower_bound(data.begin(), data.end(), x);
if (lower != data.end() && *lower == x) {
data.erase(lower);
}
Сложность этого метода O(N) в общем случае и O(logN) в случае удаления с конца.

4️⃣ Слияние двух flat_set
В некоторых случаях надо делать слияние двух отсортированных векторов. Это стандартный алгоритм, его часто спрашивают на собеседованиях.

Если нужно вставить несколько элементов, то можно вызвать такой метод, который вставит каждый элемент по отдельности за O(N), с итоговой сложностью до O(N^2) в среднем:
    void insert(InputIterator first, InputIterator last);

Но если есть знание, что вставляемые элементы [first, last) уже отсортированы, то можно вызвать другой метод с "мусорным" объектом в начале, который сделает слияние, это будет работать за O(N), что может быть быстрее:
    void insert(sorted_unique_t, InputIterator first, InputIterator last);

Такую задачу можно решить на Leetcode - Merge Sorted Array 🙂
Please open Telegram to view this post
VIEW IN TELEGRAM
#madskillz

Сделай свой эмулятор процессора Motorola 68000 🖥

В свободное время программисты могут заниматься странными вещами. Например, я сделал эмулятор процессора Motorola 68000. Сейчас там поддержано 94% инструкций и все нужные абстракции, это заняло ~2500 строк кода.

Мне было интересно, рекомендую это делать всем, кто хочет лучше понять работу процессора 🍅

m68k (сокращенно) это процессор, сильно обогнавший свое время. Он использовался на протяжении десятилетий в компьютерах Macintosh, Amiga, Atari, приставке Sega Mega Drive, и прочих устройствах.

1️⃣ Представление в C++

В архитектуре процессора уже есть элементы 32-битовости, но с ограничениями.
Всего есть 16 регистров 32-битных (и 1 регистр 16-битный).
Несмотря на то, что "адресные" регистры (A0-A7) 32-битные, по факту для адреса берутся младшие 24 бита. То есть адресуется пространство в 16 мегабайт памяти.
Процессор поддерживает зачаток виртуализации для многозадачных систем - обращение к регистру A7 по факту будет обращением либо к USP, либо к SSP, в зависимости от флага в статусном регистре.
🔍 registers.h - представление регистров

Процессор может что-то читать/писать по адресам 0x000000 - 0xFFFFFF (24 бита), не обязательно это будет физическая память. Иногда запись в определенные адреса будет влиять на периферийные устройства. Поведение определяется "шиной".
Эмулятор имеет дело с интерфейсом. Запись/чтение могут спровоцировать ошибку по любой причине (например, чтение по нечетному адресу). Я не использую исключения C++ в эмуляторе - объект ошибки возвращается из методов.
🔍 i_device.h - интерфейс записи/чтения памяти

"Текущее состояние" эмулятора, которое можно менять, можно представлять так:
struct TContext {
NRegisters::TRegisters& Registers;
NMemory::IDevice& Memory;
};

Операнды в инструкциях ("цели") могут указывать на адрес в памяти/регистр большим количеством способов. Для этих способов можно выделить примерно такой интерфейс с общим методом чтения/записи данных:
🔍 target.h - представление операнда в инструкциях

Последнее, самый большое представление - у инструкций. У них есть "тип" инструкции и все нужные параметры. Ассемблер очень "ортогональный", поэтому представление в виде набора переменных подходит лучше всего.
🔍 instructions.h - представление инструкций

2️⃣ Как реализовать и протестировать инструкции

Каждая инструкция занимает 2 байта. Иногда могут потребоваться 2/4 байта дополнительных данных после инструкции (обязательно четное число).

Декодирование инструкции можно написать глядя на крутую таблицу от GoldenCrystal (сайт регулярно лежит, в комментариях к посту есть PDF).

Краткое описание инструкции можно читать в этой markdown-документации. Иногда этого недостаточно, тогда можно читать длинное описание в этой книге.

Самое важная часть - тестирование. Небольшая ошибка в каком-нибудь статусном флаге может привести к катастрофе во время эмуляции. Когда программа большая, ее становится легко сломать в неожиданном месте, поэтому нужны тесты на все инструкции.

Мне очень помогли тесты из этого репозитория. На каждую инструкцию есть 8000+ тестов, которые покрывают все возможные случаи. Суммарно тестов чуть больше миллиона.
Они могут находить даже самые мелкие ошибки - нередко бывает ситуация, что не проходятся ~20 тестов из 8000.
Например, инструкция MOVE (A6)+ (A6)+ (обращение к регистру A6 делается с пост-инкрементом) должна работать не так, как я реализовал, поэтому я сделал костыль, чтобы работало корректно.

Продолжение в комментариях (эмуляция программ)
Please open Telegram to view this post
VIEW IN TELEGRAM
#opensource

Создание своих плагинов для Vim 👩‍💻

Я не люблю холивары, в том числе на тему текстовых редакторов. У людей есть баг - они рационализируют свой выбор и убеждения.
В худших случаях это вытекает в поиск того, за чей счет бы самоутвердиться, и приписывании другой стороне ложных свойств. Так, многие любители не-Vim уверены, что в Vim нет автокомплита, а многие любители Vim бывают уверены в каких-то вовсе страшных вещах насчет не-Vim.
Я сознательно отказываюсь от рационализации своего выбора. Например, довод о "быстроте редактирования" в Vim неактуален в обстановке, когда программист намного дольше читает уже написанный код или обсуждает решение, чем пишет новый код.

Vim👩‍💻 (позже его форк Neovim👩‍💻) для меня основной редактор с 2016 года, а полностью сменил гендер "акклиматизировался" я где-то за год (за сравнимое время можно полностью выучить любой другой редактор).
В Vim каждый сам собирает свою "IDE" из 15-20 плагинов и кучи настроек.

Vim я открываю прямо на удаленной машине - потому что работаю в виртуалке.

Для интереса можно сделать свои специфические плагины, чтобы "IDE" стала удобнее. На Neovim плагины можно писать на языке Lua. Чтобы понять, что нужно делать, можно для начала почитать эту и эту доки, но в процессе написания все равно надо много гуглить.

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

Сейчас я для работы использую три самодельных плагина, которые очень помогают решать задачи, которые раньше делал ручным способом. Они специфические для компании 😁

1️⃣ Ссылка на CodeSearch
🔍 Гифка (10.7 MB)
😱 Исходник
Раньше надо было копировать текст в терминале (выделение мышью и Ctrl+C), идти на сайт внутреннего поиска кода, вставлять текст Ctrl+V, экранировать спецсимволы... Сейчас можно выделить текст (или не выделять, если нужно найти одно слово под курсором) и нажать Ctrl+S (или ввести команду :CodeSearch, но хоткей быстрее), чтобы вывелась правильная ссылка.

Это конечно никуда не годится, лучшее решение - встроить CodeSearch например в плагин Telescope 😁

По исходнику видно, насколько много костылей в Vim. Например, "слово под курсором" ищется так: vim.fn.expand('<cword>'), а позиции начала и конца выделения это '< и '>.

2️⃣ Вставка на Pastebin
🔍 Гифка (13.3 MB)
😱 Исходник
Есть тулза, чтобы отправлять файлы в нечто вроде местного Pastebin. Чтобы вставить кусок логов или кода, приходилось выделять их, делать временный файл, вставлять выделенное, сохранять текст, вызывать тулзу в другой вкладке... Сейчас можно просто выделить текст (или не выделять, если нужно вставить весь файл) и нажать Ctrl+P (или ввести команду :Paste), чтобы плагин все это сделал и показал ссылку.

Видно еще больше костылей: отсутствие многих базовых функций (их надо писать самому), выделенный текст копируется в регистр " или 0, а путь до текущего файла можно вытащить через vim.fn.expand('%') - это все надо помнить или очень сильно читать документацию.

3️⃣ Автоматический code style при сохранении
🔍 Гифка (44.1 MB)
😱 Исходник
Используется тулза clang-format для форматирования кода, но с локальными хаками и особенностями. Если код не удовлетворяет код-стайлу, то падает проверка на CI, и приходится запускать тулзу, потом смотреть, что она нормально отработала, закрывать и открывать файл, и так далее... Сейчас эта тулза запускается сама, каждый раз после сохранения файла, меняя только нужные участки кода.

Таким образом, можно написать свои плагины, чтобы уменьшить боль от ручного труда. Это, конечно, базовые плагины, и не сравнятся с монстрами наподобии Telescope или Gitsigns. К "большим" плагинам можно делать свои плагины. 😐

Гифки записывал через blue-recorder, визуализация нажатий на кнопки через screenkey.
Please open Telegram to view this post
VIEW IN TELEGRAM
#opensource

Обзор исходников Telegram Desktop 💬

Для меня Telegram - основное средство связи со всеми коллегами и знакомыми. Поэтому я решил посмотреть в исходники десктопного Telegram и возможно что-нибудь переделать под себя.

Эти исходники находятся тут - https://github.com/telegramdesktop/tdesktop, там же внизу ссылки на инструкции по билду Telegram.

Я попробовал сбилдить Telegram для Linux на двух ноутбуках и на виртуалке, и у меня не получилось, вплоть до того что навернулось окружение рабочего стола на одном ноутбуке 😶 Сначала чинил кривые скрипты сборки, потом пытался обновить старую версию Glib, и так далее.

Зато получилось с полпинка сбилдить его для Windows 😁 Вспомнил как выглядит Visual Studio: скриншот.

Исходники самого Telegram сравнительно небольшие, но вместе с ним качается и билдится из исходников куча хрени (~25 либ включая ffmpeg и Qt, в размере несколько гигабайтов).

Десктоп написан на движке Qt, с довольно хорошим кодом, он сравнительно небольшой. После исследования известны такие факты:
1️⃣ Что-нибудь "сломать" в самом Телеграме не выйдет, так как десктоп сделан правильно - это всего лишь красивая оболочка над Telegram API, которая получает и посылает все данные из сервера.
Например, когда мы видим статус last seen recently вместо точного времени (есть такая настройка), то десктоп действительно не знает точного времени - сервер отдает ему именно такой статус.
2️⃣ Можно написать свой десктоп или даже консольный Телеграм, но на это сомнительное в своей полезности занятие уйдет слишком много времени.
3️⃣ Если собрать "свой" Телеграм и зайти туда хоть раз, то твой аккаунт ставят на счётчик ставят под колпак. За действиями с аккаунта будут следить, в случае разных приколов (спам, накрутка, другой абьюз) будет вечный бан. Об этом честно предупреждают. 🔫

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

Я сделал такую мелкую фичу: в написанном сообщении обнаруживаются все http-ссылки и заменяются на укороченный вариант из clck.ru. Это может быть нужно для безопасности, если в компании есть свой сервис url shortener для внутренних ссылок, но не хочется тратить по 5-10 секунд для вбивания каждой ссылки.

К счастью, Qt это одна из лучших библиотек C++ на свете, поэтому там есть почти всё свое. Контейнеры (как QVector, QList, QString), регексы, сеть, и очень многое другое. Поэтому такие задачи делаются сравнительно без проблем.

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

Примерный коммит, который включает короткие урлы (с параллельными походами!), выглядит так. (И еще такой кусок кода во "внешней" либе). Там работа со строками.

🔍😐 Гифка с результатом

Урлы из гифки:
исходник1, исходник2, исходник3
https://clck.ru/34A7Hv, https://clck.ru/3vyXS, https://clck.ru/8f7h6

При желании можно достаточно сильно кастомизировать Telegram, чтобы в нем показывалось больше информации (например, соответствие профиля юзера и его аккаунта во внутренней сети компании, его должность, и т.д.)
Please open Telegram to view this post
VIEW IN TELEGRAM
Channel photo updated
#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
#compiler

Простые тесты для C++ кода на Python 🍄🍄🍄🍄🍄

Часто хочется покрыть тестами код на C++ в проекте - не логику, которую описывает код, а сам исходный код. Сейчас для этого есть приличное количество чекеров в clang-tidy. Можно написать свой чекер, который все равно будут ревьюить много месяцев и КПД всего этого занятия близок к нулю. Для специфических проверок надо что-то колхозить самому.

Примерных проверок может быть много:
1️⃣ Запрет на использование typeid(x).name(), потому что он дает mangled имя, с советом использовать костыль, который отдаст demangled имя.
2️⃣ Запрет на использование старого API в новом коде во время масштабного рефакторинга.
3️⃣ Проверка, что в начале каждого файла находится копипаста лицензии проекта.

Можно придумать проверку на примере лямбд 😊 Пусть у нас есть такой код:
    int counter = 15;
const auto addEvent = [&counter](int number) {
if (counter > 0) {
// do something...
--counter;
}
};
// ... call the lambda
addEvent(1337);
В этом примере лямбда использует внешнюю переменную, которая влияет на логику.
Если переменная используется только внутри лямбды, то ее можно вкостылить прямо в capture list. Тогда вместо двух верхних строк примера будет такая строка, дающая аналогичный результат:
    auto addEvent = [counter = 15](int number) mutable {
Такой же подход работает для переменных любых типов. Если интересно, что происходит внутри лямбд, то можно почитать целую книгу про них.

Попробуем сделать тест на такие кейсы 😁 Если мы для этого используем библиотеки clang, то логика такая:
1️⃣ Получаем AST (Abstract Syntax Tree) из исходного кода.
2️⃣ Лямбды в AST это вершины LambdaExpr.
3️⃣ "Объявление переменной" в AST это вершина VarDecl.
4️⃣ "Использование переменной" (в каком-то месте) это вершина DeclRefExpr.
5️⃣ Если все "использования переменной" находятся внутри какого-то одного и того же LambdaExpr, а "объявление переменной" находится вне этого LambdaExpr, то тест должен упасть, потому что данную переменную можно всунуть в capture list лямбды.
6️⃣ Делаем рекурсивный обход AST с корня и держанием указателя на "текущую лямбду", и делаем проверку на пункт 5.

Этот тест можно сделать на 👩‍💻 C++: вкостылить новый чекер в локальной сборке clang-tidy или просто сделать программу на libclang. Но можно сделать то же самое на 👩‍💻 Python и кода будет меньше в несколько раз, и это делается быстрее - менее муторно. Python хорошо подходит для быстрого написания разных тестов.

Поддерживается libclang в Python, и после просмотра примеров можно поставить его себе:
    pip install pytest
pip install clang
pip install libclang
и сделать такой простой тест, где реализуется описанная проверка для лямбд. Можно в директории рядом сохранить тестовый файл source.cpp и проверить, что тест падает:
    python3 -m pytest test.py

Вывод:
E           Failed: These variables can be declared in lambda capture: 
E "counter" (at source.cpp:3:44), to lambda at source.cpp:4:88

libclang на Python выглядит нормально, но неприятно то, что для понятия "нода AST" не к месту придумали новый термин "курсор".
Также не хватает некоторых очевидных фичей, например получения родительской ноды: в курсорах есть пара ссылок на другие курсоры (типа родительские, двух видов), но они работают неправильно.

Кроме тестов можно писать другие тулзы, например "кодогенераторы" - которые сгенерируют какой-нибудь исходник на основе существующего кода. Про кодогенераторы можно почитать лонгрид.

Еще можно делать "исправляторы" исходников - которые берут AST, что-то туда дописывают и сохраняют в другой файл, а компилятор имеет дело с уже поправленным AST (то есть с этим другим файлом).
Please open Telegram to view this post
VIEW IN TELEGRAM
#youtube

Поваренная книга пирата: создание своего торрент-клиента 🏴‍☠️

https://youtu.be/usVMq7LDW1Y

Когда есть желание написать о чем-то прикольном, обычно я делаю это в двух форматах:
1️⃣ Статья на Хабр - кондовые лонгриды, которые можно читать, помолясь и перекрестясь.
2️⃣ Этот канал - небольшие приколы, которые быстро читаются.

Сейчас я решил попробовать новый формат - а именно формат видео на ютубе.

Первое видео получилось длиной в полчаса, оно в основном про создание своего торрент-клиента на Python и побочные вещи. Могу сказать, пока я готовил его, я посмотрел с десяток других видео про торренты на ютубе (в том числе типа дофига технические), там близко нет того уровня проникновения, как у меня.

Возможно, такой формат будет более удачным. Так можно накидать неограниченный объем информации (как на Хабре) и оставаться неформальным (как в Телеге) 😁

Заранее прошу прощения за мое чувство юмора, и дисклеймер: старайтесь не пиратить, не бухать, не накуриваться; а если очень хочется, то делайте так, чтобы никто не узнал 😡
Please open Telegram to view this post
VIEW IN TELEGRAM
C++95 pinned «#youtube Поваренная книга пирата: создание своего торрент-клиента 🏴‍☠️ https://youtu.be/usVMq7LDW1Y Когда есть желание написать о чем-то прикольном, обычно я делаю это в двух форматах: 1️⃣ Статья на Хабр - кондовые лонгриды, которые можно читать, помолясь…»
#madskillz

Как правильно выполнить любой код до и после main() 🏃

Функция int main() не является настоящей "точкой входа" в программе. До того, как зайти туда (и после него), программа выполняет много другого кода, в том числе юзерского.

Почти всегда этот код - инициализация статических объектов, которая выполняется до main(). Программа:
    std::vector<int> MakeVector() {
std::cout << "do MakeVector" << std::endl;
return {1, 2, 3};
}
std::vector<int> VECTOR = MakeVector();
int main() {
std::cout << "do main" << std::endl;
}
Выведет такое:
    do MakeVector
do main
Пример на godbolt

В этом коде можно делать что угодно (читать файлы, записывать в поток и т.д.), окружение там полностью готово. Я часто использую лямбды:
Пример на godbolt 1, godbolt 2.

😀 Есть крутой лонгрид (со схемами) про то, что происходит до и после main(): Linux x86 Program Start Up.
Но все описание там "примерное", потому что поведение зависит от компилятора и его версии 🖥
В моем случае некоторые действия находятся в другом положении схемы (увидел по gdb), и еще есть дополнительная вложенность - есть функции для отдельных .cpp-файлов, а там внутри уже вызовы для инициализации переменных.

⌨️ Для того, чтобы "вызвать функцию до main()", можно пометить ее специальным атрибутом.
А именно __attribute__((constructor)) или менее вырвиглазно [[gnu::constructor]].

Здесь слова "constructor" и "destructor" не относятся к классам в C++, это исторические названия еще до C++.

Пример на godbolt с ctor-ами и dtor-ами

Однако здесь есть типичный прикол с std::cout 😁
👍 Пример по ссылке выше, скомпилированный на x86-64 clang trunk по состоянию на 13.06.2023 работает нормально.
Более старый релиз x86-64 clang 16.0.0 дает сегфолт в кторах и будет работать только если поменять там на printf - ссылка на godbolt.
😠 Это связано с тем, что std::cout и подобные объекты инициализируются в конструкторе статического объекта std::ios_base::Init (объявление этого объекта находится в хедере <iostream>).
А компилятор данной версии так скомпилировал, что кторы выполняются ДО НЕГО и пытаются обратиться к неинициализированному объекту.
Функция printf таких спецэффектов не имеет, там просто перенаправление в нужный системный вызов (syscall) без регистрации и СМС.
Мораль думайте сами 👍

Для чего может быть полезна такая штука? Лучше описать как "вопрос-ответ"

1️⃣
Вопрос: В чем принципиальное отличие этого кода:
    [[gnu::constructor]] void foo() {
// приколы
}
от этого:
    struct Dummy {
Dummy() { /* приколы */ }
};
Dummy dummy;
или более укуренного этого:
    const auto dummy = []{
// приколы
return nullptr;
}();
Ответ: Никакого, кроме более удобной записи.

2️⃣
Вопрос: Зачем это все надо, почему нельзя этот кусок кода поместить в int main()???
Ответ: Иногда нужно выполнить кусок кода, только если мы линкуем какую-то библиотеку или подключаем хедер. Например, если в библиотеке хэширования есть расчет хитрых данных для хэшей на старте программы в [[gnu::constructor]], то не надо париться с тем, как и что вызвать в main(), можно просто линковать библиотеку и заработает.

Да точно это же и происходит для std::cout, как мы только что увидели выше!

3️⃣
Вопрос: Какие жесткие проблемы решает эта запись?
Ответ: В кторах и дторах можно указывать приоритет 😐 Чем он выше, тем позже выполнится ктор (и соответственно раньше дтор).
Пример на godbolt.
Это решает артиллерийский выстрел в ногу под названием Static Initialization Order Fiasco. Очередность выполнения инициализаций обычно зависит от порядка линковки, а с приоритетом стало можно это нормально разруливать! 😎

В остальном проблемы линковки все еще остаются легендарными - можно почитать пример особо опасной проблемы, о которой писал раньше: https://www.tgoop.com/cxx95/76
Please open Telegram to view this post
VIEW IN TELEGRAM
#theory

Замороженные корутины 🧊

Am Ende bleib ich doch alleine
Но в конце я остаюсь один
Die Zeit steht still und mir ist kalt
Время остановилось, и мне холодно

Этот пост больше теоретический, чем практический - просто эта тема сложная, чтобы даже иметь какой-то minimal viable product 😱

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

1️⃣ Разработка "тасок" по триггеру

"Таска" это такая микро-программа на Python, которая должна запускаться по какому-то триггеру и делать что-то обычно небольшое: отправка email, создание тикетов, http-запрос в сервис, и прочее 👦 Сервис выполняет эти программы на каком-нибудь свободном сервере.

Иногда таска может быть посложнее - например, создать и запустить другие таски и ждать их выполнения. В таких случаях исходная таска "замораживается" (переходит в состояние "Wait") и прекращает свою работу, а после финиширования дочерней таски возобновляет работу, снова на каком-нибудь свободном сервере 🖥

Есть проблема - система не может как бы "продолжить" код таски с той точки, где было ожидание таски. Был придуман нереальный костыль, который выглядит примерно так:
    def on_execute(self):
if not self.Context.config_stage:
self.Context.config_stage = True
param1 = ...
param2 = ...
task = sdk.CreateTask(param1, param2, ...) # создаем дочернюю таску №1
raise sdk.WaitTask(task)
if not self.Context.run_stage:
self.Context.run_stage = True
param = ...
task = sdk.CreateTask(param, ...) # создаем дочернюю таску №2
raise sdk.WaitTask(task)

Сервис сохраняет "контекст" между разными запусками таски, а метод on_execute каждый раз запускается сначала ♻️
В "контексте" можно ставить флажки, чтобы имитировать разные стадии таски, еще туда можно сохранять списки/строки/числа ✍️
Наконец, оповещение сервису о том, что текущей таске надо дождаться другую таску, делается через бросание исключения (raise), чтобы это "прорвалось" за пределы def on_execute (удобнее, чем просто return, если вызываются всякие вложенные методы) 🤔

При таком подходе надо очень аккуратно писать код и много думать, если таске нужно делать что-то немного нетривиальное: например запуск дочерних тасок в цикле, пока одна из них не завершится успешно 🙁 Попробуйте сами наколхозить такое, будет треш.

2️⃣ Микросервисы в Apphost

По ссылке открыто описано что такое Apphost - фреймворк для микросервисов (на Python или C++).
Он решает многие старые проблемы, но появляется новая проблема - теперь программа не может просто так сделать HTTP-запрос.
Нужно разбивать всю программу на микроскопические куски кода, которые принимают некие "входящие" объекты и формируют "исходящие", но не более того. Все походы во внешние сервисы делает Apphost. Эти куски кода выполняются на разных серверах.
Та часть, которая раньше делалась мгновенно, сейчас делается в 10 раз медленнее из-за настройки конфигов, жесткого обдумывания "графа" выполнения, и так далее.

Использование корутин

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

На самом деле, проще всего изучать корутины в простых языках - здесь статья про корутины в Lua. Общий подход не меняется.

Потом здесь можно почитать, как корутины реализованы в C++ (начиная с C++20) - это очень сложные статьи, их можно читать несколько недель, зато появляется максимальное понимание, как компилятор преобразовывает код.

Суть идеи, которая решит проблемы, которые я описал - замороженные корутины: программа выполняется на одном сервере, потом останавливается, ожидает возобновления, и выполняется на каком-нибудь другом сервере с той точки, где была остановлена 😎

ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ
Please open Telegram to view this post
VIEW IN TELEGRAM
#cringe

Про SJW в C++ комьюнити 🤡

Настало время обратить внимание на такую суперважную для Барнаула (Алтайский край) проблему, как борьбу за социальную справедливость в комьюнити по C++.

В начале 2022 года я отправил патч в Clang
После чего забил на него на месяц, а когда вернулся чтобы исправить его, то обнаружил что ревьюер забанен, хотя он профессионал - у него много статей, выступлений и тд.

Я написал ему на почту - что произошло и кто может отревьюить патч. Оказалось, что сообщество его "отменило" aka "закэнселило" 🤡

В 2011 году он, видимо, последовал совету Трампа ("grab them by the pussy"), после чего получил две уголовки и попал в почетный список Форбс "U.S.'s sex offender registry".

Зачем об этом знать и какое отношение это имеет к C++?
Оказывается (как он мне написал), в 2021 году группировка с includecpp.org об этом факте узнала и начала попытки "отменить" еб*ря-террориста со всего интернета, и оффлайн тоже. 🔫

По этой причине он, например, не выступал на CppCon 2021 - для этого борцуны в твиттере угрожали Гербу Саттеру, а также выгнать Microsoft из комитета по C++.

И соответственно из LLVM его также выгнали - якобы по причине нарушение "Code of Conduct".

Далее произошел небольшой прикол (это было начало 2022 года) 😁
- Я в ответ написал ему, что понимаю его - меня также "отменяют", ограничивая в правах пожрать в макдаке и оплатить онлифанс, хотя я даже не трогал маленьких девочек.
- На что он бомбанул и ответил в общих чертах, что тут как раз все правильно, SJW ни при чем, наши страны находятся в состоянии войны, тебя разъ*бут как Ирак, зато ты после этого получишь аналог Плана Маршалла 😱

От такой щедрости я почувствовал мем с пингвином. Особо не ответил ему, но надеюсь что он больше не показывает своего Маршалла местным Лолитам 😁

Однако вернемся к нашим маленьким фанатам экстремизма. Сайт includecpp.org выглядит максимально клоунским.
Достаточно посмотреть на "code of conduct" и пооткрывать твиттеры в ссылках под "the moderation and administration team includes", чтобы начать подозревать, что в западных странах есть проблемы с доступностью психотерапии для населения.

На мой взгляд, корнем проблемы является то, что комьюнити по C++ не объединено никаким бизнес-интересом - развитие языка и компиляторов не идет слишком быстро.

Из-за этого, когда толпе нечего делать, они находят всякие причины поссориться с кем-либо, и особенно крикливые объединяются в организованные группировки. В целом это оставляет плохое впечатление и желание дистанцироваться от любого рода "тусовок".
Похожие драмы происходят в Rust и прочих языках, и мне все-таки кажется что нормальный человек не будет таким заниматься.

Я пофантазировал и представил себе "Моральный кодекс C++-программиста в 2030 году", через несколько лет посмотрим насколько я попал 🚬
1️⃣ Запрет паттерна проектирования "фабрика" и слов в коде как work, job и прочее, так как это угнетает права безработных
2️⃣ Одобрямс легализации насвая в стране, истинная вера в то что это "менее вредно чем алкоголь", прослушивание учёных которые доказывают это "научными фактами"
3️⃣ Подписывание бумажки "я признаю Rust Foundation террористической организацией за вторжение в Linux Kernel и оккупацию 20% Stackoverflow"
4️⃣ Переменные наподобии номера гендера хранить в int16, потому что int8 не будет хватать
5️⃣ Не менее 28.2% C++-программистов должны составлять иностранные специалисты из беднейших регионов мира, с такой же зарплатой как у всех
6️⃣ Мультиязычность - одна часть проекта должна быть на C++, другая на Python, третья на Delphi, четвертая на JavaScript и т.д. Реклама, что такой подход делает проект более технологично богатым. Всякие утверждения, что некоторые языки не совместимы друг с другом и что моноязычный C++-проект проще разрабатывать - языковой шовинизм.
7️⃣ Из-за того, что комитет по C++ когда-то попытался поглотить комитет по C, каждый C++-программист теперь должен платить репарации C-программистам. За утверждения, что C и C++ родственные языки, имеющие общую историю и происхождение, которые совместимы в одном проекте, следует немедленный кэнселинг.
Please open Telegram to view this post
VIEW IN TELEGRAM
C++95
billy.png
#books

Обзор книги "Practical Binary Analysis" (2019 г.) 📚

(можно скачать PDF тут)

На фотографии внештатный корреспондент паблика "C++95" Билли, прочитавший данную книгу.
Его отзыв: "Oh yeah, это точно стоит three hundred bucks! ASM WE CAN!"
📞

Я давно ничего не писал из-за занятости, но сейчас смог 🫡

Эта книга посвящена разнообразному анализу бинарников, то есть исполняемых программ без соответствующего исходного кода. В ней очень много информации, больше чем в любой другой книге из #books - ее реально читать неделями.
Там есть знания, которые могут пригодиться разве что reverse engineers и vulnerable researchers, но и другим тоже может быть интересно 🤔

В 1️⃣ главе дается анатомия бинарника и как он вообще появляется на свет - как в книге про компиляцию.

Во 2️⃣ и в 3️⃣ главах описываются форматы исполняемых файлов ELF (на Linux) и PE (на Windows)

В 4️⃣ главе строится своя тулза для загрузки бинарника с помощью libbfd. В книге вообще есть упор на создание собственных анализаторов бинарников.

В крутой 5️⃣ главе решается задача из CTF по поиску "флага" в бинарнике, на примере которой применяются десятки подходов и тулз, чтобы шарить еще лучше.

В 6️⃣ главе, которая на мой взгляд является центральной, показывается фундамент анализа и дизассемблирования 🖥

Там народу являют жестокую правду - в общем случае нет какого-то единого алгоритма дизассемблирования.

Более тупые дизассемблеры (как objdump) имеют линейный алгоритм - тупо расшифровывают ассемблерные команды от начала до конца.
Так как компиляторы могут встраивать вспомогательные данные прямо в код (особенно Visual Studio), то можно например напороться на jump table. По факту это список адресов, но дизассемблер об этом не знает и нарасшифрует в этом месте ассемблерные команды 👍
Ничего не упадет, потому что в x86 "плотный" набор команд и практически любая последовательность байтов представляет собой валидный ассемблер 👍

Более умные дизассемблеры (как IDA Pro) имеют рекурсивный алгоритм - они расшифровывают как бы настоящий код и только те места, куда управление доказанно может перейти, например через jmp. Там свои сложности с неявным control flow, когда в коде есть jump table или vtable, которые неявно указывают куда передавать управление, но это решаемо.

Дизассемблеры восстанавливают функции довольно условно (в ассемблере "функции" это вообще слишком большая абстракция) - оптимизирующий компилятор может так навертеть, что код принадлежащий функции может оказаться размазанным по всему бинарнику, и разные "функции" могут переиспользовать какой-то один кусок ассемблера 👍

Чтобы определить, где находится условная "функция", дизасм ищет "сигнатуры", то есть паттерны ассемблерных инструкций, которые часто используются в начале/конце функции.
Пример сигнатуры в неоптимизированном x86 это "пролог" push ebp; mov ebp,esp и "эпилог" leave; ret
Есть и более сложные кейсы, когда делается "tail call", а оптимизированные функции могут не иметь пролога и/или эпилога, поэтому ошибок может быть от 20% и больше.

Очень сложно восстанавливать структуры данных (C/C++), дизасмы часто не пытаются этого делать ☔️

Для более простого анализа часто используется промежуточное представление (IR, Intermediate Representation) ассемблера - например, вместо исходника где могут быть сотни разновидностей инструкций x86/ARM, можно получить IR где единицы разновидностей. IR бывает несколько, например REIL.

Дизасм восстанавливает код, который делает примерно то же, что ассемблер. Обычно выглядит он все равно довольно ужасно, но дизасмы стараются 🥺 Например, по эвристикам (то есть через три костыля) определяются простые циклы (for/while), математические формулы, и прочее.

ПРОДОЛЖЕНИЕ В КОММЕНТАРИИ
Please open Telegram to view this post
VIEW IN TELEGRAM
2025/06/15 19:45:01
Back to Top
HTML Embed Code: