Продвинутые таблицы в ЛаТеХе: advanced tables in LaTeX



30 коммент.

Набор таблиц в LaTeX, как уже говорилось, дело не простое, а набор сколько-нибудь продвинутых таблиц - скажем так, удовольствие ниже среднего. Тем не менее, ЛаТеХ позволяет тонко настраивать вид таблицы и добиваться, хотя и ценой некоторых умственных усилий, весьма навороченных результатов. Немного накопленных трюков и кульбитов описаны в этом посте.

Этот пост является продолжением эпической серии о наборе диплома в латехе и конкретно темы набора таблиц. Чтобы не перегружать старый пост ещё больше, найденные трюки по работе с таблицами вынесены в новый пост, а старый - изрядно переработан.

Объединение строк и столбцов в таблицах LaTeX

Создание навороченных таблиц часто требует объединять столбцы и строки, особенно при группировке сходных данных. Для этого можно использовать команды \multicolumn и \multirow, а так же некоторые другие трюки, о которых ниже.

Напомню, что \multicolumn{2}{|c|}{Результаты измерений} означает, что мы объединяем 2 (два) столбца в таблице, получившаяся ячейка будет центрирована и нарисованы вертикальные линии.

Таблицы с объединением столбцов в LaTeX

Для того, чтобы несколько столбцов объединить в одну, можно воспользоваться командами \cline, \raisebox и \multicolumn одновременно. Здесь \multicolumn служит для объединения двух колонок в одну, \cline прочерчивает линию в строках, а \raisebox поднимает текст к середине объединённой строки.
Вот код таблицы:

\begin{table}[H]
\caption{\label{tab:bolts} Нестандартные болты для левой резьбы.}
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
& \multicolumn{2}{c|}{Диаметр} \\
\cline{2-3}
\raisebox{1.5ex}[0cm][0cm]{Нестандартные болты}
& Норма & Разброс \\
\hline
Размеры & 10 мм & 1 мм \\
\hline
\end{tabular}
\end{center}
\end{table}

В документе это будет выглядеть так (обратите внимание на первую колонку):


Для этой таблицы использован трюк с \multicolumn для объединения двух ячеек в одну, \cline{2-3} для прочерчивания горизонтальной линии в таблице от второй колонки до третьей, и \raisebox для вставки надписи Нестандартные болты.


Таблицы с объединением строк в LaTeX

Если нужна таблица, в которой несколько строк объединены, можно использовать пакет multirow, добавив \usepackage{multirow} в преамбулу документа. Это позволит создать строки, простирающиеся на несколько колонок командой:
\multirow{объединить Х строк}{ширина}{содержимое}
Предыдущий пример можно переиначить вот так:

\begin{table}[H]
\caption{\label{tab:bolts} Нестандартные болты для левой резьбы.}
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\multirow{3}{*}{Размеры нестандартных болтов} & \multicolumn{2}{c|}{Диаметр} \\
\cline{2-3}
            & Норма & Разброс \\
\cline{2-3}
            & 10 мм & 1 мм \\
\hline
\end{tabular}
\end{center}
\end{table}

В документе такая таблица будет выглядеть так:



Объединение строк и столбцов вместе плюс использование hhline

Пример с texexchange, который мне нравится, иллюстрирует применение описанных выше команд:

\begin{table}[ph]
  \centering
  \begin{tabular}{c|c|c|c|c}
    \hline
    \multirow{2}{*}{Raaa (k)} & \multicolumn{4}{c}{C ()} \\
    \hhline{~----}
    & 3.3 & 2.5 & 1 & 0.5 \\
    \hline
    \multirow{2}{*}{Raaa (k)} & \multicolumn{2}{c|}{\multirow{2}{*}{this}} & 0.5 & 0.6\\
    \hhline{~~~--}            & \multicolumn{2}{c|}{}                      & 0.7 & 1.2 \\
    \hline
  \end{tabular}
  \caption{R, C ripple size}
  \label{T:peak}
\end{table}

В документе выглядит так:


Здесь нужно отметить применение команды \hhline из одноимённого пакета hhline, которая рисует линии в таблицах, подобно обычной \hline. Особенность \hhline в том, что можно прочерчивать невидимые линии (~) и обычные (-) вот так:
\hhline{~----}
В данном случае будет нарисовано пять линий в таблице, первая будет пустая. В общем это аналог \cline{2-5}, только более удобный.

Надеюсь, что с такими таблицами вы будете встречаться так же редко, как и с такими экзотическими болтами.


Раскраска ячеек таблиц в LaTeX

Кто сказал, что таблицы в LaTeX унылы и безжизненны? Хотя часто раскраска таблицы не одобряется, иногда, особенно в презентациях, выделение ячеек таблицы цветом помогает быстрее понять материал.
Для раскраски ячеек нам понадобятся пакеты
\usepackage{color,colortbl}
и в преамбуле определяем цвета, которые нам нужны:
\definecolor{darkishgreen}{RGB}{39,203,22}
\definecolor{LightCyan}{rgb}{0.88,1,1}
\definecolor{Gray}{gray}{0.9}
\definecolor{lightRed}{RGB}{230,170,150}
\definecolor{modRed}{RGB}{230,82,90}
\definecolor{strongRed}{RGB}{230,6,6}
Теперь нужно сделать отдельный тип цветных колонок для таблицы. Для этого в документе, уже после \begin{document}, создаём новые команды в виде:
\newcolumntype{g}{>{\columncolor{Gray}}c}
и
\newcolumntype{d}{>{\columncolor{darkishgreen}}c}
Это позволит нам раскрашивать колонки. Для раскраски строк воспользуемся командой \rowcolor{цвет} из пакета colortbl. Можно раскрасить отдельно ячейку с помощью команды \cellcolor{Gray} которую помещаем в раскрашиваемую ячейку.

