Как перейти от инструкции на ассемблере к коду на C

У меня есть задание, где, среди прочего, мне нужно посмотреть в файле .asm, чтобы найти определенную инструкцию и «обратно разработать» (узнать), какая часть кода C вызывает ее выполнение на уровне ассемблера. (пример под текстом)

Каким будет самый быстрый (простой) способ сделать это. Или, лучше сказать, на какие другие команды/инструкции/метки, находящиеся вокруг него в .asm-файле, следует/могу ли я обратить внимание, чтобы привести меня к правильному коду C?

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

Архитектура, если это имеет значение, — TriCore.

Пример: мне удалось выяснить, какой код C вызывает вставку в ассемблерный файл, проследив, где используются переменные.

 .L23:
    movh.a  a15,#@his(InsertStruct)
    ld.bu   d15,[a15]@los(InsertStruct)
    or  d15,#1
    st.b    [a15]@los(InsertStruct),d15
.L51:
    ld.bu   d15,[a15]@los(InsertStruct)
    insert  d15,d15,#0,#0,#1
    st.b    [a15]@los(InsertStruct),d15
.L17:
    mov d15,#-1

это привело меня к следующему коду C:

InsertStruct.SomeMember = 0x1u;

InsertStruct.SomeMember = 0x0u;

person vandelfi    schedule 18.12.2017    source источник
comment
Идея состоит в том, что вы демонстрируете понимание C, компиляторов и ассемблеров. Есть инструменты, которые могут сделать это за вас, иногда называемые дизассемблером. Однако результат в большинстве случаев разочаровывает и иногда менее понятен, чем код на ассемблере для опытного программиста на ассемблере.   -  person Yunnosch    schedule 18.12.2017
comment
Вы должны работать через упражнение, а не обходить его. Целью этого является улучшение ваших (отсутствующих) навыков сборки и незаменимой способности понимать и повторно реализовывать алгоритм. Итак: вы должны обращать внимание на каждую строку, и самый быстрый способ - это понимать ее.   -  person Margaret Bloom    schedule 18.12.2017
comment
какие именно строки кода C вызывают выполнение конкретной инструкции. - точный весь исходный код вызывает выполнение всей сборки. Нет прямого отображения строки в строку, по крайней мере, в оптимизированном коде, иногда оптимизатор значительно меняет алгоритм, например, выполняя x / 15 путем умножения или удаляя значения суммирования всего цикла путем прямого вычисления результата и т. д. Если вы попытаетесь восстановить исходный код C из такой сборки, вы получите совершенно другой источник (с точки зрения алгоритма).   -  person Ped7g    schedule 18.12.2017
comment
@MargaretBloom Чтение ассемблера, сгенерированного компилятором, не обязательно является хорошим способом обучения. Такой код часто бывает очень странным и трудно читаемым.   -  person Lundin    schedule 18.12.2017
comment
Спасибо за ответ, но это, к сожалению, не помогает, и я понимаю, что это очень общий вопрос. К сожалению, это не упражнение, а работа. Я должен исправить существующий тест набора инструкций, который не проверяет все используемые инструкции. Итак, мне нужно просмотреть ассемблерный файл одного уровня кода и выяснить, какой код C вызывает выполнение инструкции, чтобы я мог использовать ее в своем патче. @ Лундин, да, это очень трудно читать, учитывая то, что сказал Ped7g. Он оптимизирован, и одна строка кода C не означает, что используется определенная инструкция. Взаимодействие, так сказать, является ключевым.   -  person vandelfi    schedule 18.12.2017
comment
Если вы хотите протестировать все инструкции в ISA, надеяться, что вы сможете убедить компилятор C сгенерировать их каким-то образом, — это совершенно неправильный подход. Следующая версия компилятора или изменение где-то константы может привести к другому кодогенерированию. Если вам нужен конкретный asm, пишите на asm.   -  person Peter Cordes    schedule 18.12.2017
comment
@MargaretBloom Я сомневаюсь, что кто-то действительно будет использовать TriCore в качестве архитектуры для упражнений.   -  person Martin Rosenau    schedule 18.12.2017
comment
@Yunnosch: Вы имеете в виду декомпилятор. Дизассемблер это машинный код -> ассемблерный текст. reverseengineering.stackexchange.com/questions/tagged/   -  person Peter Cordes    schedule 18.12.2017
comment
@PeterCordes Я, наверное, перепутал, или кто-то использует этот термин по-другому ...   -  person Yunnosch    schedule 18.12.2017
comment
@MartinRosenau Так и должно быть, иначе я больше никогда не буду чувствовать себя в безопасности в машине :)   -  person Margaret Bloom    schedule 18.12.2017
comment
@MargaretBloom подождите, вы все еще чувствуете себя в безопасности среди всех этих IoT и автономных вещей, которые подкрадываются? :D вау. Для меня просто наклеить на них умную наклейку никогда не было достаточно, я имею в виду, что они, безусловно, намного умнее людей в определенном арифметическом смысле, но это хорошо сочетается с реальной жизнью только примерно в 99,9% случаев. :)   -  person Ped7g    schedule 18.12.2017


