Может ли собранный код ASM привести к нескольким возможным путям (кроме значений смещения)?

Я не очень хорошо знаю x86 ASM, но мне довольно хорошо с SHARP-z80, и я знаю по опыту, что каждая инструкция (мнемоника) имеет соответствующее значение байта / слова и, глядя на шестнадцатеричный дамп собранного двоичный файл, который я могу «прочитать обратно» тот же код, который я написал, используя мнемонику.

В другойТАК вопроскто-то утверждал, чтоthere are some situations where ASM instructions are not translated to their corresponding binary value, but instead are rearranged in a different way by the assembler.

Я особенно ищу случаи, когда разборка двоичного файла привела бы к другому коду ASM, чем исходный.

Другими словами,are there any cases where assembly code is not 1:1 ratio with assembled code?

MikeKwan связан с другим вопросом, где GCC будет изменять встроенный код ASM (в проекте C), но, хотя это и является интересной темой, он не отвечает на этот вопрос, поскольку GCC является компилятором и всегда пытается оптимизировать код и на внутреннюю связь ASM влияет окружающий C-код.

 Nadir Sampaoli26 мая 2012 г., 12:31
@MikeKwan Это не совсем то, что я искал (во всяком случае, это интересное чтение, спасибо), так как GCC - это компилятор, он имеет тенденцию вести себя как компилятор, а не как ассемблер.Someone told me very self-confidently что некоторые инструкции ASM не переводятся буквально ассемблерами.
 Nadir Sampaoli26 мая 2012 г., 12:16
@MikeKwan Я имею в виду процесс сборки / компоновки. Я имею в виду: может ли двоичный результат сборки некоторого кода ASM с двумя разными ассемблерами или в два разных времени отличаться (не учитывая адреса и смещения)?
 harold26 мая 2012 г., 15:35
Инструкции x86 часто могут кодироваться несколькими способами (специальная форма, в которой используется аккумулятор, непосредственный может быть расширен или полностью указан, операндам памяти, имеющим только базовый регистр, может быть присвоено нулевое смещение и т. д.). Кроме того, коды условий имеют много синонимов.
 Mike Kwan26 мая 2012 г., 12:12
Вы имеете в виду полиморфный код? программа, которая переписывает свой собственный код во время выполнения?
 Mike Kwan26 мая 2012 г., 12:27
Что-то вроде этого?stackoverflow.com/a/6533155/712358

Ответы на вопрос(1)

Решение Вопроса

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

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

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

Некоторые машины не имеют инструкций относительно прыжка в длину. В этом случае ассемблер вставит короткий jmp относительно назад, если цель предшествует jmp и находится рядом. Если цель предшествует, но находится далеко, или цель является прямой ссылкой, ассемблер может вставить короткий относительный jmp в противоположные условия ветвления с целью после следующей инструкции, за которой следует длинный абсолютный jmp. (Я сам собирал ассемблеры, как это). Это гарантирует, что JMP всегда может достичь своей цели.

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

Теперь давайте обратимся к тем, которые сбивают с толку вашего дизассемблера.

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

Трюки с отверткой, которые вы можете найти в зрелых ассемблерах, где поддерживается каждый трюк, который когда-либо предполагал Один из моих фаворитов на 8-битных ассемблерах был "псевдо" инструкции SKIP1, SKIP2, которые вы можете рассматривать как чрезвычайно короткие относительные ветви. На самом деле это был просто байт "CMP # 8bits". и "CMP # 16bit". инструкции и использовались для перехода по 8-битной или 16-битной инструкции соответственно. Итак, «один байт» Относительный скачок, а не два. Когда вы сжимаете место, каждый байт имеет значение: - {

      SKIP1
      INC    ; 8 bit instruction
      ...

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

      SKIP2
LOOP: SHLD  ; 16 bit instruction
      ...
      BNE LOOP

Эта проблема заключается в том, что если вы разберете инструкции SKIP1 или SKIP2, вы не увидите INC (или соответствующую 16-битную инструкцию).

Уловка, используемая программистами на ассемблере для передачи параметров, заключается в том, чтобы поместить их в строку после вызова, при условии, что вызываемая подпрограмма соответствующим образом корректирует адрес возврата:

      CALL   foo
      DC     param1
      DC     param2

Или же          ВЫЗОВ          DC "строка переменной длины", 0

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

 Nadir Sampaoli26 мая 2012 г., 21:29
Прежде всего, спасибо за ответ. Я вижу, что я не фанат этих независимых ассемблеров. В SHARP-z80 у вас есть две разные инструкции для абсолюта (jp 0xDDEE) или относительный (jr 0xBB) прыгает. Затем ассемблер выдает ошибку, если вы пытаетесь выполнить относительный переход к адресу, длина которого превышает 0x7F байтов (назад или вперед). И это так и должно быть для меня, хотя, исходя из вашего ответа, я понимаю, что в x86 есть одна мнемоника, которая может быть переведена либо в относительный, либо в абсолютный скачок. Это не очень удобно, но это только я.
 13 дек. 2017 г., 04:28
@ NadirSampaoli: Рассматривая только прямые прыжки рядом (т.е. это не меняетCS регистр сегмента) x86 не имеет никаких абсолютных переходов (если вы не поместите абсолютное значение в регистр иjmp eax). Некоторые сборки позволяют писатьjmp SHORT some_label вызвать короткий прыжок, так что вы получите ошибку времени сборки, если смещение не помещается в rel8. (Или жеjmp NEAR some_label.) Для условных переходов 8086 не имеет кодировки rel16, но IIRC 386 ввел их, так что 32-битный код всегда может использоватьjle NEAR some_label если нужно.
 13 дек. 2017 г., 08:35
@PeterCordes: Тогда вам понравится, как я реализую быстрое управление исключениями. За каждой инструкцией CALL следует CMP reg, imm32. Обычные последовательности вызовов выполняются как CALL, подпрограмма запускается и возвращается, CMP выполняется без какого-либо серьезного эффекта. Для исключения вызываемая подпрограмма выбирает адреса возврата, выбирает часть imm32 CMP и добавляет это как относительное смещение к адресу возврата, чтобы вычислить адрес обработчика исключений в вызывающей стороне. Очевидно, что ни один дизассемблер не понимает этого соглашения, поэтому он не может следовать потоку управления обработкой исключений.
 13 дек. 2017 г., 04:33
Ха-ха, использование короткой инструкции в качестве непосредственных данных для кода операции вне цикла - отличный трюк. Это действительно круто. Придется иметь это в виду для кода гольфа.
 26 мая 2012 г., 22:02
Х86 не имеет мнемоники, которая переводит в относительную или абсолютную JMP. Некоторые сборщики делают. MASM имеет тенденцию использовать одну мнемонику (например, «MOV») для представления множества различных кодов операций в зависимости от типа операнда и синтаксиса. Цель ассемблера - позволить вам писать машинные инструкции под жестким контролем, если это то, что вы хотите; большинство машинных инструкций, которые вы пишете, не настолько критичны, и там, где ассемблер может вмешаться и немного облегчить жизнь большей части вашего кода, люди его дополнили.

Ваш ответ на вопрос