А теперь всё вместе:

\newcolumntype{g}{>{\columncolor{Gray}}c}
\newcolumntype{d}{>{\columncolor{darkishgreen}}c}

\begin{tabular}{|c||c||g||d|d|d|}
    \hline
 Signal & \cellcolor{Gray} Device    & \multicolumn{4}{|c|}{Computation time, s}\\
\cline{3-6}
Strength    & \cellcolor{Gray}  size     &Dantzig-   &Branch and & Active    &Projected\\ 
        &       &Wolfe      &Bound      &Set        &Gradients\\ 
\rowcolor{lightRed}
\hline
Weak        & 7x7   &400        &230        &200        &58\\ \cline{3-6}
\rowcolor{lightRed}
(0\% constr.)    & 10x10 &1000       &840        &500        &135 \\ \cline{3-6}

\hline
\rowcolor{modRed}
Moderate    & 7x7   &640        &380        &270        &54\\ \cline{3-6}
\rowcolor{modRed}
(5\% constr.)    & 10x10 &3120       &1200       &700        &110 \\ \cline{3-6}

\hline
\rowcolor{strongRed}
Strong      & 7x7   &1400       &290        &350        &55\\  \cline{3-6}
\rowcolor{strongRed}
(20\% constr.)   & 10x10 &15320      &810        &960        &120 \\  \cline{3-6}
\hline \hline
    \end{tabular}

В документе такая таблица выглядит вот так:


Здесь ещё можно отметить использование || в качестве двойного разделителя колонок.




Изменение размеров строк и столбцов в ЛаТеХ таблице

Переменная \arraystretch отвечает за расстояние между строками а переменная \tabcolsep соответственно за расстояние между столбцами. В примере ниже мы сделаем столбцы и строки пошире:

\renewcommand{\arraystretch}{1.8} %% increase table row spacing
\renewcommand{\tabcolsep}{1cm}   %% increase table column spacing
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
Расширение краёв: & \textbf{1,0-1,4} & размер ФРТ \\
\hline
Аподизация: & \textbf{0,25-0,30} & размер ФРТ \\ 
\hline
Сглаживания краёв: & \textbf{0,25-0,50}& размер ФРТ \\
\hline
\end{tabular}
\end{center}

В документе это будет выглядеть так:



Изменение высоты одной строки в таблице

Иногда требуется сделать одну из строк в таблице побольше, не задевая все остальные. Здесь поможет трюк с командой rule: команда вставит невидимую горизонтальную линию заданной ширины и толщины:
\rule[высота подъёма линейки]{ширина}{толщина}
Это не единственный манёвр, который позволяет увеличить расстояние между строками: можно в конце строки, после \\ вставить [1cm], увеличив размер строки соответственно.

В данном примере мы расширим первую строку в таблице с помощью \rule{0cm}{2cm} и вторую с помощью [1cm] следующим образом:

\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\rule{0cm}{2cm}
Расширение краёв: & \textbf{1,0-1,4} & размер ФРТ \\
\hline
Аподизация: & \textbf{0,25-0,30} & размер ФРТ \\  [1cm]
\hline
Сглаживания краёв: & \textbf{0,25-0,50}& размер ФРТ \\
\hline
\end{tabular}
\end{center}
 
Так выглядит таблица в документе:


Ещё немного о трюках с таблицами можно почерпнуть там.




Книжные таблицы в LaTeX: booktabs

По умолчанию таблицы, набранные в латехе, смотрятся неплохо, хотя им недостаёт "книжного" вида. Например, первая и последняя линия в таблице обычно жирнее, чтобы показать начало и конец таблицы.

Если вам хочется набирать таблицы "как у больших дядь в книжках", можно воспользоваться пакетом booktabs. Подключив в преамбуле документа \usepackage{booktabs} мы сразу же имеем возможность набирать красивые таблицы.
"Большие дяди" стараются сделать таблицы проще, с комфортным для глаз расстояниями между строками, и почти никогда не используют вертикальные разделители.
Вот код для такой таблицы:

\begin{tabular}{llr}
\toprule     %%% верхняя линейка
\multicolumn{2}{c}{Постобработка} \
\cmidrule(r){1-2}
Вид & Описание & Время выполнения, сек. \
\midrule %%% тонкий разделитель 
Расширение краёв        & холодный старт & 12 \
                    & горячий старт &    8 \
Аподизация          & симметричная  & 90 \
Сглаживания краёв   & по Гауссу  & 33 \
\bottomrule %%% верхняя линейка
\end{tabular}
 
Вот как выглядит такая таблица:




Когда столбцов в таблице слишком много...

Чтобы проще было задавать число колонок таблицы, особенно если их много, можно использовать конструкцию вида \begin{tabular}{l*{X}{l}}, где X это число колонок.