Ответы (2)


Архитектура TriCore (если это имеет значение).

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

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

При использовании высокооптимизирующего компилятора у вас почти нет шансов:

Например, компилятор Tasking для TriCore иногда даже генерирует один фрагмент ассемблерного кода (сохраняемый только один раз в памяти!) для двух разных строк кода C в двух разных файлах C!

Однако код в вашем примере не оптимизирован (если только структура, которую вы назвали InsertStruct, не является volatile).

В этом случае вы можете скомпилировать свой код с включенной отладочной информацией и извлечь отладочную информацию: из файла формата ELF вы можете использовать такие инструменты, как addr2line (бесплатное программное обеспечение из набора компиляторов GNU), чтобы проверить, какая строка кода C соответствует инструкции в определенный адрес.

(Примечание. Инструмент addr2line не зависит от архитектуры, если обе архитектуры имеют одинаковую ширину (32 бита), одинаковый порядок байтов и обе используют формат файла ELF; вы можете использовать addr2line для ARM, чтобы получить информацию из файла TriCore.)

Если вам действительно нужно понять фрагмент кода на ассемблере, я сам обычно делаю следующее:

Я запускаю текстовый редактор и вставляю код на ассемблере:

movh.a  a15,#@his(InsertStruct)
ld.bu   d15,[a15]@los(InsertStruct)
or      d15,#1
st.b    [a15]@los(InsertStruct),d15
...

Затем я заменяю каждую инструкцию эквивалентом псевдокода:

