Пример обычной работы в Linux-е

Примерно месяц назад ко мне обратился один человек и попросил дизассемблировать HEX-коды микроконтроллера MSP430. Дурное дело — не хитрое, и я согласился. (Правда, потом отказался по этическим соображениям, но это к делу не относится. В статье рассматривается только техническая сторона вопроса.)

Дизассемблировать и привести в порядок код — ничего в этом такого сверхъестественного нет. Нужно просто внимательно и упорно работать. Объем бинарного кода (который заливается во флеш) составлял без малого 16 кило. В мире микроконтроллеров, это достаточно большой объем. И прежде чем накинутся на слона, я решил его измерить и разделить на куски.

Для начала я тупо дизассемблировал HEX-файл с помощью Мишкиного (Mike Kohn) дизассемблера и получил очень длинный листинг более 4600 строк. Вот типовой фрагмент кода, выхваченный из этого листинга:

...
0xdcd0: 0x403c mov.w #0x0545, r12                       2
0xdcd2: 0x0545
0xdcd4: 0x12b0 call #0xe2a6                             5
0xdcd6: 0xe2a6
0xdcd8: 0x403c mov.w #0x0545, r12                       2
0xdcda: 0x0545
0xdcdc: 0x12b0 call #0xe2a6                             5
0xdcde: 0xe2a6
0xdce0: 0xf0f2 and.b #0x77, &0x0091                     5
0xdce2: 0x0077
0xdce4: 0x0091
0xdce6: 0x423c mov.w #8, r12                            1
0xdce8: 0x12b0 call #0xdeee                             5
0xdcea: 0xdeee
...

В принципе, работать можно. Но это не сотня строк, а более четырех с половиной тысяч! Это внушает.

Здесь, первая колонка — это адрес, вторая — машинные коды, третья — ассемблерная команда, соответствующая этим кодам, последняя — количество тактов, затрачиваемое на исполнение команды.

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

Хорошо. Но сколько их, функций-то? Как определить среди спагетти-кода начало-конец функции?

Мы знаем, что функции заканчиваются командой ret. ОК, можно посчитать количество ret-ов и… получить примерное количество функций. Ведь выход из функции может быть не один. Не пойдет!

Но с другой стороны, вход в функцию всегда один. А как узнать, где функция начинается? Проблема в том, что особой отметки (аналогичной команде ret) у начала функции нет.

Можно пойти по другому. Можно начала функций определить по вызовам команд call. Но этих вызовов очень много, одна и та же функция может вызываться многократно из разных мест. Для этого нужно просто откинуть дублирующиеся вызовы. Вы поняли идею?

А теперь начинается Linux-магия. Внимательно следим за руками.

Сначала выделим все строки, в которых присутствует вызов функции — команда call, а строки, где нет этого слова — откинем. Для этого выполним команду:

$ grep call MSP430.asm

grep — это Линуксовая команда, которая будет просматривать текст и выводить строки, в которых содержится слово ‘call’. MSP430.asm — это имя файла, с которым будет работать команда grep.

В результате на выходе у нас получится приличный кусок текста, в каждой строчке которого содержится вызов call. Вот короткий фрагмент полученного текста:

...
0xdcd4: 0x12b0 call #0xe2a6                             5
0xdcdc: 0x12b0 call #0xe2a6                             5
0xdce8: 0x12b0 call #0xdeee                             5
...

Отлично! Теперь нам из всего этого нужно выделить только адреса функций, которые находятся в четвертой колонке. Добавим команду cut:

$ grep call 430g415-reduce.asm | cut -d' ' -f4

Опция -d’ ‘ — говорит, что разграничителем между колонками будет считаться символ пробела (который заключен в апострофы), а опция -f4 — сообщает команде cut, что нас интересует только четвертое поле (колонка, столбец). Таким образом, после этой команды, мы получим колонку цифр:

...
#0xe2a6
#0xe2a6
#0xdeee
...

Все хорошо, но нам нужно еще избавится от повторяющихся элементов, то есть от повторяющихся адресов вызова функций. В Linux-е есть хорошая команда — uniq, которая удаляет повторяющиеся элементы. Но прежде чем отдать ей на обработку колонку цифр, давайте отсортируем цифры по возрастанию. Конечно, нам легче работать с отсортированным списком адресов функций, чем с винегретом из них. Но сортировать прямо сейчас не обязательно, это можно сделать как-нибудь потом. Но если мы отсортируем список сейчас, мы облегчим работу команде uniq.

$ grep call 430g415-reduce.asm | cut -d' ' -f4 | sort | uniq

Классно! Однако, что это мы — в ручную что ли будем сейчас подсчитывать количество строк с адресами функций? Не-е, мы гики, ручной труд нам противопоказан. Поэтому воспользуемся командой для подсчета статистики — wc:

$ grep call 430g415-reduce.asm | cut -d' ' -f4 | sort | uniq | wc -l

Опция -l — говорит команде wc, что считать нужно не все подряд, а только количество строк. В результате этой колбасы из Linux-овых команд, передающих поток данных друг другу, мы получим на экране только одно число — это есть ничто иное, как количество функций в программе. Что нам и было нужно найти. Ура!

—————

Я прошу вас не рассматривать эту статью, как нечто выдающееся. Просто я хотел поделиться с общественностью типичным для Linux-а решением проблемы. Проблема массового использования Linux-а состоит в том, что народ не знает, как с этим инструментом обращаться. Не хватает разных живых примеров для обучения. А читая вот такие короткие статьи по практическому использованию инструментария, народ сможет реально овладеть искусством обращения с мощным Джедайским световым мечом.

Advertisements

One response to “Пример обычной работы в Linux-е

  1. Вальдемар

    Прекрасная и полезная статья. Спасибо

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s