Вот пример такой таблицы с 10 колонками:

\begin{tabular}{l*{10}{l}}
\hline
Расширение краёв:   &  1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\
\hline
Аподизация:             & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\
\hline
Сглаживания краёв:  & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9  \\
\hline
\end{tabular}

А вот как она выглядит в готовом документе:


Стоит отметить пакет siunitx, который предоставляет возможность  выравнивания по десятичной точке или запятой. Также можно посмотреть на пакет array, предоставляющий расширенные версии окружений tabular и array (например, вертикально центрированные колонки и возможность определять новые типы колонок).


Ссылки

Эти трюки позволят создать весьма навороченные таблицы без необходимости перечитывать томик квантовой физики.

Конечно, есть ещё много пакетов по работе с таблицами, например ltxtable - это longtable и tabularx, который стоит использовать для создания больших и сложных таблиц с разделением по страницам, с кучей текста в ячейках. Многостраничные таблицы так же может создавать пакет supertab.

Cтоит упомянуть пакет slashbox, который позволяет создавать ячейки, разделенные по диагонали.

Стоит ещё отметить хороший туториал по таблицам, который включает \multirow и \multicol. Поборники русского стиля оформления таблиц могут заинтересоваться постом по ссылке.
Читать далее

Новый 2013 год и подведение итогов 2012



6 коммент.
Очередной понедельничный пост, на сей раз новогодний. Решил подвести немного итогов года, оглянуться назад и почить на лаврах :-)

Как оно было?

Год выдался весьма плодотворным: было много конференций, узнал много нового, обновил парк ноутбуков и реализовал хрустальную мечту юности (Lenovo X201 Tablet).

В смысле постов - их было столько же, сколько и в предыдущем году. Стало больше гостевых постов, и это радостно: гостевые посты вносят свежую струю в блог, и так я узнаю много нового и интересного (и надеюсь, не только я).

Запущен в тестовом режиме Лунодром "Дебианити". Это совсем мелкие заметки на манжетах, ссылки и прочие наброски, не подходящие под формат записок. Итог моих экспериментов со scriptogr.am и markdown, а так же временное решение проблемы хранения мелких набросков под рукой (ещё один блог заводить не хочется, здесь публиковать рано или ни к чему, а пригодиться могут).

Теперь итоги. Посты были разные, и интересно взглянуть на то, что понравилось из написанного мне, и что - читателям.

Топ5 постов, которые нравятся мне

Есть посты, которые пишутся в формате "чтобы не забылось" - и это почти всё про LaTeX и Gnuplot, которыми я пользуюсь постоянно. Но есть и нечто особенное, что стоит много часов и дней усилий по сбору материала и выпиливанию подробностей. Итак, мой Топ-5 постов за этот год, которые мне нравятся.

Странное рождение и долгая жизнь UNIX

Пост года, во всяком случае для меня, и самый большой перевод с английского на русский. На перевод и утрясание копирайтов с IEEE ушло почти четыре дня, но результат того стоил. Хотя переводчик с меня не очень хороший, смысл, думаю, передать удалось. Пост важен с исторической точки зрения, дабы помнить, с чего всё начиналось.
Статистика: +31 Plus one, 27 Comments, 5977 Views.

Markdown - гаечный ключ для забивания треугольных болтов

После того, как я нашёл применение Markdown в качестве простого и быстрого генератора вложенных списков для LaTeX, он стал частью моего workflow. Но этому предшествовала переписка с iv_vl, которая, надо сказать, направила меня по ложному следу. Пост был написан после того, как я перепробовал массу опенсорсных сноповязалок и прочих велосипедов с квадратными колёсами :-)
Статистика: +24 Plus one, 33 Comments, 2699 Views.

Краткий обзор современных окружений рабочего стола

Несколько неожиданный для меня экспромт Vlsu, ставший отличным постом. Прекрасные иллюстрации, структура и описания, хотя самый первый вариант был вызывающе неполиткорректным :-) Тем не менее, пост более чем стоил усилий, на него потраченных - один из лучших постов этого года, и абсолютный лидер по количеству просмотров. Браво, Vlsu, снимаю шляпу.
Статистика: +25 Plus one, 72 Comments, 13020 Views.

Pomodoro: тайм-менеджмент, который работает

Pomodoro я использую уже больше года, и для меня это было большим прорывом в плане учёта и анализа собственной продуктивности. Позволяет делать больше без стахановских рывков и прочего ненужного героизма. Пост, один из самых подробных за этот год, писался долго и был сильно улучшен усилиями комментаторов.
Статистика: +28 Plus one, 26 Comments, 3991 Views.

Настольный Змей Горыныч

Один из наиболее информативных постов для меня лично - узнал много нового про multihead, хотя это стоило некоторых денег (купленные переходники, хотя и используются время от времени, но tripple-head на ноутбуке не вышел). Пост нравится своей структурированностью, которая стоила немалых усилий. Кроме того, один из немногих постов, почти полностью написанный в Markdown.
Статистика: +22 Plus one, 18 Comments, 2169 Views.



Топ5 постов, которые понравились читателям

Здесь я ориентировался по числу PlusOne и просмотрам. В комментариях можно отвести душу и привести свои списки, но здесь балом правит бездушная статистика.

