Санитайзеры и трюки компоновщика
финальная проверка микрофона.
Ответим на вопрос, когда следующий выпуск подкаста?
Хороший вопрос.
Я ждал его.
Будет понедельник.
Будем разговаривать как раз про эту прощедшую конференцию к этому моменту.
Отлично.
Я тебя слышу и то, что я слышу, мне нравится.
Удачи.
Спасибо.
Ну что ж, начнем.
Всем привет.
Сегодня я Алексей Веселовский.
Сегодня я вам расскажу про санитайзера и трюки компоновщика.
Там буквально пара трюков используется.
Мы про них сегодня и поговорим.
Побольше про компоновщик, поменьше про санитайзера.
Ну, в начале, конечно же, начнем с самого важной части доклада, а именно немножечко обо мне.
Без этого не обходится ни одно выступление.
Ну, я бы назвал эту секцию как вот немножечко, вот надо развить вас, когнитивное искажение, вот такое вот.
Чтобы вам было проще воспринимать на веру, все, что я скажу, проверять не надо.
Просто верите мне.
Ну, давайте приступим.
Ну, где я, собственно, работал?
Ну, и чем я занимался?
Ну, вот разрабатывал скаду для здоровенных газомодкомпрацеров, которые загоняют природный газ под землю, на плюсах.
Немножечко в телекоме поработал.
Там тоже.
Реалтайм туда-сюда.
В медтехе поработал.
И работаю.
У меня было и есть две работы.
Одно и уже нет, одно есть.
и немножечко самодвижшихся интеллектуальных хорвестеров, тубиш комбайнов поразрабатывал, ну и всяких разных трамваев.
И во всех этих приложениях, ну может быть, кроме ВАИПа,
и там тоже.
Очень важна корректность приложения, потому что вы иногда можете физически услышать за много километров вашу ошибку, как у вас упало приложение, и что-нибудь вместе с ним.
Санитайзеры вместе с хорошим тестированием, обычно они в продакшене не используются, используются во время тестирования, помогают поймать самые злобные, именно плюсовые лисичные ошибки.
Ну, типа там в ним памяти промахнулись, всякое такое, с многопоточкой помогают.
Вот, ну, собственно, отсюда мой интерес и начало развиваться, но
Об этом позже.
Давайте продолжу квас.
Да, я ведущий подкаста.
Вот у нас есть такой замечательный подкаст.
Подписывайтесь, ставьте лайки, выпуск в понедельник.
Докладов понарассказывал всякий, ну вот, про адрес санитайзера в 20-м году, про его потроха, про трат-санитайзер, то же самое, но в 21-м, а потом внезапно переключил синого, ну не просто так, использовал его таким образом, как ни один нормальный человек использовать его не будет, а именно для системного программирования, да ещё и без ронтайма.
Ну и как же без санитайзеров реализовал маленький кусочек санитайзера-ного.
И вот совсем недавно был доклад про лик санитайзер и, собственно, его потроха и про менеджмент памяти.
Окей, к сожалению, этот доклад пока публично не доступен.
Ну, скоро будет следить за анонсами.
Ну, можно заметить, что я немножечко помешан на санитайзерах, вот, санитайзер Bias.
В те времена про них что-то говорю, и даже когда, казалось бы, санитайзер вообще не в тему, Прого, все равно санитайзер там какой-то присутствует.
Часть санитайзеров у меня еще не покрыта, это, например, memory-санитайзер, undefine-behavior-and-detaflow-санитайзер.
Про них как когда-нибудь потом поговорю.
Но, что замечательно, что все, ну, не все, многие санитайзеры используют внутри себя интерцепторы, мне кажется, и раз приходится про них рассказывать.
И поэтому я решил просто взять и отдельный доклад про них сделать вот сегодня.
Давайте плавно переходим к интерцепторам.
Что это такое?
Давайте на примере адрес санитайзера.
У нас есть вот такое приложение простое.
Выделять немножко памяти, что-то пытается с ней сделать, потом пытается ее освободить.
Что адрес санитайзер делает?
вделяет много байт, больше, чем запросили, сохраняет методату, например, стакторист, где была локация сделана, обкладывает всё это дело RedZone'ами и возвращает указатель на пользовательскую память, сколько там попросили, столько и выдал на начало этой области.
Это всё замечательно, но вот как он вообще перехватывает этот вызов-то?
Ему же каким-то образом надо этот самый молок заместить или перехватить, а потом вызвать оригинальный.
Но вот это вот и называется интерцептор.
Хорошо, давайте сформулируем, что мы хотим вообще от интерцепторов.
Но вот кроме перехвата, кроме замещения функции нам хочется еще иметь возможность вызвать оригинал.
Потому что был бы не очень здорово переимплементировать какой-нибудь петра-этокрейт, стеркопе, вообще весь мир.
Иногда нам хочется просто еще сделать что-то, а потом вызвать реальную функцию, которую мы перехватили, чтобы лишняя работа не делалась.
Вот, ну соответственно у нас какие требования получаются, но мы хотим заместить функцию, мы хотим иметь возможность вызвать оригинал, мы не хотим чего-либо хардкодить в компилятор, потому что функции много, и хотелось бы просто runtime обойтись, и что же нам делать, ну можно обратиться к услугам компоновщика.
В этом докладе должен предбредить, что я буду говорить только про Linux и только про X8664, потому что иначе доклад был бы часов на 5.
Сегодня у нас будет вот такое дерево прокачки или лабиринт.
Мы будем идти от линкера к нашей цели справу вверху.
Давайте начнем с того, что же какими же
терминами линкера оперирует компоновщик.
Вот он про функции мало чего знает, но он знает много про символы.
И с чем у нас ассоциированы символы, как у C++ разработчиков, но обычно какими-то такими ошибками.
И ничего хорошего мы стараемся на них вообще не смотреть по возможности.
Это страшно очень.
И непонятно.
Особенно под винус, но про винус мы вообще говорить не будем.
А под линуксом еще как-то можно жить.
И давайте
Собственно, с чем линкер работает?
Он работает первую очередь с кимфайлами, с объектными файлами.
Что же такое объектный файл?
CompilationUnits компилированный вот в точка О, object он так и называется, но можно библиотеку динамическую собрать, это тоже как в смысле objectFile, тоже имеете секции даже больше внутри себя, ну и исполняемый файлик эльфовский, то же самое.
Хорошо, а давайте заглянем вот самый простой объектник и посмотрим, что там у него внутри находится.
Ну а находится там вот такой вот список хедеров для секций.
Все секции нам не интересуют, нас интересуют, собственно, вот самое главное, это сим-тап, таблицы символов, к которым мы будем рассматривать далее.
Еще есть полезная секция текст, там у нас, собственно, исполняемый код находится и есть секция дейта, там у нас находится
глобальные перемены инициализированы.
Но давайте на примере, вот у нас есть какой-нибудь такой простенький сишник, две переменные, две функции статические и глобальные.
Ну и давайте посмотрим, что там у нас внутри.
Внутри у нас вот действительно такая есть таблица символов.
Что же такое у нас символы?
Ну у каждого символа вот самое главное есть имя и есть значение.
Взначение прям подозрительно напоминает какие-нибудь адреса.
На самом деле так и есть, но с небольшим созвездочкой.
Возьмем, например, вот эти два символа, мы видим, что у них вылету одинаковые.
Вот, казалось бы.
А все почему?
Потому что тут еще есть номер секции, то есть это значение, это смещение относительно начала даденной секции.
Ну что логично, вот у нас, например, функция, мы видим, что там вот единичка, и это означает, что это секция текст, ну логично, исполняемый код у нас в секции.
текст.
А вот глобально переменная, интеллированная.
Нас в секции дейта.
Ну, всё нормально.
Давайте теперь посмотрим вот на эти два символа.
Ну, и то и другое функция.
Всё хорошо.
Но у них разный binding.
Если помним, что у нас одна статическая.
С точки зрения линкера это будет local binding.
Есть глобальная функция, это global binding.
Local binding означает, что это говорит линкеру, что
Ты, пожалуйста, ищи вот этот символ.
Его рассматривает Ольга, если ты ищешь локально.
Вот в этом конкретном объектнике.
Когда будешь сливать много объектников в один, на него не смотри, пожалуйста.
А глобал, ну, смотри всегда.
Вот.
Ну, и видим, что у них все-таки разные вели, то есть разные смещения в секции текста.
Хорошо, здорово.
Мы вот немножечко разобрались, что такое символы.
Поехали дальше.
Ну что мы можем вообще в принципе сделать с этой таблицей?
Ну вот самое простое, что мы можем сделать, это взять и добавить еще одну строчку, просто копировать 6-ую строчку и просто ее с другим именем добавить.
Ну почему бы нет, но таблица есть таблица.
Давайте посмотрим, можем ли мы такое сделать в коде.
Ну, на самом деле, в C++ мы такого сделать не можем, а вот в GCT в кланге можем.
Вот взяли и вот атрибут alias объявили и замечательно у нас появляется alias, то есть псевдоним для global function.
Вот, скомпилировали, посмотрели, действительно, появилось это вот предыдущую таблицу.
Я вот седьмую строчку сам добавил, а здесь нам компилятор добавил.
Получилось то же самое.
Очень здорово.
Таким образом, мы теперь знаем, что такое алиасы, то бишь, псевдонимы для символов.
Замечу, что все символы они равнозначны.
То есть вот здесь
Шестая и седьмая строчка абсолютно одинаковые с точки зрения линкера.
Здесь нет кого это главный и на нее там типа кивданим нет с точки зрения линкера они абсолютно одинаковые.
Окей, что еще можем сделать?
Ну вот мы имели для одного значения два имени, а вот наоборот можно.
два разных значения одно имя.
Но компилятор нам такого не позволит, к сожалению, они сделают даже со всеми расширизмами GT или кланга.
Но, наверное, это и правильно.
Но если у нас много Compilation Units или много объектников разных, то почему бы и нет.
Ну, давайте попробуем это сделать.
Возьмём один объектник такой, скомпилируем, второй объектник скомпилируем, а потом как слинкуем и вот как обычно, в общем.
Получим multiple definitions.
Замечу, это не компилятор ругается, это именно компоновщик ругается.
Давайте это как-нибудь пофиксим.
Мы можем посмотреть, что компоновщик говорит, что у тебя два объектника с одним тем же символом, с одним тем же именем.
Я не знаю, какой выбрать.
Ну, давайте мы поможем выбрать, вот, возьмем и атрибут WIC вот такой вот вот к нему в одну из них.
И что?
И тогда у нас вот получается, что вот там вот бандинг, он становится WIC, не local, не global, а WIC.
Таким образом, мы говорим компоновщику, что
Ты можешь использовать конечно этот, но если есть какой-нибудь другой, используй какой-нибудь другой.
Взяли, скомпилировали, слимковали, все замечательно.
Видим, что действительно в объектник, который у нас получается, то есть вы исполняем файл, и у нас ровно один символ.
Собственно, глобал-символ, стран-глобал-символ.
А вот если мы возьмем и уберем глобал-фанк из main.c, вот как вы думаете, у нас что-нибудь изменится?
Ну да, правильно.
Вот взяли, скомпилировали, у нас действительно Вик Символ взялся, причем у него будет маркер, он как был, Вик так и остался.
То есть, Линкер, он довольно такой, тупое создание, он просто берет и копирует.
То есть, он там не преобразует, ну вот Велли преобразовалась, конечно, а все остальное, как был, так и осталось.
ok, правила простой, то есть вы можете иметь только один сильный символ и сколько угодно слабых и сильные всегда заменяют слабых
Если у вас, например, сильного нет, а есть много слабых, но линкер возьмет какой-то.
Какой он вам не скажет.
Отсюда интересные правила в C, C++ типа ODR Violation, вот это вот все.
Хорошо, мы разобрались со слабыми сильными символами, теперь давайте это как-нибудь скомбинируем.
Берем и слабый alias создадим на какой-нибудь символ.
Так можно, так можно.
Взяли, создали.
А вот, как думаете, можно ли сильный alias на слабую функцию создать?
Но на самом деле можно.
Эти два механизма вообще никак за друг другом не связаны.
Можно комбинировать как угодно.
Без проблем.
Еще раз напомню, что символы все равнозначны.
То есть тут нету главного.
У нас, конечно, именно компилятор си немножечко может вводить заблуждение, потому что у нас там глобал-фанк, это типа как функция, глобал-фанк-2 это какая-то странная штука с названием alias.
Но на самом деле с точки зрения линкера они абсолютно однозначны.
Ну то есть равнозначны.
Хорошо, разобрались.
Давайте теперь как-нибудь заиспользуем, мы уже столько теорий нагнали, давайте что-нибудь полезное.
Вот в адрес Sanitizer есть публичный API, и там полно полезных функций.
В частности, есть такая функция SanitizerPrintMemoryProfile, который позволяет прям посмотреть ваше текущее состояние памяти.
Он берет и вот прямо вот вам может выдать, сколько у вас чуво лоцировано и прям самые частые локации сверху.
Причем он не сэмплирующий, он точный.
Классная штука.
Единственное недостаток, что работает только, когда адрес Синтезер Антайм доступен.
Мы хотим куда-нибудь в середину нашей программы воткнуть этот вызов.
И если у нас адрес Синтезер Антайм есть, тогда мы хотим, чтобы нам выдал вот этот профайл.
Если нет, тогда, чтобы ничего не выдавал.
Да, conditional compilation мы не хотим, потому что зачем перекомпилировать каждый раз здоровенное приложение несколько часов.
Давайте его вызовем, так она замечательно компилируется, а вот так она замечательно не линкуется, потому что мы без адрес-санитайзера попробовали собрать, и у нас ничего и нет, он говорит, нет такой функции.
Давайте как-нибудь это исправим, я не знаю, добавим что ли эту функцию сами пустую, но тогда все хорошо, но только с адрес-санитайзером у нас получается multiple definitions.
Ну что, мы уже изучили, что есть слабые символы, поэтому берём и добавляем, говорим, что это слабое.
И что у нас компилируется, линкуется и так и всяк.
С адресанитайзером, если запустить, то у нас замечательно выводится, собственно, профиль памяти.
Работает.
У нас успешный успех наступил.
Здорово, классно.
Наконец-то наши знания нам за чем-то пригодились.
И вот мы наконец что-то полезное сделали.
Давайте теперь что с GCC тоже самое сделаем, тот же самый код.
Берем, собираем, запускаем.
И ничего не происходит.
Почему?
Потому что GCT, по умолчанию, использует динамический runtime адрес Unitizer, а кланк использует статический.
То есть, до того мы говорили исключительно про статическую компоновку, а сейчас поговорим про динамическую.
Она, видимо, как сильно отличается.
Да, оно сильно отличается, поэтому мы идем теперь по нижнему пути и идем через динамическую компоновку.
Будем сейчас про нее всякое узнавать.
Там, к счастью, довольно просто все.
Похоже, что динамический компоновчик намного более ленивый, нежели статический, но потому что динамическому приходится работать прямо во время работы вашего приложения.
А статический там, когда-то работает Dota Go, и как там вашу приложение потом работает, его и особо не волнует.
Поэтому статический линкер может проверять все символы, а вот динамический просто хватает перпопавшихся.
И значит, что он наш признак VIG, скорее всего, просто игнорирует.
Хорошо, давайте посмотрим, как ищутся символы.
Вот у нас есть такой замечательный XS-ник, который зависит от трех библиотек ABC, а библиотека A еще и а библиотеке C. Замечу, динамические библиотеки.
Давайте мы вызываем какую-нибудь функцию и как и линкер будет ее динамический компоновщик, будет ее искать.
Вначале он в экзешнике посмотрит, потом в своей первой зависимости посмотрит, потом в своей второй зависимости посмотрит, потом в своей третьей зависимости посмотрит.
Что логично.
Потом бы, если бы у нас в С бы не обошли, тогда бы он начал еще смотреть в первой зависимости, своей первой зависимости.
А зависит от С. В С бы он тоже бы пришел.
Давайте немножко другой граф зависимости нарисуем.
Вот у нас есть, допустим, вот экзешник.
Зависит от i и от c, а а зависит еще от b. Вот он в начале, понятное дело, у себя проверит у экзешники, но у экзешники проверит потом свои первые зависимости, а потом как бы думать в b или в c пойдет.
Да, правильно, пойдет в С, а потом уже пойдет в Б. Ну, все просто в принципе.
Ну, давайте как-нибудь это на примере посмотрим, что ли.
Вот у нас здесь такое приложение замечательное.
Делает молок, делает утечку памяти, просто идеальная штука.
Взяли, скомпилировали с адрес-санитайзером и видим, что у нас, ну вот, у нас адрес-санитайзер в виде первой зависимости.
На WDSO не смотрите, это его не существует в реальности, как файла.
Окей.
Давайте посмотрим, как на самом деле ещё ищутся функции.
Вот этот самый молок мы берём.
Можно воспользоваться LD Debug, такая замечательная переменная окружения.
Можно сказать, есть символы, и она будет показывать, как лукап символов происходит во время работ приложения.
Видим, что у нас вначале в A ищутся в самом экзешнике, а потом уже в Либасане ищутся.
Замечательно.
Вообще лддб штука очень полезная, там много всяких разных опций.
Можно у кота спросить, что там, и он ответит.
Вот сколько всего есть.
А вот как бы думать, почему у нас кот разговаривает?
Но, на самом деле, разговаривать не код, разговаривать LDSO — это динамический библиотек, где, собственно, и живет динамический компоновщик.
И вы, наверное, видели еще вот такие вот переменные окружения — это ровно для него.
Поскольку это библиотека, от которой зависит ваш ХЗ-шник, то ваша само приложение читает вот эти вот переменные окружения.
Окей, хорошо, мы вот много всего узнали про порядок поиска символов, про LDDBug и LDSO узнали.
Давайте же пофиксим вот наше предыдущее приложение, оно у нас, напомню, было вот так, проблему видите.
Ну, правильно.
У нас просто компоновщик всегда будет находить ровно вот эту реализацию.
У нас же в XZ-шнике лежит эта функция.
В XZ-шнике все больше ничего искать не надо.
На самом деле очень простое решение этой проблемы мы просто берем эту функцию куда-нибудь вытащим в отдельную билетику динамическую.
Вот так вот вытащили.
Вот такой у нас столмайн.
Майн вообще ничего не знает про эту библиотеку.
Потом вот так вот собрали, слинковали.
И вот мы запускаем, все нормально.
То есть это нормально, но без динамической, без адреса не тайдера.
То есть как и положено, все работает, ничего не падает.
Теперь можем посмотреть зависимость, собственно, по динамическим библиотекам, но видим, что у нас вот, да, есть наша библиотека здесь.
Мы можем посмотреть, как происходит лукап символа.
Видим, что находится в липфейка санпрофайл.
Как и ожидалось.
Теперь давайте проверим с адрес с санитайдером.
На самом деле, я здесь компилирую с этой опцией.
Можно было на самом деле run-temp отсутствовать через LDP Reload, просто предыдущей версии нашего XS-шника.
Было бы то же самое абсолютно.
Ну вот, запустили, увидим, вот он наш профиль, всё как надо.
То есть, как ожидалось работает.
И если посмотреть зависимости, видим, что Асан у нас спереди, а сзади у нас наш фейковый, асан-профайл.
Ну, собственно, как ожидалось.
У санитайзеров есть нюанс, они всегда стараются быть самой первой вашей зависимостью.
Именно так они перехватывают все вызовы на самом деле.
Ну, как мы теперь знаем.
Счастье.
Можем посмотреть, как символы ищутся, но логично, что вначале вау, потом в Либасан, в нашей библиотеке в это уже не ищутся, потому что уже все нашли.
Можно посмотреть, как лука происходит в двух разных случаях.
Слева у нас с адрес санитайзер runtime, справа без адрес санитайзер runtime.
Здорово!
Мы пофиксили наше приложение, нам что-то не хватает.
Мы не знаем, как вызывать оригинальную функцию.
которая была.
Но это потом давайте подведем в начале итоги по замещению всяких символов.
То есть как приоритетировать.
Вот хотим мы, например, создать дефолтную имплементацию, которую мы даем возможность пользователю, ну или какой-нибудь в биотеке, заместить.
Что мы можем сделать в случае статической компоновки, мы можем объявить эту функцию просто как вик-символ.
Без проблем.
Для динамической компоновки надо просто объявить этот символ в динамической библиотеке и сделать всё что-то назовись, чтобы она загружалась после той библиотеки, ну или экзавишника, ну экзавишника первой, где могут её переопределить.
Если мы сами хотим зарядифания символ, но все очень просто, засовываем свой собственный символ либо в экзошник, либо в какой-нибудь динамическую библиотеку, которая прям первые грузится, но лд при лоу для этого очень хорошо подходит.
С этим разобрались мы теперь точно знаем, как у нас замещаются функции, нам не хватает знать, как нам оригинальную функцию вызывать.
Которая была замещена.
Ну вот, что мы сделаем?
Мы просто возьмем ADL-хима, будем использовать.
Вот маленький пример.
Здесь мы типа сделаем свой условный перехвачек, приопределяем молок, поскольку это прямо-таки в майни будет.
Но, точнее, это будет динамические библиотеки.
Он пишет что-нибудь в консоль и потом вызывает оригинальный молок.
Больше ничего не делает.
Динтим RTDL Next просто возвращает указатель на следующий символ с таким именем.
Не текущий, а следующий.
Вот тут у нас такой замечательный мейн, где мы просто принт в молок 10 делаем.
больше ничего собрали в динамическую библиотеку собрали наш приложение с этой динамической библиотекой вызываем ну все собственно все как вы ожидалось мало позывается два раза потому что принт внутри себя тоже динамический память выделяет кстати поэтому он медленный а окей давайте
Да, у нас, кажется, всё готово.
Пойдём что ли, посмотрим, как интерсепторы работают в настоящих санитайзерах.
Но если мы пойдём поищем, например, всё, что связано с малоком, какие символы есть в Асановском рантаме, мы там внезапно найдём не один символ, а четыре.
Почему так сложно?
Ну так сложно, потому что санитайзеры, они стараются быть довольно гибкими, и если они сами что-нибудь перекватывают, они не хотят лишать других удовольствий и тоже перехватить эту самую функцию, например, малок.
То есть мы можем делать такую цепочку из перехватчиков, и они друг другу мешаться не будут.
Поэтому здесь предусмотрены некоторые точки расширения.
Но давайте вначале рассмотрим.
Вы, наверное, заметили, что на самом деле здесь функции две.
Функция номер раз имеет два символа, потому что них вы или одинаковый, поэтому это одна и та же функция.
Это, собственно, просто тромплин, который просто-напросто вызывает символ под названием интерсептор-малок с двумя почерками в начале.
Всего несколько сомберных строчек кода там внутри.
строчек кода ансамверных инструкций.
И вторая функция, это уже настоящее реализация молока адреса не тайзера.
Опять два символа, один слабый и другой сильный.
И там была, в первой функции тоже было два символа, один слабый и другой сильный.
И здесь на самом деле правило простое.
Все слабые символы, это точки расширения потенциальные.
То есть, как это работает нормально?
Ну, когда настолько адрес санитайзер есть, никаких других интерсепторов нет.
Ну, вот у нас пользовательский код вызывает Малок.
Малок — это на самом деле трамплин, который вызывает интерсептор Малок с двумя подчерками.
Ну, а это уже, собственно, наша замечательная реализация Малока в адрес санитайзера.
Ну, все просто.
А вот если бы взяли
Ну да, здесь все слабые символы – это точки расширения.
И в обоих случаях, если мы хотим откнуть свой интерсептор дополнительный, то мы должны перепределить слабый символ.
И в своей имплементации, например, молока, мы должны вызвать соответствующий сильный символ.
То есть если мы перепределяем молок, мы вызываем интерцептор Trompoline-молок.
Если мы перепределяем септор-молок с двумя почерками, то мы там должны вызвать интерцептор-молок с тремя почерками.
Все довольно просто.
И если мы пределили вообще все, то у нас есть в итоге три интерцептора.
Один самый правый с тремя почерками — это интерцептор, результирующий адрес санитайзера, и два пользовательских молок — интерцептор-молок.
Это все полезно.
Почему?
Потому что
Почему это полезно?
Потому что мы можем, например, собирать статистику какую-нибудь, мы можем это делать с адрес-санитайзером.
Например, в Малоке мы считаем, сколько раз у нас Малок вызвали.
Какой-нибудь средним вот интерсептор Малок может там стактрейс куда-нибудь для профильировки складывать.
А последний интерсептор Малок это там, например, адрес-санитайзер, собственно.
Окей, вот мы, кажется, дошли до конца, но у меня есть еще немножко бонуса.
этот бонус, про который я вам только анонс скажу, а как работать не скажу.
Время заканчивается, и меня скоро начнут выгонять.
На самом деле, что мы можем сделать, имея вот эти знания, плюс еще немножко других?
Мы можем делать библиотеку, которая нам будет рисовать графутичек памяти, который нам зарепортовал ликсонитайзер.
Вы наверное видели, ликсонитайзер иногда какую-то фигню пишет, какие-то индиректлики, как оно друг с другом согласуется вообще непонятно.
Иногда у нас утекшие объекты могут друг на другу ссылаться.
Это бывает полезно, чтобы понять, как это все там утикает, что за кластера там у нас утикают прям целых объектов и так далее.
Хотелось бы иметь возможность на это все посмотреть для любого приложения, у которого было собрано безлик санитайзер, без адрес санитайзера, и которого вообще ни к чему этому не было подготовлено.
Что мы можем сделать?
Например, вот такое есть замечательное приложение.
Создаёт две ноды, которые ссылают друг на друга и всё это радостно утекает.
Замечательное модельное приложение.
И мы можем это собрать без всяких опций и вот такую магию сделать.
Подсунуть мою секретную библиотеку.
подсунуть туда runtime через ld.preload ликсонитайзера как шаредную библиотеку и, собственно, вызвать out.
И потом у нас сгенерируется .file, это для графиза.
Мы можем сгенерировать .png, у нас будет вот такой граф.
Можем прям картинки смотреть.
Но про это я расскажу когда-нибудь на следующем докладе.
Ну, и того, суммируя, я вас призываю изучать ваши тулы, даже если вы их давно пользуетесь, наверняка они могут что-нибудь такое, о чем вы не знали.
Наверняка они очень интересно реализованы.
Погружайтесь глубже, поближе к системе, подальше от прикладного.
Используйте ваши тулы и ваши знания как-нибудь необычно.
по креативне, по крайней мере это весело.
Изучайте, учитесь и делайте доклады.
Всё, всем спасибо.
Здесь вот немножко полезная информация была.
Спасибо большое.
Отличный доклад.
У нас появились вопросы из чата.
Так, вопрос первый.
Перепределять молок для дополнительной функциональности придется в самом executable, чтобы этот символ нашелся до санитайзерного символа.
Но можно воткнуться ровно так, как я описал в самой последней секции.
Там как раз написано, что можно перепределить молок либо в XZ-шнике, либо можно попробовать встать перед санитайзером.
Через LDP-Low, например.
Но в XZ-шнике лучше.
Так, следующий вопрос по интерцептор.
Почему осуществляется перехват, если санитазер определяет молок с атрибутом ВИК?
Исходный молок же будет глобал?
Исходный молок?
А, это же как динамический компоновщик работает.
Ему абсолютно плевать на ВИК, это Элистронг.
Он не разбирается в этих тонких материях.
Он просто хватает, вот он нашёл какой-то символ.
Он его схватил.
Он дальше до Липси уже не пойдёт.
Он уже его нашёл.
Пусть он даже убит.
А со статическим мы не можем переопределить молоку.
А вот там вот вот есть... Если молоку... Вот первый вопрос к зрителям от меня.
Это вам надо суги подумать.
Понятно.
Через час.
Да, она всё через одно место.
Так, вопрос.
Какой самый прикольный бак удалось найти с помощью санитайзеров на практике?
Я даже и не знаю.
У меня сейчас в голову ничего не приходит.
Наверное, у нас был замечательный бак, когда у нас по-моему по ссылке передавал сей интеджер и что-то с ним было не то.
Но я уже не помню подробностей.
Понятно.
Вопрос, почему граф в секретной библиотеке ссылается на символы и не пытается их зарезовывать, показывая адреса.
А там ровно адреса и показывается.
Я могу отмотать?
Я могу отмотать?
Да.
Вот так.
Тут ровно адреса и есть.
Это не символы, нифига.
Вот.
Так что...
Библиотека настолько секретная, что она даже ещё не доработанная, на самом деле есть каждому адресу, можно ещё и стактрайс будет прилипить и так далее, какой-нибудь description.
А так вот это реально адреса двух объектов, они друг на друг ссылаются.
Всё нормально.
Давайте верну народину вопрос к зрителям.
У нас есть вопрос, как вызвать оригинальную функцию при переопределении, используя статическую линковку.
А здесь вот есть вопрос.
Я вообще всё предусмотрел.
Во-первых, вам надо будет постараться оригинальную функцию.
Ну, если у вас два строна символа, то вам линкер просто так не даст.
А если у вас перепределили WIC-символ, то простого ответа здесь нет.
Потому что в результирующем XF-шнике, например, у вас уже не будет вик-символа, у вас будет Ольг-стронг.
Я еще пользуюсь случаем, у вас медленные вопросы, задав вопросы от себя.
Статический, динамический линковщик по умолчанию это важно, а для... Мы слушали про два компилятора, а для Microsoft-скоинтлска компилятора, какие используются по умолчанию?
Во-первых, здесь провиндунничего не было.
Во-вторых, насколько я помню, для винды там все-таки динамическая компоновка, но мир винды и санитайзера это вообще отдельный мир.
Там для интерцепторов вообще может на литу патчется код у вас.
Там все немножко интереснее.
и страшнее, и глючнее, ну в общем.
Так, еще один вопрос приехал.
Будет ли работать замена VIG через LD-Preload, если файл скомпилирован статически?
VIG через LD-Preload?
Нет, не будет, потому что в любом случае мы ищем первую очередь в X-ишнике.
Как бы все.
Хорошо.
Так, вопросы еще подъезжают.
Но у нас ещё есть вопросы на экране.
Да, есть.
Какой из них самый сложный?
Они все прикольные.
Самый прикольный вопрос на самом деле для плюсовига, который, кстати, важно знать, вот последний.
Из зала, наверное, его не видно, но поэтому он самый важный.
То есть, как C++ под капотом реализует вот это вот, но мы можем же в одном хэдере взять и прям функцию реализовать.
Можем, можем.
У нас будет multiple definitions, когда линкер будет работать.
Не будет, а как это работает?
Или, например, у нас одна этажная инфенциация шаблон, может быть 100-500 раз встречаться в одной единицкомпиляции.
Или в несколько единицкомпиляций при линковке это как-то линкуется.
И тоже снова никаких конфликтов.
Как это?
А, нет.
Мэнглинг здесь не нужен.
На самом деле, я вам могу порекомендовать доклад, который именно про этот.
Я сегодня говорил больше про интерцепторы и мы конкретную задачу решали, но есть доклад англоязычный, замечательный, которые расписывают именно линкер, символы, со ссылками на C++ и некоторыми нюансами.
Там есть пара ошибок в докладе?
Но это нормально.
У меня наверняка тоже.
Мы не задаем вопросы.
У нас есть еще вопросы.
Доехали.
Как вообще возникла идея санитайзера?
Это было связано с тем, что не удавалось найти бак в разрабатываемой программе.
А, ну я же синтезер это придумал, это кости серебряному вопрос, но он в гугле работал, и я позориваюсь у них багов много.
Звучит как укол.
Это не укол, это комплимент.
Мало багов в приложении, которые никогда не используются.
Много багов.
В любом приложении, которое используется часто.
Хорошо.
Будет ли динамическая компиляция ругаться как статическая надубликата, если в разных инклюдах объявлена одна и та же стронг-функция или тупо возьмёт первую?
Я не знаю, что такое динамическая компиляция, это что-то из мира джавы.
Наверное, имеется в виду динамическая линковка.
Но динамическая линковка, как мы видим, не ругается, она просто хватает первой символа и всё.
При определении функции, если вызвать ее же, к примеру, принт из молок, мы уйдем в бесконечную рекурсию?
Ну, бесконечно не будет, будет так и верфловно, конечно, можем.
Отлично.
Как именно санитайзер старается всегда быть первым при динамической линковке?
Я бы сказал, через личные связи с компилятором или компоновщиком,
Там, когда вы говорите Fsanitize Address, например, то GTT или Clang знает, как
пояснить за адрес Sanitizer компоновщику.
И, собственно, там помещают... Тут есть, кстати, вопрос такой.
Да, вот, где у нас находятся динамические зависимости у каждого объектника.
И вот в эту таблицу зависимости он помещает просто первым.
Вот и всё.
Этот адрес Sanitizer runtime.
Так что там всё через своячество абсолютно.
Но можно попытаться самому и свою что-нибудь воткнуть, это вполне возможно, просто придется больше команду длиннее писать, скажем так, для компиляции.
Окей, у нас продолжают эти вопросы.
Если санитайзер в несколько, какой будет первым?
Как выстраивается порядок?
Мало, какие санитайзеры друг с другом совместимы.
У нас, например, есть адрес санитайзера и лик санитайзер, но они могут существовать вместе, но они довольно смешным образом существуют.
А именно адрес санитайзер в runtime может включаться лик санитайзер.
Вот, но обычно вы, например, не можете включить какой-нибудь одновременно трет-санитайзера и адрес-санитайзера, это просто не будет работать.
Во время компиляции прям компилятор расскажет, не, я так не хочу.
Окей, вопрос.
Получается, что стронг функцию при статической линковке вообще нельзя переопределить, засадить или можно атрибутом позже сделать ее VIG.
отрибутом позже, но вы можете объект не конечно подредактировать.
Нет, я подозреваю, что тут имеется в виду, что у меня есть executable, я в нем как-то делаю заголовку функции, говорить, вон ту функцию из той бибриотеки, сделай вик, а используя вместо неё моё.
Если у тебя уже есть executable, то там всего один символ уже остался.
Последний, ты его можешь сделать вик, конечно, но это не на что уже не повлияет.
Понятно.
Мне кажется, что тут вопросы пошли в втором кругу.
При динамической линковке учитывается... Ай.
При динамической линковке учитывается вик, когда оба символа в одной шерит-лайбрели.
Ваши шаридо-лайбрури считаете, что это экзешник.
Такой независимый.
Кстати, можно из экзешники делать шаридо-лайбрури и при желании наоборот.
И с помощью небольшой магии.
Эльфийской магии.
Эльф, вот там есть магия.
Поэтому там точно такие же правила, как в экзешнике.
Почему в докладе не упомянут PLT или God Hook?
потому что иначе доклад долго бы длился.
Я был средоточен именно на том, как адрес санитайзера реализуют интерсептора.
Вопрос, для чего нужен трамплин.
Нельзя ли сразу вызвать нужную функцию?
Какую нужную?
Кто из них нужную?
Никто не знает.
Потому что фича в том, что все дополнительные интерсепторы, которые определяют пользователь, они друг по другу тоже ничего не знают.
У нас еще здесь есть некоторое количество вопросов, но думаю, на оставшиеся вопросы мы ответим в чате.
Был Алексей Веселовский.
Спасибо.