Привет. Я хочу поговорить о своей курсовой работе или о том, к чему приводит любопытство.
Давно от нечего делать пишу программы для Symbian. И время от времени при сборке сталкивался со странностями.
Все указывало на утилиту elf2e32. Его задача — преобразовать входной бинарный файл формата elf в другой, специфичный для Symbian — образ e32. Давно меня терзало любопытство — как вообще работает эта утилита и почему она иногда глючит? Чуть позже меня начал мучить другой вопрос - тема курсовой работы =) Я решил совместить приятное с полезным и скачал его исходники.
И понеслось.
Первый коммит не произойдет Во-вторых, мы включаем нестандартные расширения gcc, добавляем недостающие классы, функции и константы из исходного кода.
Предмет радостно собирается и падает. Прогресс однако.
Запускаем под отладчиком - отладчик заносится в класс, который только инициализирует другой, который инициализирует следующий.
Ура! Настоящая особенность! Пойдем.
Упс.
Где мы?!!! Остановите отладчик! Врач хирург! Скальпель! Алкоголь! Огурец! Классы - это приложения в топке! Вы указываете nullptr вместо NULL! У нас есть С++14! Ух ты, какой ужасный конструктор, который инициализирует все нулями! И снова, и снова, и снова — но здесь C++14 требует инициализации классов по умолчанию! Как все сейчас лаконично.
Хорошо.
Исправляем как можно больше сразу.
Я наконец-то разобрался, почему отладчик скачет по исходному коду как влитой — авторы ударились головой об абстракцию, выращивая наследование на 80 уровне от класса UseCaseBase :) Потом, видимо, конструктор классов статических экземпляров для Message & Классы ParameterManager исчезли из поля зрения.
Синглтон Майерса? Нет, мы не слышали.
F печь абстракция! Да здравствует революция!!! Вива ПОД!!! Ух ты! Как интересно выросло это дерево.
Основную работу выполняет функция BuildAll().
Если указаны все параметры, функция собирает библиотеку импорта, файл, определяющий имена функций и переменных и порядок, в котором они доступны в библиотеке импорта, и сам двоичный файл.
Все потомки UseCaseBase изменили свой алгоритм посредством перегрузки.
Иногда мы готовим в потомках вспомогательные данные, но чаще всего просто отключаем создание некоторых файлов.
Например, не указано имя файла для сборки чего-либо — создается новый класс.
Идиоты.
При необходимости достаточно прервать выполнение такой функции коллектора.
Мои действия понять нетрудно Б-) Продолжаем удалять пустые классы, заменяем NULL на nullptr_t, заменяем итераторы диапазонов на for(auto x: *).
Исправляем ошибки обработки параметров командной строки.
Вам необходимо проверить код статическим анализатором.
С чего начать? Хм, под XP выбор невелик — cppcheck, и codeblock его поддерживает «из коробки».
Ух ты, какой улов! Есть даже функция удаления для char[]! Блин, я знаю куда делось полгига свободной оперативки =) Итак добавляем файлы, сгенерированные из elf-файла libcrypto.dll и сам файл, описывающий параметры командной строки для их создания.
Упс.
CPPCcheck ошибся.
Должно быть (a||b).
Попробую собрать в Visual Studio 15 и потыкать палкой в Win10. Давайте установим его на виртуальную машину.
Готово, скачайте и запустите установщик онлайн-студии.
Что? Не хочет сохранять загрузку в общую папку с хостером?! Да, задохнись! Скачивайте там, где вас учили.
А теперь переносим скачанный файл в папку и запускаем установку.
Что? Опять игнорирует общую папку??! Да, задохнись! Стань там, где тебя учили.
В принципе десятка вполне неплохо работает на одном ядре и 3-х гигах кадра.
Студия в студию! Я думал об этом, но недолго.
Открываем мой проект в студии и он опять ругается на папку.
Как долго это возможно? Да, захлебнуться.
Давайте собирать, ругается на нестандартное расширение STL hash_set. Удаленный.
Удаленный??? Включите мозг =) Ух ты, какой причудливый код:
Давайте немного подумаем.int ElfFileSupplied::UnWantedSymbolp(const char * aSymbol) { static hash_set<const char*, hash<const char*>, eqstr> aSymbolSet; int symbollistsize=sizeof(Unwantedruntimesymbols)/sizeof(Unwantedruntimesymbols[0]); static bool FLAG=false; while(!FLAG) { for(int i=0;i<symbollistsize;i++) { aSymbolSet.insert(Unwantedruntimesymbols[i]); } FLAG=true; } hash_set<const char*, hash<const char*>, eqstr>::const_iterator it = aSymbolSet.find(aSymbol); if(it != aSymbolSet.end()) return 1; else return 0; }
И вуаля: int ElfFileSupplied::UnWantedSymbolp(const char * aSymbol)
{
int symbollistsize = sizeof(Unwantedruntimesymbols) / sizeof(Unwantedruntimesymbols[0]);
for (int i = 0; i<symbollistsize; i++)
{
if (strstr(Unwantedruntimesymbols[i], aSymbol))
return 1;
}
return 0;
}
Моя красота.
Так почему же программа выдает исключение, если этот флаг установлен неправильно или вообще не установлен? Почему вы такие жестокие, прекрасное далеко.
Давай просто сбросим этот флаг на безопасное значение.
И этот флаг тоже хорош.
И этот, и этот, и этот. Или, может быть, лучше вынести это в отдельную функцию? Хорошая идея! Назовем его ParameterManager::CheckOptions()! Шаг влево - падение, шаг вправо - неотловленное исключение, прыжок на месте - спасибо хоть не BSOD =) Скучно.
Глюки и кривоватость.
Оля-ля!!! ЭCleanUpStack Симуляция Symbian на STL?:
В принципе, ничего особенного: std::vector<char*> cleanupStack;
Очистка: std::vector<char*>::iterator aPos;
char *aPtr;
aPos = cleanupStack.begin();
while( aPos != cleanupStack.end() )
{
aPtr = *aPos;
delete[] aPtr;
++aPos;
}
Некоторые светлые умы использовали л/р вместо левого/правого.
Спасибо, cppcheck. Ай, лень парсить логи cppcheck перед монитором.
Что нам предложит GitHub?.
Codacy. Подключаем проект. Немного подумал и готово! Теперь сообщения об успехах в борьбе с ошибками можно читать лёжа на диване ^^ Ну вроде не глючит. Давайте что-нибудь соберем, например libcrypto.dll. Работает, хотя несжатый файл на сто байт больше созданного утилитой из SDK. Далее будут постоянно сравниваться бинарники, созданные этой версией утилиты и из SDK. Параметры командной строки, конечно, идентичны.
Итак, где взять аналог diff для бинарных файлов? Хм, сделаю скрипт на поршне.
Информации слишком много — нужно что-то гораздо проще.
Dll для распознавания pdf/djvu - AlternateReaderRecog.dll - хороший вариант, на выходе меньше 4 килобайт. Итак, смещения различаются в разделе импорта.
Откройте их в шестнадцатеричном редакторе.
Начало то же самое, в моей версии дальше фигня, сразу после окончания раздела в оригинальной версии.
Но в моей версии следующий раздел начинается на 100 байт позже.
Файлы отличаются на одинаковую величину в байтах! Тогда смещения указывают на правильные адреса.
Двоичный файл правильный!!! Аааа!!! Месяц спустя.
Итак, откуда взялись эти сто байт? Ну а раз не понятно как это работает, начинаем ломать алгоритм создания E32Image. Продолжаем издеваться над AlternateReaderRecog.dll. Увеличиваем размер выходного бинарника — никак, стираем разделы memset — никак, уменьшаем размер бинарника — никак.
Гррррр.
Что за?!!! Я ломаю выхлоп в релизной версии, а запускаю отладочную версию?!!! Привет, моло, начни сначала.
Оооо, раздел затерт - хорошо! Увеличен размер двоичного файла! Отлично!!! Давайте уменьшим размер раздела импорта! Есть!!! Побайтно идентичен этому же разделу в выводе этой утилиты из SDK! Давайте посмотрим на код создания этого раздела.
"sizeof(char*)" - как-то вспомнились статьи Андрея Карпова, одного из разработчиков Pvs-studio, о том, как типы могут занимать разное количество памяти - и сколько места они занимают? MinGW - 8 байт, Visual Studio - 4!!! Делим эти 8 байт пополам, вот и дело.
Тффс! А что насчет раздела кода? Это dll без глобальных переменных.
Глобальных переменных нет - разделов тоже нет. Возьмем что-нибудь потяжелее - libcrypto.dll. Выходной файл моей утилиты теперь стал на 100+ байт меньше.
Что??? Раздел импорта побайтно идентичен — хорошо.
Раздел кода - нет?!! Я такую стену текста на глаз сравнить не могу.
Пойду искать дифф для побайтового сравнения.
После пары дней игры с Гуглом я наконец нашел его.
vbindiff — консольная утилита с интерфейсом типа Norton Commander, показывающая разницу между двумя файлами на двух горизонтальных панелях.
Чтобы перейти к месту различия, нажмите Enter. Отлично! Вы можете перетащить на иконку два файла для сравнения и программа их откроет! Большой!!! Давайте сравним - настолько разные crc и время создания в шапке.
Ничего.
Вот немного другое, вот еще сто.
Ух!!! Десятки, сотни, тысячи байт разницы?!!! Итак, давайте посмотрим, к какому разделу они относятся.
Давайте посмотрим на смещения.
Да, раздел данных.
Проделываем тот же трюк, что и с разделом импорта.
Сбрасываем его с помощью memset, есть.
Увеличиваем размер раздела.
Он падает. Увеличиваем.
Предлагает руку и сердце отладчика.
Блин.
Открываем функцию, создающую раздел - каша функций.
Грр.
.
Эх, завтра.
Я пока что-нибудь еще поправлю.
Я, например, добавлю тесты, но там такой бардак, что разделить программу на маленькие модули невозможно.
Тесты прямо в код вставлять нельзя — потом разберетесь.
Идея! Постоянно запускать программы с разными аргументами — так я постоянно тестировал программу… Но давайте лучше вынесем все это в отдельный скрипт на Python. Да, отличная идея, просто отличная.
В случае ошибок выполнения теста скрипт должен продолжать работать и сообщать о них, но не вылетать.
Вот и все! Вернемся к нашим овцам.
Эту функция вызывает эту, потом эту, пойдем сюда.
Итак, сэр, куда я попал? Ух, я запуталась.
Ой, завтра.
Я пока что-нибудь другое исправлю.
И так прошло два месяца.
Блин, а где этот участок кода генерируется? Мне пришлось уйти в академический отпуск, так что хоть с вами разберусь!!! Тааак.
Вот входные символы для раздела.
Что покажет printf? В буфер консоли всё не поместится.
Сохраним выхлоп в файл.
Итак, пока ничего особенного.
Стоп! Идентичные линии!!! Много одинаковых строк!!! Где?! Добавьте printf в каждый источник данных (терпения нам хватило на 3 из пяти, ха).
Пустой! Давайте посмотрим на один из оставшихся вызовов функций.
Тааак.
Приращение итератора после цикла??? И TODO для предупреждения о кодировании??? Давайте заведем это в цикл.
Запуск!!! Есть соответствие размера! Есть побайтовое совпадение!!! Исправленный!!! git виноват отказывается называть героя.
Давайте посмотрим оригинал - я этого не делал.
А может, это была «бомба» для разработчиков, не связанных с Nokia? Гррр.
Мы внимательно проверяем выходные данные теста и сравниваем файлы побайтово.
Все работает как надо! В выпуске! Олаля! Пришло время большой уборки!!! Пришло время искоренить дерево UseCaseBase!!! Большую часть потомков мы уже исчерпали, поэтому полезные функции переносим в класс-генератор.
Остаются только UseCaseBase и его потомок ElfFileSupplied. UseCaseBase — это оболочка класса, который обрабатывает параметры команд и объявляет несколько чисто виртуальных функций для класса ElfFileSupplied. Короче, скрипач не нужен.
Небо голубое, хорошо.
Еще часик.
Я разберу этот класс, и мы сможем пойти погулять.
И подышать воздухом, погреться, хорошо.
Поехали! Итак, закомментируйте эту функцию.
Давайте собирать! Ооочень надо подумать, как это красиво переделать.
Готово!!! Следующая функция! Готовый! Следующий! Готовый! Готовый! Да! Да! Да! Последняя особенность.
Уффф.
Запускаем после сборки.
Семикратное ускорение работы?!!! Выхлоп правильный.
Забавно.
Отладочная версия тоже уменьшилась на 2 метра?!!! Ух ты!!! Вы также можете прогуляться.
Ночью?!!! Как??? Где мой день?!!! Ладно, прогоню тесты и отдохну.
Тесты спокойно отработали - можно отдыхать.
Давайте я сейчас напишу что-нибудь свое.
Ох, жутковато выглядит класс, работающий с доступными извне функциями и переменными.
Принцип работы: чтение из файла, разбор строк и сохранение в файл.
Для анализа строк был выделен целый класс отборной лапши на C. Тааак.
Давайте подумаем.
Какая же красота получилась: читаем строку std::getline(), удаляем пробелы по краям строк и анализируем.
Продолжение следует. Исходный код - https://github.com/fedor4ever/elf2e32 Теги: #программирование #открытый исходный код #двоичный файл #c++14 #проекты с открытым исходным кодом
-
Аккуратный Обмен Сетевыми Сообщениями
19 Oct, 24 -
Apple Ipad 2 Серии Mc770Ll/A Статья
19 Oct, 24 -
Неделя Безопасности 12: Атаки С Клавиатуры
19 Oct, 24 -
Спасите Проект: Самые Важные Вопросы
19 Oct, 24 -
Парам-Спам-Пам
19 Oct, 24 -
Коллтрекинг С Оплатой За Лид, А Не За Номер
19 Oct, 24