Вскрытие 9В батарейки: в поисках батареек АААА

Написанный за час и почти не стоивший усилий, пост неожиданно понравился народу на Хабре и собрал больше всех ПлюсАдынов за этот год (почти полсотни). Все деньги, потраченные на его написание, с лихвой отбились кассовыми сборами с рекламы :-)
Статистика: +46 Plus one, 18 Comments, 5041 Views.

Странное рождение и долгая жизнь UNIX

Здесь наши симпатии совпадают - второй пост по плюсадинам. Кривизну перевода сильно выправил тов. Minoru, за что ему большое спасибо.
Статистика: +31 Plus one, 27 Comments, 5977 Views.

Краткий обзор современных окружений рабочего стола

Самый комментируемый и самый посещаемый пост года от Vlsu. Уже растащен на скриншоты, понаставлена куча ссылок и есть по крайней мере два репоста. Как говорится, признание к художнику приходит не тогда, когда его работы в первый раз выставят, а тогда, когда их в первый раз украдут :-)
Статистика: +25 Plus one, 72 Comments, 13020 Views.

Возможности настройки GRUB2 в Linux

Второй пост по числу просмотров в этом году, и не мой, а написанный Yamamaya. Очень подробный и насыщенный деталями, хотя это и было главным объектом критики ведущего. Тем не менее, пост информативный и второй по количеству просмотров за год.
Статистика: +24 Plus one, 29 Comments, 10591 Views.

Как редактировать в Vim без порчи и бибиканья

Снова гостевой пост, на этот раз от Pento, о текстовом редакторе, который пищит и всё портит. Несмотря на краткость и лаконичность, набрал почти 9000 просмотров и часто появляется в поисковых запросах о Vim.
Статистика: +6 Plus one, 38 Comments, 8480 Views.

И ещё немного статистики

Отбирать что-то лучшее всегда непросто, поэтому я приведу Топ5 самых посещаемых, самых комментируемых и собравших больше всех +1 постов, опубликованных в этом году. Ваше слово, товарищ Статистика!

Самые плюсадинистые


Название постаPlus oneCommentsViews
Вскрытие 9В батарейки: в поисках батареек АААА46185041
Странное рождение и долгая жизнь UNIX31275977
Pomodoro: тайм-менеджмент, который работает28263991
Краткий обзор современных окружений рабочего стола257213020
Возможности настройки GRUB2 в Linux242910591
Markdown - гаечный ключ для забивания24332699

 

Самые комментируемые

Название постаPlus oneCommentsViews
Краткий обзор современных окружений рабочего стола257213020
Вычислительный танк заряженный свободой: ThinkPad  15675854
Как редактировать в Vim без порчи и бибиканья6388480
Назад в мезозой к программным динозаврам 19372001
Markdown - гаечный ключ для забивания24332699
Консольный Mplayer: мощь без границ21333371

Самые просматриваемые


Название постаPlus oneCommentsViews
Краткий обзор современных окружений рабочего стола257213020
Возможности настройки GRUB2 в Linux242910591
Как редактировать в Vim без порчи и бибиканья6388480
Навигационные программы для Android6318227
Странное рождение и долгая жизнь UNIX31275977



Глядя в даль...

В следующем году будет... а, впрочем, кто его знает, что там будет. Много черновиков и идей, мало времени, много интересного. Приоткрывая пухлую папку черновиков, можно видеть наброски про латех, гнуплот, программирование на C и Матлаб, не очень много про железо, немного про ядро, пара забойных обзоров и ещё кое-что про Андроид. Ну и что там гостевые авторы понапишут. Кстати, ваши посты тоже тут могут засветиться - гостевые посты принимаются в любое время.


Открывая шампанское...

С новым годом Линукса на Десктопе! Чтоб никогда ничего не падало, чтоб фич было много, а багов - мало. Чтоб была свобода, Столлман, равенство и братство :-)

Ну и уж этот-то год точно станет Годом Линукса на Десктопе :-)
Читать далее

Декомпиляторы, или Что делать, если нужно восстановить исходники из бинарников?



19 коммент.
Проблема восстановления исходного кода из скомпилированных бинарников возникает сравнительно часто и протекает остро, с воем и рыданиями. Здесь нам, до некоторой степени, помогут замечательные программы-декомпиляторы, и в этом посте автор собрал свои скромные попытки выдрать исходники (или хотя бы намёки на них) из скомпилированных из С-шного кода бинарников.


Задача для декомпилятора бинарников, собранных из С кода:
Классический случай: один деятель на факультете написал на правильном ANSI C (и используя библиотеки BLAS и LAPACK) нужные и хорошие алгоритмы, и скомпилировал их в виде MEX-файлов для использования (это С-шный код, который можно вызывать из МАТЛАБ).

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

Нужно восстановить исходники если и не до компилируемого состояния, то во всяком случае выудить оттуда алгоритмы и ключевые методы, использованные при реализации.