a15 =  ((((unsigned)&InsertStruct)>>16)<<16;
d15 =  *(unsigned char *)(a15 + (((unsigned)&InsertStruct)&0xFFFF));
d15 |= 1;
*(unsigned char *)(a15 + (((unsigned)&InsertStruct)&0xFFFF)) = d15;
...

На следующем шаге я пытаюсь упростить этот код:

a15 =  ((unsigned)&InsertStruct) & 0xFFFF0000;

Затем:

d15 = *(unsigned char *)((((unsigned)&InsertStruct) & 0xFFFF0000) + (((unsigned)&InsertStruct)&0xFFFF));
...

Затем:

d15 = *(unsigned char *)((unsigned)&InsertStruct);
...

Затем:

d15 = *(unsigned char *)&InsertStruct;
...

В конце концов я пытаюсь заменить инструкции перехода:

d15 = 0;
if(d14 == d13) goto L123;
d15 = 1;
L123:

... становится:

d15 = 0;
if(d14 != d13) d15 = 1;

... и наконец (возможно):

d15 = (d14 != d13);

В конце у вас есть код C в текстовом редакторе.

К сожалению, это занимает много времени, но я не знаю более быстрого метода.

person Martin Rosenau    schedule 18.12.2017
comment
Вы несколько усложняете материал с a15. Вы на самом деле не думаете об этих инструкциях как об изменении адреса с операторами C, не так ли? Я имею в виду, unsigned char *a15 = hi(&InsertStruct) / unsigned d15 = a15[lo(InsertStruct)]. Или просто перейдите непосредственно к более разумному C, такому как d15 = InsertStruct.SomeMember;, как вы думаете об этом, когда поймете, как компиляторы работают со статическими адресными константами на машинах с инструкциями фиксированной ширины. - person Peter Cordes; 18.12.2017
comment
@ Мартин Розенау Спасибо, что нашли время ответить. Я принимаю это как ответ, учитывая, что вы предоставили два метода, которые я мог бы использовать для решения моей проблемы. Я сомневаюсь, что это сильно поможет без предварительного знания ассемблера, но это лучше, чем ничего. Спасибо еще раз. - person vandelfi; 18.12.2017

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

Ваша цель безумна, а первая половина вашего вопроса имеет обратное значение/лишь слабое отношение к вашей реальной проблеме.

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

Если вы хотите протестировать все инструкции в ISA, надеяться, что вы сможете убедить компилятор C сгенерировать их каким-то образом, — это совершенно неправильный подход. Вы хотите, чтобы ваш тест продолжал проверять одно и то же в будущем, поэтому вы должны . Если вам нужен конкретный asm, пишите на asm.

Это тот же вопрос, заданный пару недель назад для ARM: оптимизация-будет-ди">Как заставить IAR использовать нужные инструкции Cortex-M0+ (оптимизация для этой функции будет отключена) за исключением того, что вы говорите, что собираетесь строить с включенной оптимизацией (что может упрощают получение более широкого диапазона генерируемых инструкций: некоторые из них можно использовать только в качестве глазок-оптимизаторов по сравнению с простым обычным генерированием кода).


Кроме того, начиная с asm и превращая его в эквивалентный C, нет гарантии, что компилятор выберет эту инструкцию при компиляции, поэтому заголовок вопроса лишь слабо связан с вашей реальной проблемой.


Если вы все еще хотите заставить компилятор генерировать конкретный ассемблер, чтобы создать хрупкий исходный код, который может делать только то, что вы хотите, с очень специфическими компилятором / версией / параметрами, первым шагом будет подумайте: «когда эта инструкция станет частью оптимизированного способа выполнения чего-либо?».

Обычно этот ход мыслей более полезен для оптимизации путем настройки исходного кода для более эффективной компиляции. Сначала вы думаете об эффективной реализации функции, которую вы пишете, на ассемблере. Затем вы пишете свой исходный код на C или C++ таким же образом, т. е. используя те же временные файлы, которые, как вы надеетесь, будет использовать компилятор. Например, см. >Каков эффективный способ подсчета установленных битов в позиции или ниже? где я смог заставить gcc использовать более эффективную последовательность инструкций, как это делал clang в моей первой попытке.

Иногда это может сработать; для ваших целей это просто, когда в наборе инструкций есть только один действительно хороший способ что-то сделать. например ld.bu выглядит как байтовая загрузка с нулевым расширением (u для беззнакового) в полный регистр. unsigned foo(unsigned char*p) {return *p;} должен компилироваться в это, и вы можете использовать атрибут noinline, чтобы предотвратить его оптимизацию.

Но insert, если это вставка нулевого бита в битовое поле, с тем же успехом могло бы быть and с ~1 (0xFE), предполагая, что TriCore имеет и-немедленное. Если insert имеет не непосредственную форму, это, вероятно, самый эффективный вариант для single-bit bitfield = rand() (или любого значения, которое все еще не является константой времени компиляции после оптимизации с распространением констант).

Для упакованных арифметических инструкций TriCores (SIMD) вам понадобится компилятор для автоматической векторизации или использования встроенного.

В ISA вполне могут быть некоторые инструкции, которые ваш компилятор никогда не выдаст. Хотя я думаю, что вы только пытаетесь проверить инструкции, которые компилятор выдает в других частях вашего кода? Вы говорите «все используемые инструкции», а не «все инструкции», чтобы хотя бы гарантировать, что задача возможна.


Не встроенная функция с аргументом — отличный способ принудительно сгенерировать код для переменных времени выполнения. Те, кто использует asm, сгенерированный компилятором, часто пишут небольшие функции, которые принимают аргументы и возвращают значение (или сохраняют в глобальную или volatile), чтобы заставить компиляцию генерировать код для чего-то, не отбрасывая результат и не превращая распространение констант. всю функцию в return 42;, т. е. mov-непосредственное / ret. См. Как удалить шум из вывода сборки GCC/clang? подробнее об этом, а также выступление Мэтта Годболта на CppCon2017: «Что мой компилятор сделал для меня в последнее время? Unbolting the Compiler's Lid» для отличного введения новичка в чтение ассемблерного кода, сгенерированного компилятором, и того, что современные оптимизирующие компиляторы делают для небольших функций.

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

int main(void) {
    volatile int vtmp = 123;
    int my_parameter = vtmp;

    ... then use my_parameter, not vtmp, so CSE and other optimizations can still work
 }

[...] Он оптимизирован

Показанный вами вывод компилятора определенно не выглядит оптимизированным. Это похоже на загрузку/установку бита/сохранение, затем загрузку/очистку бита/сохранение, которое должно быть оптимизировано до простой загрузки/очистки бита/сохранения. Если только эти ассемблерные блоки не были на самом деле непрерывными, и вы показываете код из двух разных блоков, склеенных вместе.

Кроме того, InsertStruct.SomeMember = 0x0u; — неполное описание: очевидно, оно зависит от определения структуры; Я предполагаю, что вы использовали однобитовое битовое поле int SomeMember :1;? Согласно справочному руководству TriCore ISA, которое я нашел, insert копирует диапазон битов из одного регистра в другой в указанной позиции вставки и поступает в форме регистра и непосредственного источника.

Замена целого байта может быть просто хранилищем вместо чтения/изменения/записи. Таким образом, ключевым моментом здесь является определение структуры, а не только оператор, скомпилированный в инструкцию.

person Peter Cordes    schedule 18.12.2017
comment
хорошо, спасибо за подробный ответ. Вы правы насчет структуры. Я не могу вставить исходный код сюда, поэтому я просто вставил то, что я сделал, чтобы воссоздать инструкцию вставки и вывод ассемблера, который подтвердил мне, что вставка использовалась. Вы также правы в том, что тест набора инструкций должен тестировать только часть всего теста инструкций, т. е. инструкции, которые используются на одном уровне кода, а часть кода, которая должна их тестировать, была сплайсирована на это из другого проекта, поэтому я должен исправить это, протестировав еще около 30 используемых инструкций. - person vandelfi; 18.12.2017
comment
Мне кажется, что мне не хватает серьезных знаний ассемблера, чтобы закончить это. Большая часть того, что вы объяснили, к сожалению, выше моего текущего уровня навыков. Но спасибо, что нашли время, чтобы изложить все тонкости. - person vandelfi; 18.12.2017
comment
@vandelfi: Я бы порекомендовал вам нанять консультанта, который знает ассемблер, чтобы он пришел и посмотрел, какую проблему вы пытаетесь решить в первую очередь с помощью этого теста, и помог вам понять, правильный ли это подход. Вы пытаетесь проверить аппаратную совместимость с различными чипами TriCore или что-то в этом роде? Если это так, то простое написание ассемблера вручную имело бы гораздо больше смысла. - person Peter Cordes; 18.12.2017
comment
@vandelfi или наоборот, имея свои функции с разумным API внутри своего рода библиотеки, вы можете создавать приложение для модульного тестирования, вызывающее ваши функции и проверяющее результаты, не заботясь о конкретных используемых инструкциях. Ваш вопрос на данный момент, к сожалению, звучит как Проблема XY. - person Ped7g; 18.12.2017
comment
поэтому концепция безопасности в автомобильной сфере находится под вопросом. У нас есть код, отвечающий за функциональность, затем код, который отслеживает это, а затем еще немного кода, который проверяет, правильно ли инструкции, используемые в части мониторинга, манипулируют данными. это то, что я имею в виду под тестом набора инструкций. - person vandelfi; 18.12.2017
comment
@vandelfi: какие проблемы тебя беспокоят? У вас есть модель угроз? Вы обеспокоены тем, что компилятор неправильно использует определенные инструкции, или аппаратное обеспечение неправильно выполняет определенные инструкции? Или оба? Если плохая генерация кода вызывает беспокойство, то простое тестирование другого кода, использующего одну и ту же инструкцию по-разному в другом контексте, не является хорошим тестом. Я думаю, что предложение Ped7g по модульному тестированию хорошее: попробуйте написать тесты, которые используют те же фактически скомпилированные функции, которые вам нужны, а не другой код, скомпилированный по-другому. - person Peter Cordes; 18.12.2017
comment
часть кода IST выполняется постоянно и проверяет, привели ли данные, вычисленные функциями более высокого уровня (которые используют определенные инструкции asm), к ожидаемому результату. В противном случае возникает реакция на ошибку. Это проблема времени выполнения, связанная с тем, что что-то идет не так на оборудовании. - person vandelfi; 18.12.2017
comment
@vandelfi, тогда вам нужен специализированный ассемблерный код, написанный непосредственно с целью проверки неисправности ЦП. Хотя мне очень трудно представить, что ЦП работает со сбоями только для конкретной инструкции, по моему наивному мнению (я не специалист по аппаратному обеспечению и очень мало знаю об этом), неисправный ЦП, вероятно, будет иметь неисправную часть чипа, что приведет к все виды побочных эффектов, такие как тестовая установка / демонтаж, уже будут сильно затронуты, результат тестируемой инструкции также может быть искажен, но вопрос в том, хватит ли жизни, чтобы сообщить об этом. - person Ped7g; 18.12.2017
comment
@vandelfi также тестирует только инструкции, которые не будут обнаруживать неисправность в конкретной строке адреса/данных, т. е. тесты могут стать зелеными, поскольку они будут использовать часть ОЗУ, которая все еще доступна, в то время как основной код уже будет сходить с ума, так как нужна адресная строка потому что это часть оперативной памяти будет работать со сбоями. Затем ваш тестовый код должен не только выполнять те же инструкции, что и ваш основной код, но также проверять память и другие ресурсы, активно используемые живым кодом, то есть либо чередовать живые данные с некоторым свободным пространством для тестов, либо иметь блокировки доступа и останавливать основной код. код во время теста. - person Ped7g; 18.12.2017
comment
Питер: знаешь, какая была бы лучшая шутка? Если они не смогут создать этот тест на ассемблере, потому что C требуется из-за какого-то стандарта, а исходный код asm не будет проходить какую-либо проверку политик, в то время как шаткий исправленный изолентой C, скомпилированный с большой удачей для определенных инструкций, будет рассматриваться как правильное решение. :D - person Ped7g; 18.12.2017
comment
Спасибо всем за ваши комментарии. Я хотел бы, чтобы я мог найти лучший способ описать все затруднительное положение. - person vandelfi; 18.12.2017