Как мне написать код 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: Не волнуйтесь, все в порядке.
- Вы декомпилируете отладочную версию. Итак, оптимизация [вероятно] отключена.
- Исходная сборка выглядит как неоптимизированный код.
- Декомпилированный код
d1 = -d1;
- Результирующая сборка вашего перекомпилированного кода оптимизируется с помощью дополнительного фактора (см. ниже).
- На x86 это создаст одну инструкцию . Если бы были в (например)
%ecx
, сборка будет такой:neg %ecx
- На архитектурах, в которых этого нет (например,
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
И дополнительные ненужные последние две инструкции опущены.
Подведем итог: перекомпилированный код делает именно то, что делает исходный код, но оптимизирован для выполнения этого за меньшее количество инструкций.
Второй пример делает то же самое.