Сопоставление исходного кода с листингом сборки программы C++

Анализ дампа ядра в розничной сборке часто требует сопоставления objdump любого конкретного модуля и источника. Обычно сопоставление дампа сборки с исходным кодом становится проблемой, если функция достаточно вовлечена. Сегодня я попытался создать assembly listing одного конкретного модуля (с опцией компиляции -S), ожидая, что увижу чередующийся источник со сборкой или какую-то корреляцию. К сожалению, список не был достаточно дружелюбным, чтобы сопоставить, поэтому мне было интересно

  • Учитывая дамп ядра, из которого я могу определить место падения
  • objdump списка сборки неисправного модуля, перекомпилировав
  • модуль с опцией -S.

Можно ли вести индивидуальную переписку с источником?

В качестве примера я вижу листинг сборки как

.LBE7923:
        .loc 2 4863 0
        movq    %rdi, %r14
        movl    %esi, %r12d
        movl    696(%rsp), %r15d
        movq    704(%rsp), %rbp
.LBB7924:
        .loc 2 4880 0
        testq   %rdx, %rdx
        je      .L2680
.LVL2123:
        testl   %ecx, %ecx
        jle     .L2680
        movslq  %ecx,%rax
        .loc 2 4882 0
        testl   %r15d, %r15d
        .loc 2 4880 0
        leaq    (%rax,%rax,4), %rax
        leaq    -40(%rdx,%rax,8), %rdx
        movq    %rdx, 64(%rsp)

но не мог понять, как интерпретировать метки типа .LVL2123 и директивы типа .loc 2 4863 0

Примечание Как показано в ответах, чтение исходного кода сборки и интуитивное определение шаблона на основе символов (таких как вызовы функций, ветки, оператор return) — это то, что я обычно делаю. Я не отрицаю, что это не работает, но когда функция довольно вовлечена, чтение страниц листинга сборки вызывает боль, и часто вы получаете листинг, который редко совпадает либо из-за встроенных функций, либо из-за того, что оптимизатор просто выбросил код, как он хотел. У меня такое ощущение, что, видя, насколько эффективно Valgrind обрабатывает оптимизированные двоичные файлы и как в Windows WinDBG может обрабатывать оптимизированные двоичные файлы, я что-то упускаю. Поэтому я решил начать с вывода компилятора и использовать его для корреляции. Если мой компилятор несет ответственность за искажение двоичного файла, лучше всего будет сказать, как соотнести его с исходным кодом, но, к сожалению, это было наименее полезным, а .loc действительно вводит в заблуждение. К сожалению, мне часто приходится читать невоспроизводимые дампы на разных платформах, и меньше всего времени я трачу на отладку мини-дампов Windows через WinDBG и значительное время на отладку Linux Coredumps. Хотя, может быть, я что-то делаю не так, поэтому я придумал этот вопрос.


person Abhijit    schedule 02.05.2012    source источник
comment
Это не ответ на ваш вопрос, но в любом случае может быть полезен: msdn.microsoft.com/en-us/library/aa238730%28v=vs.60%29.aspx   -  person João Fernandes    schedule 02.05.2012
comment
В дампе ядра должны быть адреса. Так что попробуйте программу addr2line перевести в исходные локации. Для этого, конечно, требуется исполняемый файл с символами отладки (он должен работать, даже если ваша распределенная версия была удалена, просто сравните с неразделенной версией)   -  person edA-qa mort-ora-y    schedule 02.05.2012
comment
@edA-qamort-ora-y: я попробую это и дам вам знать, на чем закончу. Кстати, разве это не должен быть ответ, а не комментарий?   -  person Abhijit    schedule 02.05.2012
comment
@Abhijit, мне было неясно, это именно то, что вы ищете. Я также не делал этот точный сценарий, поэтому я не могу быть уверен, что он работает.   -  person edA-qa mort-ora-y    schedule 03.05.2012


Ответы (3)


Можно ли вести индивидуальную переписку с источником?

О: нет, если только не отключена вся оптимизация. Первоначально компилятор может выдать некоторую группу инструкций (или подобных инструкций) в строке, но затем оптимизатор переупорядочивает, разделяет, объединяет и вообще полностью меняет их.


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

.LBB7924:
        .loc 2 4880 0
        testq   %rdx, %rdx
        je      .L2680

