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

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

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

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

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

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

 .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;

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-код в текстовом редакторе.

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

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

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

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

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

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


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


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

Обычно такое мышление более полезно для оптимизации путем настройки исходного кода для более эффективной компиляции. Сначала вы думаете об эффективной реализации asm написанной вами функции. Затем вы пишете исходный код на 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() (или любое значение, которое все еще не является константой времени компиляции после оптимизации с постоянным распространением).

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

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


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

Назначение volatile а затем чтение этой переменной было бы еще одним способом победить постоянное распространение даже для теста, который должен выполняться без внешних входных данных, если это проще, чем использование функций noinline. (Компиляторы перезагружаются с 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
 }

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

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

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

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

Другие вопросы по тегам