- Последний пост
- 5 авг.
- Последнее чтение
- 10:49
- Постов за неделю
- 0
- Всего постов
- 20
- Тип
- открытый
- Язык
- русский
- В каталоге с
- 12 авг.
- 1/24сутки в ленте
- 112
- 1/48двое суток
- 128
- 1/72трое суток
- 138
Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.
Посты
Back to Back 2026 Раньше была отличная конференция C++ Zero Cost Conf с докладами не хуже C++ Russia, а в некоторые года и лучше. Сейчас переименовали. По докладам, которые я посмотрел (запись): 🤩 Антон Полухин, «Новости со встречи международного комитета C++». Новости хорошие, ждём реализации. И/или верим в неё. 🤩 Тимофей Довгаль, «Использование статической рефлексии C++26 для генерации». Здесь Константин Владимиров @cpp_lects_rus обещал разбор, ждём и смотрим! 🤩 Раед Романов, «Проектируем механизм отмены для C++20-корутин». Описание инженерных решений. Т.к. вообще не моя тема, просто с интересом послушал о новом для себя. 🤩Алексей Горгуров, «Модули, пакетники, системы сборки, и как их друг с другом передружить». С тулингом вокруг модулей с каждым годом становится всё лучше и это не может не радовать. Хотя он всё ещё далёк от идеала. Очень далёк. 🤩 Александр Емеленко, «Трассирую и профилирую — бесплатно». Напомнило один эпизод из моей жизни — собеседование в Spectral::Technologies, откуда сам автор доклада. По сути весь доклад был посвящен снижению затрат времени "прямо сейчас" при записи отметок времени прохождения контрольных точек. Типичная история оптимизаций, всё как мы любим: было N ns на запись, стало K ns (N > K). 🤩 Константин Владимиров, Елизавета Носкова, «Что C++ программист должен знать про ABI». Интересное, как обычно. Наиболее забавным мне показалась передача lambda на стеке. На исполняемом стеке, да. В остальном надо смотреть, рекомендую. Было интересно, но раньше всё равно было лучше. Существенно лучше. #conference
NixOS on HostVDS (anywhere) Все мы любим переиспользовать свои наработки, знания и навыки везде, где это возможно. И это правильно, т.к. существенно повышает личную эффективность. Мне нравится один хостинг, поэтому решил внести туда классическим путём NixOS. То есть через nixos-anywhere. В этом мне помог товарищ, который останется анонимным. Если следовать инструкции на основе классического примера, ничего особо не получится, т.к., видимо, загрузка идёт не совсем через EFI. На этом овраги кончаются и начинается бумажная гладь. Но модификация потребуется незначительная. Этот инструмент использует disko для разметки диска. У меня получилось что-то такое: { disko.devices.disk.main = { device = "/dev/vda"; type = "disk"; content = { type = "gpt"; partitions = { boot = { name = "boot"; size = "1M"; type = "EF02"; }; root = { name = "root"; size = "100%"; content = { type = "filesystem"; format = "ext4"; mountpoint = "/"; }; }; }; }; }; } И немного редактируем конфиг из примера, чуть-чуть меняя опции grub: { modulesPath, ... }: { imports = [ (modulesPath + "/installer/scan/not-detected.nix") (modulesPath + "/profiles/qemu-guest.nix") ./hardware-configuration.nix ./disko.nix ]; boot.loader.grub = { enable = true; device = "nodev"; efiSupport = false; }; services.openssh.enable = true; users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA SIGSEGV DREC" ]; system.stateVersion = "25.11"; } Не забываем поменять ключик на свой! Никакого hardware-configuration.nix у нас нет, поэтому всё устанавливается командой: nix run "github:nix-community/nixos-anywhere" -- --flake .#hostvds --generate-hardware-config nixos-generate-config ./hardware-configuration.nix root@123.45.67.89 Конечно же, IP надо поменять на выданный провайдером. Минимальный flake выглядит типично для NixOS: { inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { nixpkgs, disko, ... }: { nixosConfigurations.hostvds = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ disko.nixosModules.disko ./configuration.nix ]; }; }; } И всё! Дальше можно работать и по стандартной схеме c nixos-rebuild. Кстати, там есть минимальные системные требования, поэтому на самом дешёвом сервере не запустить... Придётся потратить целых (!) 1.99 $/месяц! Лично мне обошлись мероприятия по отладке (при почасовой оплате) в 0.0044 $. Если будет запрос, упакую в модули и заверну во flake. #nix
Panic! At the disko Видел достаточно много, откровенно говоря, сомнительных решений для генерации образов дисков для виртуальных машин и разметки физических блочных устройств. Да чего греха таить, я тоже в своё время писал такую утилитку по молодости да по глупости. Все они пытались быть "гибкими" и "декларативными" и на входе потребляли аргументы командной строки, YAML или JSON-чик. Моё поделие было на последнем. Как водится, нашлось готовое решение данной проблемы — disko. Ещё и с описанием на широко известном в узких кругах языке Nix. Ещё и с бесшовной интеграцией в NixOS. Вот на нём мы и решим столь "важную" проблему, как разметка, допустим, частично шифрованного диска с открытой частью. Будем предполагать, что всё происходит внутри такого блока: { disko.devices.disk.vault = { ... }; | Укажем в нём тип текущего элемент (это диск) и конкретное устройство и тип таблицы разделов: type = "disk"; device = "/dev/sda"; content.type = "gpt"; К этому всему добавим раздел размером 2ГиБ с поддерживаемой любым чайником файловой системой content.partitions.plain = { size = "2G"; content = { type = "filesystem"; format = "vfat"; }; }; Всё остальное займёт LUKS-раздел с ext4: content.partitions.encrypted = { size = "100%"; content = { type = "luks"; name = "secret"; content = { type = "filesystem"; format = "ext4"; }; }; }; И ко всему этому добавим рекомендованные параметры (с огромным запасом!): content.partitions.encrypted.content.extraFormatArgs = [ "--pbkdf argon2id" "--pbkdf-memory 2097152" "--pbkdf-parallel 4" "--pbkdf-force-iterations 8" ]; Запустим: # disko --mode format ./disko.nix И происходит магия? Да, но её можно увидеть, добавив --debug. Там будет такая строка: script=/nix/store/*-disko-format Заглянув в файл там, можно понять, что всего лишь генерируется bash-скрипт подобной командой: $ nix-build /nix/store/*-disko-*/share/disko/cli.nix --no-out-link --impure --argstr mode format --arg diskoFile /path/to/disko.nix Однако, нет возможности зашить всякие бинарники по определённым смещениям на диске (вполне типовой сценарий) и отсчитывать разделы с учётом их размера, что в случае проектов с disko, а конкретно nixos-hardware (см. rockchip), частично решается довольно странным образом — хуком после разметки. К тому же нет возможности делить свободное пространство в тех или иных соотношениях, только 100%. Даже в моей скромной поделке такая функциональность была. #nix
3️⃣ good kitty, по странному стечению обстоятельств, тоже просит пароль. Кроме запутанных вычислений и не совсем нуль-терминированных строк есть в main некоторая очевидная проверка пароля: sVar2 = read(0,local_68,0x40); lVar3 = sVar2 + -1; local_bd = 1; local_bc = 0; if (0 < lVar3) { do { if (7 < local_bc) break; if (local_68[(int)local_bc] != *(char *)((long)&local_b8 + (long)(int)local_bc)) { local_bd = 0; } local_bc = local_bc + 1; } while ((int)local_bc < lVar3); } local_bd = lVar3 == 8 & local_bd; if (local_bd == 0) { puts("bad kitty!"); } else { puts("good kitty!"); } Явно видно из строки local_bd = lVar3 == 8 & local_bd;, что пароль должен быть длиной ровно 8 символов. Здесь же видно, что local_bd определяет некоторое соответствие пароля. А выше видно, что это результат сравнения local_68 и local_b8 по первым 8 байтам. Ладно уж, подловим состояние памяти ровно в момент проверки: $ gdb ./crack (gdb) b *main + 0x2a9 (gdb) r enter the right password 12345678 Breakpoint 1, 0x000055b18c8ed555 in main () (gdb) x/s $rsp+0x10 0x7fffad9e9560: "00sGo4M0passwordenter the right password\377\377\377\377\377\377\377\377\001" Извлекаем оттуда первые 8 символов и... Мы получаем вполне закономерный исход: $ ./crack enter the right password 00sGo4M0 good kitty! Думаю, вообще ни у кого не возникнет сомнений в истинности столь тривиального утверждения. #reverse #ghidra
Yet Another Writeup Выдалось время для легального реверс-инжиниринга через "взлом" crackmes, из которых было выбрано три... Нет, не по каким-либо рациональным соображениям, а только чтобы распутать цепочку good kitty => good girl => good_boy. Такой подход вполне простителен, если учесть, что речь не о профессионале, а о почти первом опыте. Но обо всех по порядку... 0⃣ Во всех решениях использовались одни и те же немодифицирующие инструменты: objdump, gdb, ghidra. С патчингом любой сможет, наша задача — найти пароль. 1️⃣ good_boy предлагает ввести пароль и код main выглядит примерно так: int local_118; short local_114; long local_10; __printf_chk(1,"Enter the password: "); __isoc99_scanf("%255s",&local_118); if ((local_118 == 0x30783468) && (local_114 == 0x72)) { puts("Good boy!"); } else { puts("Bad boy!"); } Несложно догадаться, что надо буквально ввести в local_118 напрямую и в local_114 через переполнение буфера то, с чем они сравниваются. С учётом endianness и структуры стекового фрейма в интерпретаторе Python получим: >>> ''.join([chr(code) for code in [0x68, 0x34, 0x78, 0x30, 0x72]]) 'h4x0r' В целом, даже если не учитывать ничего, из этих пяти символов вряд ли можно было догадаться получить что-то другое, учитывая некоторый контекст... Ура, я хороший мальчик: $ ./good_boy Enter the password: h4x0r Good boy! Были сомнения? 2️⃣ good girl работает с 8 пользовательскими аргументами командной строки, с которыми делается буквально следующее: iVar6 = 0; local_68[0] = 0xb; local_68[1] = 0xd; local_68[2] = 0x17; local_68[3] = -7; local_68[4] = -0x1b; local_68[5] = 0x6b; local_68[6] = -0x35; local_68[7] = 0x29; local_68[8] = 0x43; local_68[9] = -0x33; local_68[10] = 0xfd; local_68[0xb] = -0x8f; local_68[0xc] = -0x77; local_68[0xd] = 0x65; local_68[0xe] = 0x191; local_68[0xf] = -0x187; puVar4 = (undefined8 *)(param_2 + 8); do { puVar5 = puVar4 + 2; lVar1 = strtol((char *)*puVar4,(char **)0x0,10); lVar2 = strtol((char *)puVar4[1],(char **)0x0,10); iVar6 = iVar6 + local_68[(long)(int)lVar2 + (long)(int)lVar1 * 4]; puVar4 = puVar5; } while ((undefined8 *)(param_2 + 0x48) != puVar5); if (iVar6 == 0) { puts("good girl!"); } else { puts("bad girl!"); } Несложно догадаться, что аргументы разбиваются на пары (arg[n], arg[n+1]) и по ним к аккумулятору прибавляется число с индексом 4*arg[n] + arg[n-1] в массиве. Т.к. массив мал и нет дополнительных ограничений, пусть все arg[n] будут нулями. Итого надо крайне глупым путём перебрать комбинации из 4 чисел в массиве: >>> nums = [0xb, 0xd, 0x17, -0x7, -0x1b, 0x6b, -0x35, 0x29, 0x43, -0x33, 0xfd, -0x8f, -0x77, 0x65, 0x191, -0x187] >>> for n1 in nums: ... for n2 in nums: ... for n3 in nums: ... for n4 in nums: ... if (n1 + n2 + n3 + n4) == 0: ... print(0, nums.index(n1), 0, nums.index(n2), 0, nums.index(n3), 0, nums.index(n4)) Из соображений здравого смысла не привожу всех результатов, воспользуемся последним: $ ./good-girl 0 15 0 14 0 9 0 7 good girl! Это, конечно, весьма спорное утверждение, но пусть будет.
SiPEED Tang Nano 4K + Nix Есть у меня отличная детская ПЛИСина от GOWIN. Есть пара статей по работе с ним с помощью открытых инструментов, всё описано в репке. Оттуда стащим examples/blinky.v и examples/tangnano4k.cst для простоты и краткости. Также не так давно близко ознакомился с такой отличной системой как Nix. Принципиально она решает только проблему dependency hell. Поэтому просто соберём образ для платы с её помощью. Для этого возьмём из readme.md и examples/Makefile необходимые макросы (я вроде ничего из них не выкинул, хотя надо бы) и команды и соберём всё это в такой код: { stdenv, lib, nextpnr, openfpgaloader, python3Packages, yosys, }: stdenv.mkDerivation { pname = "sipeed"; version = "v0.1"; src = lib.cleanSource ./.; nativeBuildInputs = [ nextpnr openfpgaloader yosys python3Packages.apycula ]; buildPhase = '' yosys \ -D LEDS_NR=6 \ -D OSC_TYPE_OSCZ \ -D INV_BTN=0 \ -D FORCE_BRAM \ -D CPU_FREQ=27 \ -D BAUD_RATE=115200 \ -D NUM_HCLK=2 \ -D RISCV_MEM_16K \ -p "read_verilog blinky.v; synth_gowin -json blinky.json" nextpnr-himbaechel \ --json blinky.json \ --write pnrblinky.json \ --device GW1NSR-LV4CQN48PC7/I6 \ --vopt cst=tangnano4k.cst gowin_pack -d GW1NSR-LV4CQN48PC7/I6.cst -o pack.fs pnrblinky.json ''; installPhase = '' mkdir $out install pack.fs $out/ ''; } Как видим, всё "деклоративно", хотя... В итоге всё сводится к императивному, как и всё в этом мире. Но контролируемость окружения и зависимостей делает повторяемой сборку. Не воспроизводимой, а именно повторяемой. Кстати, стоит завернуть это в flake для пущей красоты. Больше принципиальных преимуществ нет. Даже рассказы про "достаточно простого советского nix build..." ничем не лучше старого доброго make (или даже запуска скрипта). Больше не пишите такую глупость в комментариях, это выигрыш -5 (минус пяти) символов. Повидав некоторое количество подходов к управлению разнородными зависимостями, могу утверждать, что данный подход далеко не худший. Даже из лучших. #nix #fpga
без подписи
без подписи
[Doc] FreeCAD Scripting Почти всё, что делается в FreeCAD, может инициироваться из Python-кода. Там далеко не худший скриптинг, который я видел в своей жизни. Поэтому сгенерируем кейкапы для моей клавиатуры... Пока только пробный образец. Файл будет в комментариях. 0️⃣Не забываем включить консоль в настройках как в доке; 1️⃣Попробуем поменять букву на кейкапе, сохранить, экспортировать, играемся; 2️⃣Меняем букву на тексте: FreeCAD.ActiveDocument.getObject("ShapeString").String="A" 3️⃣Пересчитываем весь документ: FreeCAD.ActiveDocument.recompute() 4️⃣Экспортируем тело куда надо с соответствующим именем: Mesh.export([FreeCAD.getDocument("gateron_low_template").getObject("Fusion")], u"/home/moskalets/keycaps/gateron-low-template-man-A.3mf") 5️⃣Зацикливаем по алфавиту, итоговый код: import string for sym in string.ascii_uppercase: FreeCAD.ActiveDocument.getObject("ShapeString").String=sym FreeCAD.ActiveDocument.recompute() Mesh.export([FreeCAD.getDocument("gateron_low_template").getObject("Fusion")], u"/home/moskalets/keycaps/gateron-low-template-"+sym+".3mf") #python #freecad
Оптимизирующие компиляторы. Структура и алгоритмы Не так давно собрался с силами и дочитал книгу Константина Владимирова (@cpp_lects_rus), которая не похожа ни на что из того, что я ранее пытался читать по данной тематике. Я, конечно, совсем не эксперт в данной тематике... Но как читатель (ученик) вполне могу типичный. Что мне понравилось: 1️⃣ Основная часть повествования посвящена оптимизациям, как и заявлено. И генерации машинного кода, конечно. И только 1 глава из 7 рассматривает общие вопросы тулчейна и структурную организацию компиляторов. Остальное — то, чего нам не хватало в сильно более толстых книгах; 2️⃣ Практически все оптимизации рассмотрены на SSA-представлении, что делает их максимально применимыми к современным компиляторам. Я, например, подсматривал в код LLVM для выяснения дополнительных подробностей; 3️⃣ Есть достаточно строгие для такого объёма доказательства необходимых свойств или ссылки на другие работы. Кстати, надо бы с некоторыми из них ознакомиться и преисполниться; 4️⃣ Есть приближенные решения NP-полных задач в контексте компилятора, которые... Обычно основаны на наивном решении с некоторыми улучшениями. В общем-то, для меня было максимально полезно и могу рекомендовать как учебник вместе с курсом на YouTube. Учитесь грамотно и да пребудет с вами сила! #compiler #book
Raspberry Pi 5 + Arch Linux ARM У меня есть плата и есть свободное (для неё) время. Почему бы не поставить туда рач? Тем более есть гайд. И, в общем-то, гайд такой... Костыльный. Не хотел бы я так делать. Поэтому вот другой костыльный гайд: 1️⃣ Делим диск на два раздела — 0.25-1G под загрузочные дела (обязательно помечаем как загрузочный!) и остальное по своему усмотрению (приличные раздолбаи оставляют всё под один раздел). Монтируем их в root и boot; 2️⃣ Загружаем сборку и пакет с rpi-специфичным ядром и не только. Первый распаковываем bsdtar в root, а второй засовываем куда-нибудь... Допустим, в root/root. При этом root/boot перемещаем в boot; 3️⃣ Добавляем в boot из вендорских бинарей ядро kernel_2712.img, дерево устройств bcm2712-rpi-5-b.dtb и от себя дописываем config.txt: kernel=kernel_2712.img enable_uart=1 и cmdline.txt: console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 4️⃣ Вставляем microSD в платку, включаем и логинимся через UART (root/root). Там сносим всё, что мы закастомили и ставим пакет через pacman, можно указать опцию из статьи — --overwrite "/boot/*"; 5️⃣ Перезапускаем и проверяем, что есть wifi/bt и всё нас устраиваем; 6️⃣ Настраиваем Arch и пердолимся как умеем. Тут уж как-нибудь сами. По итогу у меня вполне неплохой сервачок дома для разного рода личных неприличных целей. Возможно, на этой же плате, дополнив её MCU, построю Klipper-принтер или ещё какой девайс с ЧПУ. #linux
Немного ультранасилия Последний пункт задания Clang Internals, написанного мной в несколько функциональном стиле (смотрите строго с широкого монитора). import clang.cindex import sys for node in filter(lambda n : n.spelling == "fisting", filter(lambda n : n.kind.is_declaration(), clang.cindex.Index.create() .parse(None, sys.argv) .cursor.walk_preorder() ) ): print(f"'fisting' found on {node.location.file}:{node.location.line}:{node.location.column}") Немного переусложнил, но суть такова: 1. Парсим файл и получаем translation unit; 2. Достаём оттуда корневую ноду и по ней запускаем обход; 3. Фильтруем только объявления; 4. Фильтруем только с именем fisting; 5. Печатаем диагностику для них. Функциональщина в руках нефункциональщика — зло. Доклад окончил. #compiler #llvm
tidy fisting Следующая ступень в поиске фистинга — проверка в clang-tidy. Очень неприятно было собирать clang-tools-extra (на ноутбуке неприлично тяжело). Безотлагательно приступаем! Проверку добавляем скриптом: ./clang-tools-extra/clang-tidy/add_new_check.py misc fisting Здесь в качестве категории проверок была выбрана категория misc, которая соответствует проверкам без категории. Будет создано несколько файлов и изменена пара существующих. Нас интересует только clang-tools-extra/clang-tidy/misc/FistingCheck.cpp, где заменим вставленную проверку-placeholder. void FistingCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(namedDecl(hasName("fisting")).bind("fistingId"), this); } Мы видели это в другом посте. И напишем свою проверку и вывод диагностики. void FistingCheck::check(const MatchFinder::MatchResult &Result) { const auto *decl = Result.Nodes.getNodeAs<NamedDecl>("fistingId"); if (decl == nullptr) return; diag(decl->getLocation(), "%0 detected") << decl << FixItHint::CreateInsertion(decl->getLocation(), "fisting"); diag(decl->getLocation(), "use 'fisting' to earn 300$", DiagnosticIDs::Note); } Осталось дописать документацию, тесты... Тут уже сами справитесь. #compiler #llvm
butt plugin Следующим шагом преисполнения в познании стоит разработка плагина для clang. Почти всё взято отсюда, поэтому проще сказать об отличиях. Начинаем начинать! Для плагинов есть свой список (вероятно, так стоит переводить registry), добавим в него наш плагин. static clang::FrontendPluginRegistry::Add<FistingAction> FistingPlugin("fisting", "Find fisting for 300$ in code."); При этом FistingAction почти не отличается от оригинала, разве что наследуется от другого базового класса и должен уметь (или делать вид, что умеет) обрабатывать опции. class FistingAction final : public clang::PluginASTAction { public: bool ParseArgs(const clang::CompilerInstance &compiler, const std::vector<std::string> &arg) override { for (auto it = arg.begin(); it != arg.end(); ++it) { if (*it == "fisting") { llvm::outs() << "You needn't enable fisting manually, just send me 300$!\n"; } } return true; } }; И всё? И всё! Точнее, осталось собрать это дело в fisting.so и запустить его. clang++ example.cpp -fsyntax-only -fplugin=./fisting.so -Xclang -plugin -Xclang fisting -fplugin-arg-fisting-fisting И радуемся своим 300$! #compiler #llvm
Это мэтч Следующим шагом в задании было написание того же на ast_matchers. Для упрощения своей жизни я предварительно проверил работу в clang-query и получил запрос: match namedDecl(hasName("fisting")) Примерно то же самое делает и MatchFinder int main(int argc, const char **argv) { // ... command line processing ... clang::ast_matchers::DeclarationMatcher fistingMatcher = clang::ast_matchers::namedDecl(clang::ast_matchers::hasName("fisting")) .bind("fistingId"); FistingPrinter printer; clang::ast_matchers::MatchFinder finder; finder.addMatcher(fistingMatcher, &printer); return tool.run(clang::tooling::newFrontendActionFactory(&finder).get()) ? EXIT_FAILURE : EXIT_SUCCESS; } При этом в случае совпадения вызывается определенный пользователем callback для дальнейшей более сложной для общей реализации обработки: class FistingPrinter final : public clang::ast_matchers::MatchFinder::MatchCallback { void run(const clang::ast_matchers::MatchFinder::MatchResult &result) override { clang::ASTContext *context = result.Context; const clang::NamedDecl *decl = result.Nodes.getNodeAs<clang::NamedDecl>("fistingId"); if (decl == nullptr) return; llvm::outs() << "hint: fisting detected on " << context->getFullLoc((decl->getBeginLoc())).getLineNumber() << ":" << context->getFullLoc((decl->getBeginLoc())).getColumnNumber() << "\n"; } }; #compiler #llvm
Его темнейшество На лекции по Clang internals было задание проверки принадлежности идентификаторов к словарю (под это ещё была забавная легенда). Первым этапом была реализация с помощью Visitor и LibTooling. А я решил сделать более простую (и гачимучную) задачу. Простенькая обработка опций и запуск действия на фронтенде: int main(int argc, const char **argv) { auto expectedParser = clang::tooling::CommonOptionsParser::create( argc, argv, ....); if (!expectedParser) { llvm::errs() << expectedParser.takeError(); return EXIT_FAILURE; } clang::tooling::CommonOptionsParser &optParser = expectedParser.get(); std::string error; clang::tooling::ClangTool tool(optParser.getCompilations(), optParser.getSourcePathList()); return tool.run( clang::tooling::newFrontendActionFactory<FistingAction>().get()) ? EXIT_FAILURE : EXIT_SUCCESS; } И дальше создадим это действие, единственным отличием которого от базового будет перегруженный метод, создающий кастомный обработчик AST. class FistingAction final : public clang::ASTFrontendAction { public: std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &compiler, llvm::StringRef inFile) override { return std::make_unique<FistingConsumer>(&compiler.getASTContext()); } }; В свою очередь этот обработчик умеет только запускать visitor'а в единицу трансляции class FistingConsumer final : public clang::ASTConsumer { public: explicit FistingConsumer(clang::ASTContext *context) : visitor_(context) {} virtual void HandleTranslationUnit(clang::ASTContext &context) override { visitor_.TraverseDecl(context.getTranslationUnitDecl()); } private: FistingVisitor visitor_; }; А уже visitor, методы которого вызывают на нодах определенного рода, находя fisting, выводит пользователю подсказку, что у него есть 300$ и где они class FistingVisitor final : public clang::RecursiveASTVisitor<FistingVisitor> { public: explicit FistingVisitor(clang::ASTContext *context) : context_(context) {} bool VisitNamedDecl(clang::NamedDecl *d) { if (d->getDeclName().isIdentifier() && (d->getDeclName().getAsString() == "fisting")) { llvm::outs() << "hint: fisting detected on " << context_->getFullLoc((d->getBeginLoc())).getLineNumber() << ":" << context_->getFullLoc((d->getBeginLoc())).getColumnNumber() << "\n"; } return true; } private: clang::ASTContext *context_; }; Класс в clang/include/clang/AST/RecursiveASTVisitor.h должен сделать немного: обойти все ноды, каждую из них перебрать до самого базового класса AST, вызвать на каждой итерации этого перебора метод Visit*. И если этот метод "затенил" оригинал, то вызовется именно пользовательский. Вроде бы я разобрался... А это только первая итерация задачи. Дальше как-то сами. Да пребудет с вами fisting! #compiler #llvm
Такие себе инженерные решения Копаясь в LLVM, нашёл в clang/doc/LibTooling.rst строку в глобальном scope: static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); И это выглядит очень странно: как-то неявно связанное с... Чем-то глобальным. Так оно в целом и есть. Посмотрим на сам класс: struct extrahelp { StringRef morehelp; explicit extrahelp(StringRef help); }; extrahelp::extrahelp(StringRef Help) : morehelp(Help) { GlobalParser->MoreHelp.push_back(Help); } То есть фактически здесь сделан вызов до main с помощью конструктора глобального объекта. Но здесь мы используем какой-то другой глобальный объект... А началась ли его жизнь? И здесь ещё один трюк: static ManagedStatic<CommandLineParser> GlobalParser; А ManagedStatic — это класс с перегрузкой operator*/operator->, которые лениво создают этот объект. Забавно. #compiler #llvm
Мяу! Благодаря llvm-project/llvm/include/llvm/Support/CommandLine.h мы теперь точно знаем, как выглядит кот: struct cat { OptionCategory &Category; cat(OptionCategory &c) : Category(c) {} template <class Opt> void apply(Opt &O) const { O.addCategory(Category); } }; #compiler #llvm
Channel photo updated
Channel created