Кратко: суть и сложность проблемы
Декомпилятор (Decompiler) пытается перевести скомпилированный бинарный файл обратно в некое подобие исходного кода. Качество выхлопа зависит от особенностей языка исходника:
  • Для C# или Java есть много декомпиляторов - байткод на java содержит  много информации. Это помогает восстанавливать декомпилятору исходник до состояния, пригодного к повторной компиляции.
  • Совершенно другая история с двоичными файлами, в которых, как правило, отладочной информации нет. Тем не менее, динамически связанные библиотеки функций, как правило, вызываются по имени. Часто, типы параметров библиотечных функций известны, и это может помочь до известных пределов. 
Декомпиляторы пытаются восстановить информацию, которая частично утрачена при компиляции в бинарный файл - в этом и заключается основная сложность.

Если вы думаете, что с помощью декомпилятора вы получите обратно красивый код на С - вы будете сильно разочарованы.  В лучшем случае на выходе будет общая (и довольно грубая) структура программы, в худшем - только имена функций и немного мусора.

Так что ответ на вопрос заголовка поста: "Обхватить голову руками и закричать #$@@@@@!" :-)


Программы для декомпилирования (decompilers) для C/C++
Декомпиляторов для C/C++ немного, и ниже список из наиболее работоспособных. Здесь нет разделения на опенсорс или Linux-only - для такого дела, как вскрытие исходников, можно (и нужно) поступиться своими светлыми идеалами и наступить на горло собственной песне.

Сразу замечу: скорее всего, ни один декомпилятор не выдаст вам сразу компилируемый код. Придётся потратить порядком времени и сил, чтобы это месиво превратить в код, который можно читать (желательно, не только компилятору).


Boomerang
Boomerang это C decompiler с открытыми исходами:
  • поддерживаемые бинарные форматы:  ELF  PE-COFF Mac-OS
  • платформы: Windows/Linux
  • поддерживаемые архитектуры: IA32 MIPS PPC
  • метод работы: поточный, есть жалкий графический интерфейс, лучше использовать CLI.
Весьма продвинутый набор алгоритмов анализа кода, что не удивительно - один из соавтором защитил на этом докторскую диссертацию [Michael James Van Emmerik, Static Single Assignment for Decompilation, Ph.D. thesis, the University of Queensland, Australia. PDF, mirror PDF].

Качество кода, выдаваемого декомпилятором:
  • Структурирование: очень хорошее
  • Переменные: хорошее
  • Типы данных: очень хорошее
Выдаваемое качество кода сильно варьируется: некоторые функции почти идеально восстановлены, хорошо видна структура кода и есть указание типа переменных. В других случаях функции сильно запутаны и их почти невозможно прочитать.

Программа всё ещё в состоянии бета-версии и для больших проектов не подходит. Скачать можно здесь.


RecStudio
Интерактивный декомпилятор RecStudio для С и (отчасти) С++, закрытая разработка:
  • поддерживаемые бинарные форматыELF  PE-COFF AOUT RAW PS-X
  • платформы: Windows/Linux/MacOS
  • поддерживаемые архитектуры: x86 (ia32) x86_64 Mips PowerPC mc68k
  • метод работы: поточный и интерактивный, есть графический интерфейс.
Использует продвинутый набор алгоритмов анализа (partial Single Static Assignment, SSA), хотя до сих пор (и это после 20 лет!) в стадии разработки. Качество кода, выдаваемого декомпилятором:
  • Структурирование: хорошее
  • Переменные: частично
  • Типы данных: частично или никак
Выдаваемое качетсво кода, как правило, хуже, чем у Boomerang, хотя обновлённый RecStudio более подробен.

Программа работает вполне стабильно, есть сборки под Linux. Скачать можно здесь.



dcc - DOS to C decompiler
Поточный декомпилятор Dcc, только ANSI C  и для exe-файлов, с открытым исходным кодом под GPL:
  • поддерживаемые бинарные форматыEXE/COM
  • платформы: Windows
  • поддерживаемые архитектуры: x86
  • метод работы: поточный.
Один из первых декомпиляторов вообще, и только под DOS. Сильная сторона - структурирование кода. Качество кода, выдаваемого декомпилятором:
  • Структурирование: хорошее
  • Переменные: частично
  • Типы данных: частично или никак
Разработка Cristina Cifuentes, которая защитила PhD в Queensland University of Technology  на этом деле [можно полистать, если что - C. Cifuentes, Reverse Compilation Techniques].

Скачать можно отсюда (на сайте университета).



Hex Rays - plugin для IDA Pro
На самом деле Hex Rays не является отдельной программой - это плагин-декомпилятор для IDA Pro. Комбинация продвинутых возможностей IDA Pro (это дизассемблер) и Hex Rays в качестве декомпилятора очень впечатляет, как и аэрокосмическая цена.
По причине закрытости продукта (нет даже демо-версии) и нереальной цены в этом разделе про Hex Rays больше ничего написано не будет.




Ходовые испытания в реальных условиях
Для начала попробуем декомпилировать что-нибудь совсем простенькое и написанное на ANSI C и с использованем библиотеки BLAS для векторых и матричных операций. Бинарный файл можно скачать здесь.



1. Простенький C-шный бинарник + BLAS
Собственно, код на C для перемножения матрицы и вектора (используется CBLAS). Исходник:


#include <stdio.h>
#include <cblas.h>

double m[] = {
  3, 1, 3,
  1, 5, 9,
  2, 6, 5
};

double x[] = {
  -1, -1, 1
};

double y[] = {
  0, 0, 0
};

