Раскрытие ассемблера языка кода спагетти
Я унаследовал программу из 10 тысяч строк, написанную на ассемблере 8051, которая требует некоторых изменений. К сожалению, это написано в лучших традициях кода спагетти. Программа, написанная в виде одного файла, представляет собой лабиринт операторов CALL и LJMP (всего около 1200) с подпрограммами, имеющими несколько точек входа и / или выхода, если они вообще могут быть определены как подпрограммы. Все переменные являются глобальными. Есть комментарии; некоторые верны. Там нет никаких существующих тестов, и нет бюджета для рефакторинга.
Немного предыстории приложения: код управляет коммуникационным узлом в торговом приложении, которое в настоящее время развернуто на международном уровне. Он обрабатывает два последовательных потока одновременно (с помощью отдельного коммуникационного процессора) и может взаимодействовать с четырьмя различными физическими устройствами, каждое из которых принадлежит разному поставщику. Производитель одного из устройств недавно внес изменение ("Да, мы внесли изменение, но программное обеспечение абсолютно одинаковое!"), Что приводит к тому, что некоторые конфигурации системы перестают работать, и не заинтересовано в его изменении (каким бы оно ни было) они не изменились).
Первоначально программа была написана другой компанией, передана моему клиенту, а затем изменена девять лет назад другим консультантом. Ни оригинальная компания, ни консультант не доступны в качестве ресурсов.
Основываясь на анализе трафика на одной из последовательных шин, я придумала хак, который, кажется, работает, но уродлив и не устраняет основную причину. Если бы у меня было лучшее понимание программы, я думаю, что смог бы решить актуальную проблему. У меня есть еще одна неделя, прежде чем код будет заморожен для поддержки даты отгрузки на конец месяца.
Оригинальный вопрос: мне нужно понять программу достаточно хорошо, чтобы внести изменения без поломок. Кто-нибудь разработал методы для работы с этим беспорядком?
Я вижу некоторые отличные предложения здесь, но ограничен по времени. Однако в будущем у меня может появиться еще одна возможность продолжить некоторые из более сложных действий.
11 ответов
Во-первых, я бы попытался связаться с теми людьми, которые изначально разрабатывали код или, по крайней мере, поддерживали его до меня, надеясь получить достаточно информации, чтобы получить общее представление о коде в целом, чтобы вы могли начать добавлять полезные комментарии к Это.
Может быть, вы даже можете попросить кого-нибудь описать наиболее важные API (включая их сигнатуру, возвращаемые значения и назначение) для кода. Если глобальное состояние модифицируется функцией, это также следует сделать явным. Точно так же начинайте различать функции и процедуры, а также регистры ввода / вывода.
Вы должны четко дать понять своему работодателю, что эта информация необходима, если они вам не верят, пусть они действительно сядут с вами перед этим кодексом, пока вы описываете, что вы должны делать и как вы должны делать это (обратный инжиниринг). В этом случае полезно иметь работодателя с опытом работы в области вычислительной техники и программирования!
Если у вашего работодателя нет такого технического образования, попросите его пригласить другого программиста / коллегу, чтобы он объяснил вам ваши шаги, это фактически покажет ему, что вы серьезно и честно относитесь к этому, потому что это реальная проблема, а не просто с вашей точки зрения (убедитесь, что есть коллеги, которые знают об этом "проекте").
Если это возможно и осуществимо, я бы также очень четко дал понять, что заключение контракта (или, по крайней мере, связывание) с бывшими разработчиками / сопровождающими (если они больше не работают в вашей компании, то есть) для оказания помощи в документировании этого кода было бы предварительным -реально реально улучшить код в течение короткого промежутка времени и обеспечить его более легкое сопровождение в будущем.
Подчеркните, что вся эта ситуация связана с недостатками в предыдущем процессе разработки программного обеспечения и что эти шаги помогут улучшить базу кода. Таким образом, кодовая база в ее нынешнем виде является растущей проблемой, и все, что делается сейчас для решения этой проблемы, является инвестицией в будущее.
Само по себе это также важно, чтобы помочь им оценить и понять вашу ситуацию: делать то, что вы должны делать сейчас, далеко не тривиально, и они должны знать об этом - хотя бы для того, чтобы оправдать свои ожидания (например, в отношении сроков и сложности задание).
Кроме того, лично я бы начал добавлять модульные тесты для тех частей, которые я достаточно хорошо понимаю, чтобы я мог медленно начать рефакторинг / переписывание некоторого кода.
Другими словами, хорошая документация и комментарии к исходному коду - это одно, а наличие комплексного набора тестов - это еще одна важная вещь, никто не может реально ожидать изменения незнакомой базы кода без какого-либо установленного способа тестирования функциональности ключа.
Учитывая, что код имеет размер 10 КБ, я бы также рассмотрел выделение подпрограмм в отдельные файлы, чтобы сделать компоненты более идентифицируемыми, предпочтительно используя оболочки доступа вместо глобальных переменных, а также интуитивно понятные имена файлов.
Кроме того, я хотел бы изучить шаги по дальнейшему улучшению читабельности исходного кода за счет уменьшения сложности, наличия подпрограмм с несколькими точками входа (и, возможно, даже с различными сигнатурами параметров?), Которые выглядят как верный способ запутать код без необходимости.
Точно так же огромные подпрограммы также могут быть преобразованы в более мелкие, чтобы улучшить читаемость.
Итак, одной из самых первых вещей, которые я хотел бы рассмотреть, было бы определение тех вещей, которые действительно усложняют взлом базы кода, а затем переделывают эти части, например, разбивая огромные подпрограммы с несколькими точками входа на отдельные подпрограммы, которые вместо этого вызывают друг друга. Если это невозможно сделать из-за соображений производительности или из-за накладных расходов, используйте вместо этого макросы.
Кроме того, если это жизнеспособный вариант, я хотел бы рассмотреть возможность поэтапного переписывания частей кода с использованием языка более высокого уровня, либо с использованием подмножества C, либо, по крайней мере, путем чрезмерного использования макросов сборки, чтобы помочь стандартизировать код базы, но и помочь локализовать потенциальные ошибки.
Если инкрементная перезапись в C является выполнимой опцией, одним из возможных способов начать было бы превращение всех очевидных функций в функции C, тела которых - в начале - скопированы / вставлены из файла сборки, так что в итоге вы получите C функции с множеством встроенной сборки.
Лично я также попытался бы запустить код в симуляторе / эмуляторе, чтобы легко пройтись по коду и, надеюсь, начать понимать наиболее важные строительные блоки (при изучении использования регистров и стеков), хороший симулятор 8051 со встроенным отладчиком должен быть предоставляется вам, если вы действительно должны делать это в основном самостоятельно.
Это также поможет вам придумать последовательность инициализации и структуру основного цикла, а также граф вызовов.
Возможно, вы даже можете найти хороший симулятор 80851 с открытым исходным кодом, который можно легко модифицировать, чтобы он также автоматически предоставил полный граф вызовов, просто выполнив быстрый поиск, я обнаружил gsim51, но, очевидно, есть несколько других опций, в том числе и проприетарных.
Если бы я находился в вашей ситуации, я бы даже подумал о том, чтобы передать свои усилия по модификации моих инструментов, чтобы упростить работу с этим исходным кодом, т.е. многие проекты sourceforge принимают пожертвования, и, возможно, вы можете убедить своего работодателя спонсировать такую модификацию.
Если не в финансовом отношении, может быть, вы предоставили соответствующие патчи к нему?
Если вы уже используете проприетарный продукт, вы можете даже поговорить с производителем этого программного обеспечения и подробно рассказать о ваших требованиях и спросить их, хотят ли они улучшить этот продукт таким образом или, по крайней мере, они могут предоставить интерфейс, позволяющий клиенты могут делать такие настройки (некоторая форма внутреннего API или, может быть, даже простые скриптовые сценарии).
Если они не реагируют, укажите, что ваш работодатель уже некоторое время думает об использовании другого продукта, и что вы были единственным, кто настаивал на том, что этот конкретный продукт будет использоваться...;-)
Если программное обеспечение ожидает определенное аппаратное и периферийное оборудование ввода / вывода, вы можете даже захотеть написать соответствующий цикл аппаратного моделирования для запуска программного обеспечения в эмуляторе.
В конечном счете, я точно знаю, что лично мне гораздо больше понравился бы процесс настройки другого программного обеспечения, чтобы помочь мне понять такого монстра кода спагетти, чем ручное пошаговое выполнение кода и игра на эмуляторе, независимо от того, сколько галлонов кофе я могу получить.
Получение пригодного для использования callgraph из эмулятора с открытым исходным кодом 8051 не должно занять намного больше времени, чем, скажем, выходные (максимум), потому что это в основном означает поиск кодов операций CALL и запись их адресов (позиции и цели), так что все выводится на файл для последующей проверки.
Наличие доступа к внутренним компонентам эмулятора на самом деле также было бы отличным способом для дальнейшей проверки кода, например, чтобы найти повторяющиеся шаблоны кодов операций (скажем, 20-50+), которые могут быть учтены в отдельных функциях / процедурах, это может на самом деле помочь уменьшить размер и сложность базы кода еще больше.
Следующим шагом, вероятно, будет проверка использования стека и регистрации. И для определения типа / размера используемых параметров функции, а также их диапазона значений - так что вы можете представить соответствующие модульные тесты.
Использование инструментов типа dot / graphviz для визуализации структуры последовательности инициализации и самого основного цикла будет просто радостью по сравнению с выполнением всего этого вручную.
Кроме того, вы фактически получите полезные данные и документы, которые могут послужить основой для более качественной документации в долгосрочной перспективе.
Боюсь, нет волшебной пули для такого рода проблем. Я считаю, что единственное решение состоит в том, чтобы распечатать файл ASM, а затем пойти куда-нибудь тихо и симулировать запуск программы построчно в уме (при записи содержимого регистров и областей памяти в блокноте). Через некоторое время вы обнаружите, что это не займет столько времени, сколько вы ожидаете. Будьте готовы потратить много часов на это и выпить галлоны кофе. Через некоторое время у вас появится понимание того, что он делает, и вы сможете рассмотреть изменения.
Есть ли у 8051 неиспользуемые порты ввода-вывода? Если это так, и вы не можете работать при вызове определенных подпрограмм, то добавьте код, чтобы отправить эти запасные порты как высоко, так и низко. Затем, когда программа работает, наблюдайте за этими портами с помощью осциллографа.
Удачи
Я знаю, это звучит безумно.... но я безработный (я выбрал неподходящее время, чтобы сказать партнеру по браку идти в ад) и у меня есть немного свободного времени. Я был бы готов взглянуть на это. Раньше я писал сборку для яблока][и оригинального ПК. Если бы я мог поиграть с вашим кодом на симуляторе в течение пары часов, я мог бы дать вам идею, если бы у меня была возможность документировать его для вас (без проведения моего незапланированного отпуска). Поскольку я ничего не знаю о 8051, это может быть невозможно для кого-то вроде меня, но симулятор выглядел многообещающе. Я не хотел бы денег, чтобы сделать это. Достаточно просто получить представление о встроенных разработках 8051. Я сказал вам, это будет звучать безумно.
Найти другую работу - серьезно! В противном случае может помочь книга "Эффективная работа с унаследованным кодом", хотя я думаю, что она ссылается на унаследованный код как на код без юнит-тестов.
Я делал такие вещи пару раз. Некоторые рекомендации:
- Начните с просмотра схемы, это должно помочь вам понять, на какие порты и штыри влияют желаемые изменения.
- Используйте grep, чтобы найти все звонки, ответвления, переходы и возвраты. Это может помочь понять поток и идентифицировать куски кода.
- Посмотрите на вектор сброса и таблицу прерываний, чтобы определить основные линии.
- Используйте grep для создания перекрестной ссылки для всех меток кода и ссылок на данные (если ваши инструменты на ассемблере не могут сделать это за вас).
Имейте в виду закон Хофштадтера: это всегда занимает больше времени, чем вы ожидаете, даже если вы принимаете во внимание закон Хофштадтера.
Удачи.
Насколько хорошо вы понимаете аппаратную платформу, на которой работает этот код?
Был ли он переведен в режим пониженного энергопотребления (Pcon=2) для экономии энергии? Если да, то как он проснулся? (сброс или аппаратное прерывание)
Вам нужно подождать, пока генератор стабилизируется после включения питания, прежде чем делать последовательную связь
Был ли он переведен в спящий режим (Pcon=1)
Существуют ли разные версии аппаратного обеспечения в полевых условиях?
Убедитесь, что у вас есть все варианты оборудования для тестирования.
Не тратьте свое время на симулятор - с ним очень сложно работать, и вам приходится делать множество предположений относительно аппаратного обеспечения. Получите In In Circuit Emulator (ICE) и работайте на оборудовании.
Программное обеспечение было написано на ассемблере по той причине, по которой вам нужно выяснить, почему. т.е. - ограничения памяти - ограничения скорости
Может быть причина того, что этот код беспорядок
Посмотрите на файл ссылки для:
XDATA SPACE, IDATA SPACE и CODE SPACE:
Если нет свободного места для кода или Xdata или Idata?
Оригинальный автор, возможно, оптимизировал его, чтобы уместить в доступное пространство памяти.
Если это так, вам нужно поговорить с оригинальным разработчиком, чтобы узнать, что он сделал.
Это один из немногих случаев, когда я порекомендую вам применить свои мягкие навыки и представить вашему PM/Manager/CXO свои аргументы в пользу переписывания, а также экономию времени / затрат, связанных с таким мероприятием.
Вам не нужен специальный бюджет для рефакторинга и тестирования - они экономят ваши деньги и позволяют вам работать быстрее - доберитесь до него. Это техника, которую вы должны использовать для добавления изменений в унаследованный, унаследованный код, потому что это самый дешевый способ сделать это без "поломок".
В большинстве случаев я думаю, что есть компромисс, когда вы получаете больше качества в обмен на то, чтобы тратить больше времени, но с устаревшим кодом, с которым вы не знакомы, я думаю, что быстрее создавать тесты - вы должны выполнить код до того, как Вы отправляете это, правильно?
У меня была очень похожая проблема с программным обеспечением 8052. Таким образом, компания унаследовала такого зверя с полным объемом ПЗУ (64 Кбайт), около 1,5 мегабайта сборочных модулей спагетти плюс два 3000-строчных модуля PL/M, составляющих это чудовище кодирования. Первоначальные разработчики программного обеспечения были давно мертвы (это не значит, что никого не было, но на самом деле никто не понимал этого в целом), компиляторы, которые их компилировали, были с середины 80-х годов на эмуляторе MDS-70, и несколько критически важных модули были в пределах этих компиляторов. Как добавить еще один глобальный символ, и компоновщик потерпит крах. Добавьте еще один символ в файл ASM, и компилятор потерпит крах.
Итак, как можно начать резать это?
Сначала вам понадобятся инструменты. Например, Notepad++ - очень хорошая вещь, поскольку его можно использовать для перекрестного поиска по нескольким файлам одновременно, что идеально подходит для поиска модулей, ссылающихся на глобальный символ. Это, наверное, самый важный элемент.
Если возможно, получите любые документы, которые вы можете найти в программном обеспечении. Самая неотложная проблема, которую необходимо решить с этими животными, заключается в том, чтобы понять, как они примерно составлены, какова их архитектура. Обычно это не входит в саму программу, даже если это не правильно прокомментировано.
Чтобы получить архитектуру самостоятельно, сначала вы можете попытаться построить граф вызовов. Это проще сделать, чем граф потока данных, поскольку обычно между вызовами и переходами между файлами меньше, чем глобальными переменными. Для этого графы вызовов учитывают только глобальные символы, предполагая, что исходные файлы должны быть модулями (что не всегда верно, но обычно так и должно быть).
Для этого используйте инструмент для поиска по нескольким файлам, создайте большой список (например, в OpenOffice Calc), где вы собираете, какой символ определен в каком файле, и какие файлы ссылаются на этот символ, вызывая его.
Затем украдите несколько больших (!) Листов из плоттера и начните рисовать. Если вы очень опытны в каком-либо графическом программном обеспечении, вы можете использовать его, но, если это не так, это с большей вероятностью вас сдержит. Поэтому нарисуйте график вызовов, показывающий, к какому файлу обращаются другие файлы (без отображения самих символов, с примерно 50 файлами, вы не сможете управлять им).
Скорее всего, результатом этого будут спагетти. Цель состоит в том, чтобы уладить это, чтобы получить иерархическое дерево с корнем (который будет файлом, содержащим точку входа в программу) без циклов. Вы можете пожрать несколько листов во время этого процесса, итеративно выпрямляя зверя. Вы также можете обнаружить, что некоторые файлы настолько перепутаны, что их невозможно представить без циклов. В этом случае наиболее вероятно, что один "модуль" был каким-то образом разделен на два файла, или были перепутаны более концептуальные модули. Вернитесь к списку вызовов и сгруппируйте символы таким образом, чтобы разрезать проблемные файлы на более мелкие независимые блоки (вам также необходимо проверить сам файл на наличие локальных переходов, чтобы увидеть, что ваше предполагаемое сокращение возможно).
До конца, если вы уже не работаете где-то еще для своего блага, вы получите иерархический граф вызовов с концептуальными модулями. Из этого можно вычесть намеренную архитектуру программного обеспечения и работать дальше.
Следующая цель - архитектура. По вашей ранее составленной карте вам нужно будет перемещаться по программному обеспечению, выяснять его потоки (задачи прерывания и основные программы) и приблизительные цели каждого из модулей / исходных файлов. То, как вы можете это сделать, и то, что вы получите, зависит в большей степени от предметной области.
Когда эти двое сделаны, "отдых" довольно прост. Этим вы должны по существу знать, что должна делать каждая часть этой вещи, и, таким образом, вы знаете, с чем вы, вероятно, имеете дело, когда начнете работать с исходным файлом. Однако важно, что всякий раз, когда вы находите что-то "подозрительное" в источнике, программа, кажется, делает что-то неуместное, чтобы вернуться к вашей архитектуре и вызвать граф, и внести исправления, если это необходимо.
В остальном методы, упомянутые другими, применимы хорошо. Я только обрисовал их, чтобы дать некоторое представление о том, что можно сделать в действительно отвратительных случаях. Я хотел бы иметь только 10 тысяч строк кода, чтобы иметь дело с тогда...
Я бы сказал, что ответ IanW (просто распечатайте его и продолжайте отслеживать), вероятно, лучший. Тем не менее, у меня есть немного нестандартная идея:
Попробуйте запустить код (возможно, двоичный) через распространитель, который может восстановить код C (если вы можете найти его для 8051). Может быть, он определит несколько процедур, которые вы не можете (легко).
Может быть, это поможет.