выглядит как ветвь, если %rdx равно нулю, и исходит из строки 4880. Найдите строку, определите тестируемую переменную, отметьте, что в настоящее время она назначена %rdx.

.LVL2123:
        testl   %ecx, %ecx
        jle     .L2680

Итак, этот тест и ветвь имеют одну и ту же цель, поэтому все, что будет дальше, будет знать, что %rdx и %ecx оба отличны от нуля. Исходный код может быть структурирован следующим образом:

if (a && b) {

или, возможно, это было:

if (!a || !b) {

и оптимизатор переупорядочил две ветки...

Теперь у вас есть некоторая структура, которую вы, надеюсь, сможете сопоставить с исходным кодом, вы также можете выяснить назначение регистров. Например, если вы знаете, что тестируемая вещь является элементом данных некоторой структуры, прочитайте назад, чтобы увидеть, где %rdx был загружен из памяти: был ли он загружен с фиксированного смещения в какой-то другой регистр? Если да, то этот регистр, вероятно, является адресом объекта.

Удачи!

person Useless    schedule 02.05.2012
comment
+1 за подробное руководство. Это действительно замечательно. Но, к сожалению, это и в основном то, что упомянул @Chris, - это то, что я делаю, чтобы прочитать дамп. Но, согласитесь, чтение страниц дампов иногда может стать настоящей головной болью, особенно когда функция встроена. Например, сегодня я трачу почти час, чтобы прочитать почти 1000 строк ассемблера. У меня было предчувствие, что должно быть что-то, что я упускаю, учитывая, насколько эффективно и почти предсказуемо Valgrind сопоставляет полностью оптимизированный двоичный файл с исходным кодом, если вы компилируете с опцией -g (с символами). - person Abhijit; 02.05.2012
comment
Что ж, метка .loc может отображать группу инструкций в строку кода, и, предположительно, valgrind может использовать ее для присвоения приблизительной стоимости инструкций строке исходного кода. Однако это не то же самое, что выяснить причину сбоя программы: вы пытаетесь восстановить логическое состояние программы, которое оптимизатор преобразовал и отбросил. Это сложнее и не требуется для учета valgrind. - person Useless; 02.05.2012

Директива .loc — это то, что вам нужно. Они обозначают строки #4863, 4880 и т. д. Не существует идеального соответствия между исходным кодом и оптимизированным ассемблером (именно поэтому вы видите 4880 более одного раза). Но .loc это то, как вы узнаете, где оно находится в файле. Синтаксис:

.loc <file> <line> <column>
person Rob Napier    schedule 02.05.2012
comment
Есть ли что-нибудь еще, что я могу сделать здесь? Например, у .loc есть два других числа, такие как 2 и 0. Что это? А что это за метки .LVL2123 и .LBE7923? - person Abhijit; 02.05.2012
comment
Обычно это goto цели, выдаваемые компилятором. Например, у вас есть je .L2680, поэтому где-то должна быть строка, начинающаяся с .L2680:. - person Useless; 02.05.2012

Если вы не используете статические ссылки на системные библиотеки, даже без символов отладки в двоичном файле будут символические имена - имена функций системной библиотеки, с которыми связаны.

Это часто может помочь сузить ваше место в коде. Например, если вы видите, что в функции foo() она вызывает open(), а затем ioctl(), а затем происходит сбой прямо перед вызовом read(), вы, вероятно, легко найдете эту точку в исходном коде foo. (В этом отношении вам может даже не понадобиться дамп - в Linux вы можете получить запись о возникновении сбоя относительно библиотечных и системных функций, используя ltrace или strace)

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

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

person Chris Stratton    schedule 02.05.2012
comment
К сожалению, мне часто приходится отлаживать невоспроизводимые дампы в разных системах, и то, что вы упомянули, это то, как я это делаю, и это работает, но мне приходится вкладывать много усилий и времени. Я подсчитал, что отладка мини-дампа Windows занимает гораздо меньше времени (через WinDBG) по сравнению с отладкой дампа ядра Linux. Даже мучительно отлаживать дампы Solaris/AIX/HP-UX. Увидев, насколько эффективно Valgrind сопоставляет оптимизированный двоичный файл с исходным кодом, я стал очень амбициозным и оптимистичным в отношении определения чего-то более эффективного. У @edA-qa mort-ora-y было предложение, которое мне еще предстоит попробовать, и сообщить вам, на чем я заканчиваю :-) - person Abhijit; 02.05.2012