int main(void)
{
  int i, j;

  for (i=0; i<3; ++i) {
    for (j=0; j<3; ++j) printf("%5.1f", m[i*3+j]);
    putchar('\n');
  }

  cblas_dgemv(CblasRowMajor, CblasNoTrans, 3, 3, 1.0, m, 3, x, 1, 0.0, y, 1);


  for (i=0; i<3; ++i)  printf("%5.1f\n", y[i]);

  return 0;
}

После перемножения выдаст результат на консоль.

Выхлоп Boomerang
У него много ключей и параметров, часть которых не знает даже официальная, скажем так, документация. Тем не менее, для ключа -Td (Use data-flow-based type analysis) мы имеем выхлоп в стиле дзен:

double y;
double m = 3.;

// address: 0x80484e4
int main(int argc, char **argv, char **envp) {
    void *local23;   // r28

    for(;;) {
        proc1();
    }
}

Скажем так, не слишком ободряюще. Ключ  -Tc (Use old constraint-based type analysis) выдаёт больше информации к размышлению:

char y[24];
long long m[9] = { 0x4008000000000000LL, 0x3ff0000000000000LL, 0x4008000000000000LL, 0x3ff0000000000000LL, 0x4014000000000000LL, 0x4022000000000000LL, 0x4000000000000000LL, 0x4018000000000000LL, 0x4014000000000000LL };

// address: 0x80484e4
int main(int argc, char **argv, char **envp) {
    int local10;   // r24
    int local13;   // r28
    int local14;   // r29
    int local15;   // r32
    int local2;   // m[r28 + 72]{60}
    int local3;   // r28{181}
    int local6;   // m[r28 + 72]{119}
    int local7;   // m[r28 + 72]{149}
    int local8;   // m[r28 + 76]{9}
    int local9;   // m[r28 + 76]{49}

    *(int*)(local13 - 4) = local14;
    *(int*)(local13 - 12) = 0;
    local3 = local13 - 84;
    if (*(int*)(local3 + 72) > 2) {
        *(int*)(local3 + 52) = 1;
        *(int*)(local3 + 48) = 0x8049868;
        *(long long*)(local3 + 40) = 0.;
        *(int*)(local3 + 36) = 1;
        *(int*)(local3 + 32) = 0x8049848;
        *(int*)(local3 + 28) = 3;
        *(int*)(local3 + 24) = 0x8049800;
        *(long long*)(local3 + 16) = 1.;
        *(int*)(local3 + 12) = 3;
        *(int*)(local3 + 8) = 3;
        *(int*)(local3 + 4) = 111;
        *(int*)local3 = 101;
        cblas_dgemv();
        local6 = 0;
        for(;;) {
            *(long long*)(local3 + 4) = y[0];
            *(int*)local3 = 0x80486b6;
            proc1();
            local7 = *(int*)(local3 + 72) + 1;
        }
    }
    local8 = 0;
    for(;;) {
        local10 = *(int*)(local3 + 72) + *(int*)(local3 + 72) + *(int*)(local3 + 72);
        local15 = m[local10];
        *(long long*)(local3 + 4) = local15;
        *(int*)local3 = 0x80486b0;
        proc1();
        local9 = *(int*)(local3 + 76) + 1;
    }
} 
 

Подробностей тут больше, и тут выловлен самый главный ключик - cblas_dgemv();    


Выхлоп RecStudio
Намного более обилен и представляет собой следующий поток сознания:


// Generated by Rec Studio 4 - build Oct 20 2012

_init()
{// addr = 0x08048398
    _unknown_ __ebx;                       // r1
    _unknown_ __ebp;                       // r6
    _unknown_ _t2;                         // _t2

    __esp = __esp - 4;
    L1();
    _pop(__ebx);
    if( *((intOrPtr*)(_t2 + 0x1414)) != 0) {
        __gmon_start__();
    }
    frame_dummy();
    __do_global_ctors_aux();
    _pop(__eax);
    return;
}

L080483A4()
{
    _unknown_ _t2;                         // _t2

    _pop(__ebx);
    if( *((intOrPtr*)(_t2 + 0x1414)) != 0) {
        __gmon_start__();
    }
    frame_dummy();
    __do_global_ctors_aux();
    _pop(__eax);
    _pop(__ebx);
    __esp = __ebp;
    _pop(__ebp);
    return;
}

__gmon_start__()
{// addr = 0x080483D8
    goto __imp____gmon_start__;
}

putchar()
{// addr = 0x080483E8
    goto __imp__putchar;
}

__libc_start_main()
{// addr = 0x080483F8
    goto __imp____libc_start_main;
}

cblas_dgemv()
{// addr = 0x08048408
    goto __imp__cblas_dgemv;
}

printf()
{// addr = 0x08048418
    goto __imp__printf;
}

