Как мне написать код C, чтобы полученная сборка использовала дополнительные инструкции dsll32 и dsra32?

Я декомпилирую игру для PS2, поставляемую в виде отладочной сборки. Я дошел до того, что декомпилировал достаточно, чтобы иметь возможность скомпилировать файл ELF с помощью исходного компилятора (Metrowerks CodeWarrior).

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

Оригинальная сборка:

      dsll32  v1,s1,16
dsra32  v1,v1,16
subu    v1,$0,v1
dsll32  v1,v1,16
dsra32  v1,v1,16
dsll32  s1,v1,16
dsra32  s1,s1,16

Это было декомпилировано в следующий код C:

      d1 = -d1;

И был скомпилирован в следующую сборку:

      subu    v1,$0,s1
dsll32  s1,v1,16
dsra32  s1,s1,16

Обратите внимание, что одна пара инструкций по сдвигу отсутствует. Пока мне не удалось это повторить. Я пробовал добавлять различные приведения, меняя их наd1 = 0 - d1, мне предлагали добавить 64-битный кастинг, но ничего не дало желаемого результата.

Вот еще один пример:

Оригинальная сборка:

      lh      v0,80(sp)
dsll32  v1,v0,16
dsra32  v1,v1,16
lw      v0,128(sp)
lb      v0,0(v0)
dsll32  v0,v0,24
dsra32  v0,v0,24
dsll32  v0,v0,16
dsra32  v0,v0,16
addu    v0,v1,v0
dsll32  v0,v0,16
dsra32  v0,v0,16
dsll32  s3,v0,16
dsra32  s3,s3,16

Код С:

      x = xposi + sprdat->xoff;

Скомпилировано в:

      lh      v1,80(sp)
lw      v0,128(sp)
lb      v0,0(v0)
dsll32  v0,v0,24
dsra32  v0,v0,24
addu    v0,v1,v0
dsll32  s3,v0,16
dsra32  s3,s3,16

Кто-нибудь знает, какой код C будет за это отвечать?

1 ответ

TL;DR: Не волнуйтесь, все в порядке.

  1. Вы декомпилируете отладочную версию. Итак, оптимизация [вероятно] отключена.
  2. Исходная сборка выглядит как неоптимизированный код.
  3. Декомпилированный кодd1 = -d1;
  4. Результирующая сборка вашего перекомпилированного кода оптимизируется с помощью дополнительного фактора (см. ниже).
  5. На x86 это создаст одну инструкцию . Если бы были в (например)%ecx, сборка будет такой:neg %ecx
  6. На архитектурах, в которых этого нет (например,mips), мы вычитаем значение из 0.

Ниже приводится несколько грубое объяснение.


Переменная C [декомпилированная] равна . Он находится в регистре MIPS. В коде используется временная переменная (назовите ее), которая находится в регистре MIPS.

Чтобы избежать путаницы (возможно, только моей :-)] между произвольным именем, присвоенным декомпиляторомd1, давайте переименуем это как .

В исходной сборке первые две инструкции преобразуют 16-битное целое число в 32-битное целое:

      dsll32  v1,s1,16            # move 16 bit int's sign bit into bit 31
dsra32  v1,v1,16            # shift back with sign extension

Логический сдвиг вправо приведет к сдвигу нулей слева.

Однако ...

Арифметический сдвиг вправо сместит бит 31 в число с левой стороны.

Это распространенный прием для ряда архитектур, в которых нет (как в x86 ) специальных инструкций CISC для перемещения 16-битного числа в 32-битный регистр с автоматическим расширением знака (например,movswl), либо из памяти, либо из другого регистра.

Например, -1 вshortбудет иметь битовый шаблон 0xFFFF (т. е. 0x0000FFFF). Мы конвертируем это в 32 бита (т.е. 0xFFFFFFFF).

При этом используется дополнительная переменная, которая помещается в регистр.v1. Вероятно, [неявно] определяется как:

      int tmp;

Все это означает, что (который находится в регистре) было определено как 16-битное целое число:

      short mydata;

Хотя в x86 есть арифметические инструкции размера слова, которые работают непосредственно с 16 битами (например,subw), MIPS имеет только 32-битные арифметические инструкции между регистрами (исключением являетсяaddiкакой знак расширяет непосредственный операнд).

Теперь, чтобы фактически инвертировать число, мы вычитаем его из нуля:

      subu    v1,$0,v1                # perform negation

Эквивалентный код C:

      tmp = 0 - tmp;

Следующие две инструкции помещают знаковый бит из 32-битного.tmpв знаковый бит 16-битного числа:

      dsll32  v1,v1,16                # put sign bit from 16 bit part into bit 31
dsra32  v1,v1,16                # shift back to get sign bit for 16 bit number

Остальные две инструкции выполняют аналогичное [и ненужное] преобразование, чтобы получить окончательное 16-битное значение дляmydata:

      dsll32  s1,v1,16                # mydata = tmp << 16;
dsra32  s1,s1,16                # mydata = mydata >> 16;

Восстановленная сборка уже предполагает, чтоs1знак был расширен до 32 бит (в предыдущих инструкциях). Итак, первые две инструкции исходной сборки не нужны:

      # assume s1 has _already_ been sign extended to 32 bits here in _prior_
# instructions
subu    v1,$0,s1

# move sign bit from 32 bits back to 16 bits
dsll32  s1,v1,16
dsra32  s1,s1,16

И дополнительные ненужные последние две инструкции опущены.


Подведем итог: перекомпилированный код делает именно то, что делает исходный код, но оптимизирован для выполнения этого за меньшее количество инструкций.

Второй пример делает то же самое.

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