_start(
    signed int __eax,                      // r0
    _unknown_ __edx                        // r3
)
{// addr = 0x08048430
    _unknown_ __ebx;                       // r1
    signed int _t5;                        // _t5
    _unknown_ _t6;                         // _t6
    _unknown_ _t10;                        // _t10

    __edx = __edx;
    _t4 = __eax;
    _pop(__esi);
    __ecx = __esp;
    __esp = __esp & 240;
    _push(__eax);
    _push(__esp);
    _push(__edx);
    _push(__libc_csu_fini);
    _push(__libc_csu_init);
    _push(__ecx);
    _push(_t10);
    _push(main);
    __libc_start_main();
    asm("hlt ");
    0;
    0;
    _push(0);
    _push(_t6);
    __esp = __esp - 4;
    if(completed.5982 != 0) {
    } else {
        _t4 = dtor_idx.5984;
        _t6 = ( &__DTOR_END__ -  &__DTOR_LIST__ >> 2) - 1;
        if(_t4 >= _t6) {
        } else {
            do {
                _t5 = _t4 + 1;
                dtor_idx.5984 = _t5;
                 *((intOrPtr*)(_t5 * 4 +  &__DTOR_LIST__))();
                _t4 = dtor_idx.5984;
            } while(_t4 < _t6);
        }
        completed.5982 = 1;
    }
    __esp = __esp + 4;
    _pop(__ebx);
    _pop(__ebp);
    return;
}

__do_global_dtors_aux(
    _unknown_ __esi                        // r5
)
{// addr = 0x08048460
    _unknown_ __ebx;                       // r1
    _unknown_ __ebp;                       // r6
    _unknown_ _t4;                         // _t4
    signed int _t5;                        // _t5
    signed int _t6;                        // _t6
    _unknown_ _t10;                        // _t10

    if(completed.5982 == 0) {
        _t5 = dtor_idx.5984;
        _t10 = ( &__DTOR_END__ -  &__DTOR_LIST__ >> 2) - 1;
        if(_t5 >= _t10) {
L4:
            completed.5982 = 1;
            return;
        }
        do {
            _t6 = _t5 + 1;
            dtor_idx.5984 = _t6;
             *((intOrPtr*)(_t6 * 4 +  &__DTOR_LIST__))();
            _t5 = dtor_idx.5984;
        } while(_t5 < _t10);
        goto L4;
    }
    return;
}

frame_dummy()
{// addr = 0x080484C0
    _unknown_ __ebp;                       // r6

    __eax = __JCR_LIST__;
    if(__JCR_LIST__ == 0) {
    } else {
        __eax = 0;
        if(__eax != 0) {
             *__esp =  &__JCR_LIST__;
             *__eax();
            return;
        }
    }
    return;
}

main(
    _unknown_ __fp0                        // r28
)
{// addr = 0x080484E4
    signed int _v8;                        // _cfa_fffffff8
    signed int _v12;                       // _cfa_fffffff4
    intOrPtr _v32;                         // _cfa_ffffffe0
    char* _v36;                            // _cfa_ffffffdc
    intOrPtr _v48;                         // _cfa_ffffffd0
    char* _v52;                            // _cfa_ffffffcc
    intOrPtr _v56;                         // _cfa_ffffffc8
    char* _v60;                            // _cfa_ffffffc4
    intOrPtr _v72;                         // _cfa_ffffffb8
    intOrPtr _v76;                         // _cfa_ffffffb4
    intOrPtr _v80;                         // _cfa_ffffffb0
    _unknown_ __ebp;                       // r6

    __fp0 = __fp0;
    __esp = __esp & 240;
    __esp = __esp - 80;
    _v12 = 0;
    while(_v12 <= 2) {
        _v8 = 0;
        while(_v8 <= 2) {
            __fp0 ?_?  *((long long*)((_v12 + __edx + __edx + _v8) * 8 +  &m));
            asm("fstp qword [esp+0x4]");
             *__esp = 134514352;
            printf();
            _v8 = _v8 + 1;
        }
         *__esp = 10;
        putchar();
        _v12 = _v12 + 1;
    }
    _v32 = 1;
    _v36 =  &y;
    asm("fldz ");
    asm("fstp qword [esp+0x28]");
    _v48 = 1;
    _v52 =  &x;
    _v56 = 3;
    _v60 =  &m;
    asm("fld1 ");
    asm("fstp qword [esp+0x10]");
    _v72 = 3;
    _v76 = 3;
    _v80 = 111;
     *__esp = 101;
    cblas_dgemv();
    _v12 = 0;
    while(_v12 <= 2) {
        __fp0 ?_?  *((long long*)(_v12 * 8 +  &y));
        asm("fstp qword [esp+0x4]");
         *__esp = "%5.1f\n";
        printf();
        _v12 = _v12 + 1;
    }
    return 0;
}

__libc_csu_fini()
{// addr = 0x080485F0
    _unknown_ __ebp;                       // r6

    return;
}

__libc_csu_init(
    intOrPtr _a4,                          // _cfa_4
    intOrPtr _a8,                          // _cfa_8
    intOrPtr _a12                          // _cfa_c
)
{// addr = 0x08048600
    intOrPtr _v36;                         // _cfa_ffffffdc
    intOrPtr _v40;                         // _cfa_ffffffd8
    _unknown_ __ebx;                       // r1
    _unknown_ __edi;                       // r4
    signed int __esi;                      // r5
    _unknown_ __ebp;                       // r6
    _unknown_ _t14;                        // _t14
    _unknown_ _t15;                        // _t15
    signed int _t18;                       // _t18

    __i686.get_pc_thunk.bx();
    _t15 = _t14 + 4529;
    __esp = __esp - 28;
    _init();
    _t18 = _t15 + -248 - _t15 + -248 >> 2;
    if(_t18 == 0) {
    } else {
        __esi = 0;
        do {
            _v36 = _a12;
            _v40 = _a8;
             *__esp = _a4;
             *((intOrPtr*)(_t15 + -248 + __esi * 4))();
            __esi = __esi + 1;
        } while(__esi < _t18);
    }
    __esp = __esp + 28;
    return;
}

__i686.get_pc_thunk.bx()
{// addr = 0x0804865A
    return;
}

__do_global_ctors_aux()
{// addr = 0x08048660
    intOrPtr* __ebx;                       // r1
    _unknown_ __ebp;                       // r6

    __eax = __CTOR_LIST__;
    if(__eax == 255) {
    } else {
        __ebx =  &__CTOR_LIST__;
        asm("o16 nop ");
        do {
            __ebx = __ebx - 4;
             *__eax();
            __eax =  *__ebx;
        } while(__eax != 255);
    }
    return;
}

_fini()
{// addr = 0x0804868C
    _unknown_ __ebx;                       // r1
    _unknown_ __ebp;                       // r6
    _unknown_ _t1;                         // _t1

    __esp = __esp - 4;
    L1();
    _pop(__ebx);
    __do_global_dtors_aux(__esi);
    _pop(__ecx);
    return;
}

L08048698()
{
    _unknown_ _t1;                         // _t1

    _pop(__ebx);
    __do_global_dtors_aux(__esi);
    _pop(__ecx);
    _pop(__ebx);
    __esp = __ebp;
    _pop(__ebp);
    return;
}

L08048698()
{
    _unknown_ _t1;                         // _t1

    _pop(__ebx);
    @rec __do_global_dtors_aux@__do_global_dtors_aux@(__esi);
    _pop(__ecx);
    _pop(__ebx);
    __esp = __ebp;
    _pop(__ebp);
    return;
}

// Statistics:
//      74 Register nodes
//      35 Temporaries nodes
//       5 Casts
//     207 Statements
//       2 Labels
//       1 Gotos
//      17 Blocks
//     469 Nodes
//      10 Assembly nodes
//      27 Unknown Types


Total time: 0 seconds.


Структура программы (вначале) в общем несколько лучше, чем у Boomerang, и куда больше подробностей.



2. Бен, ай нид хелп: MEX-файл, написанный на C  + BLAS, исходников которому нет.
Этот пример в посте приводить не стану, так как он длинный, но желающим попробовать своё декомпиляйшн-кунфу такая возможность предоставится:
Некоторые входные данные: это оптимизационный алгоритм для Quadratic Programming типа Branch-and-Bound (почитать тут и здесь). Алгоритм в целом прост и незатейлив, но самая сложная часть в нём - определить lower/upper bound через решение упрощённой оптимизационной задачи, и делать это быстро. Как такое сделать - хороший вопрос, и именно он меня интересует более всего. 

Короче, важен не столько алгоритм, сколько его составные компоненты (стратегия и подпрограммы для lower bound estimation).

Автор этих строк, поковыряв выхлоп RecStudio, нашёл для себя подсказку на строчке 12319:
qps_mq_sub( .....

и особенно на строчке 12088:
getalp( ....
что позволило автору предположить, что для lower/upper bounds  использутеся Liner Programming.  Не слишком много, но по крайней мере понятно, в какую сторону копать.

Если у кого-то вдруг проявится желание потыкать в оный бинарник палочкой, IDA Pro и ещё чем - не стесняйтесь отписываться в комментариях.
Обновление: теперь можно сравнить выдачи Boomerang, RecStudio, и (спасибо, Григорий!) IDA Pro. В самом деле, выдача IDA Pro куда лучше того, что дают остальные, особенно boomerang. Можно выудить (до некоторой степени) структуру программы и даже сообщения об ошибках.


Ссылки
Интересующийся читатель может попробовать полистать вебстраницы автора RecStudio с полезной информацией, сходить на wiki-ресурс по обратной разработке.

Помимо познавательных диссертаций Michael James Van Emmerik (Boomerang) и Cristina Cifuentes [C. Cifuentes, Reverse Compilation Techniques], есть хорошие книжки по теме:
  • "Compilers - Principles, Techniques and Tools", Aho, Sethi, Ullman, 1986 Addison-Wesley Publishing Co.  ISBN 0-201-10088-6.
  • "Advanced Compiler Design & Implementation", Steven Muchnick, 1997 Morgan Kaufmann Publishers, ISBN 1-55860-320-4.
  • "How debuggers work - Algorithms, Data Structures, and Architecture", Jonathan Rosemberg, 1996 John Wiley and Sons, ISBN 0-471-14966-7.

Дело это интересное, но весьма утомительное, хотя может помочь при раскопках очередного legacy-software и сэкономить вам полжизни.
Читать далее

Как оформить исходный код программ в LaTeX без адских страданий



32 коммент.
Иногда в документ LaTeX нужно вставить кусок программного кода или псевдокод, поясняющий алгоритм. В зависимости от языка программирования и целей, подсветка кода в LaTeX достигается подключаемыми пакетами расширений разной степени кривизны и безумия документации.

Пост подвергался чистке и правке после публикации:
Автор заходил править этот пост 16 августа 2013 года.

Читать далее