Я пытаюсь вручную перевести некоторую сборку на C/C++, чтобы перенести кодовую базу на 64-разрядную версию, поскольку Visual Studio не позволяет коду __asm компилироваться в 64-разрядной версии.
Я перевел некоторые части, однако я застрял на следующем (это выдержка из полной функции, но она должна быть автономной):
void Foo()
{
char cTemp = ...;
int iVal = ...;
__asm
{
mov ebx, iVal
mov dl, cTemp
mov al, dl
MOV CL, 3
CLC
MOV AX, BX
ROR AH, CL
XOR DL, AH
SAR DL, 1
RCL BX, 1
RCL DL, 1
}
}
Части, с которыми я борюсь:
RCL BX, 1
RCL DL, 1
Насколько я понимаю, это эквивалентно следующему:
short v = ...;
v = (v << 1) + (CLEAR_FLAG ? 1 : 0)
Что, насколько я понимаю, означает, что если значение (v << 1) переполняется, добавьте 1, в противном случае добавьте 0 (хотя я могу ошибаться, поэтому, пожалуйста, поправьте меня, если это так).
Что я изо всех сил пытаюсь сделать, так это обнаружить переполнение в C/C++ при выполнении операции сдвига. Я осмотрелся, и единственное, что я могу найти, это обнаружение переполнения сложения/вычитания до того, как это произойдет, но ничего в отношении смещения битов.
Можно ли вообще перевести такую сборку на C/C++?
На [неофициальных] страницах ISA для RCL et. al., у них есть пример кода C для объяснения инструкций: c9x.me/x86/html/file_module_x86_id_273.html Также используйте unsigned short вместо short
Показанная функция ничего не делает с результатами.
Что вы делаете с BX и DL, когда закончите? Какова цель `mov al, dl`? Вскоре после этого вы перезаписываете al на MOV AX, BX.
Этот код не имеет никакого эффекта, так как он не является результатом именованной переменной C, к которой можно получить доступ вне блока asm. И это mov поэтому значение, оставленное в EAX, не является возвращаемым значением. В любом случае, void похож на rcl bx,1, немного смещаясь от CF (флаг переноса) к нижней части BX, как вы показываете. Смещенный бит переходит в CF, поэтому adc bx,bx помещает сдвинутый бит в конец DL.
Спасибо всем за информацию. Я намеренно сделал функцию настолько простой, насколько это возможно, так как в основном хотел понять, как перевести часть RCL, поэтому не думал, что остальная часть функции нужна. Чтобы я полностью понял, что именно подразумевается под SAR DL, 1 установкой флага переноса для следующего RCL? Означает ли это, что если SAR DL, 1 переполняется, флаг переноса устанавливается для следующей инструкции?
Нет, читайте инструкцию по эксплуатации: felixcloutier.com/x86/sal:sar:shl:shr для смен и felixcloutier.com/x86/rcl:rcr:rol:ror. CF = бит, сдвинутый наружу. Итак, DL & 1 для значения DL перед сдвигом. К вашему сведению, это стандартное поведение для сдвигов в большинстве ISA с флагом переноса.
Довольно часто полезно выяснить на высоком уровне, что в целом выполняет asm, а затем повторно реализовать это на C, поэтому мы спрашиваем, что происходит позже. Вместо того, чтобы транслитерировать каждую операцию одну за другой. Однако, если вам нужно сделать это в качестве первого шага, надеюсь, оптимизирующий компилятор сможет разобраться в беспорядке. Надеюсь, ассемблер не зависит от записи неполных регистров и последующего чтения полного регистра, иначе вам придется реализовать эту часть на C, объединяя записи неполных регистров (в DL) обратно в uint32_t EDX или создавая объединение.
@PeterCordes - Полную функцию см. в редактировании. Я считаю, что функция пытается выполнить некоторое преобразование строки, но трудно сказать, так как я не слишком хорошо знаком с ассемблером. С точки зрения перевода битов SAR и RCL, если я понял, что мне нужно сделать >> битовый сдвиг для инструкции SAR, однако я не очень уверен в том, как после этого запросить флаг переноса, чтобы я мог использовать его для следующего эквивалента RCL в С/С++. Надеюсь, это имеет смысл. Спасибо.
Я не писал функцию, так что все ее грехи не мои (к счастью) :)
Проливает ли имя функции какой-либо свет на цель? Является ли это своего рода «расшифровкой» данных, запутанных данных, или это просто действительно неэффективный и/или запутанный код? Окончательное значение DL/EDX не используется, поэтому окончательный rcl dl, 1 бесполезен. Кроме того, mov al, dl бесполезен, потому что ничто не читает его или AL перед MOV AX,BX. clc бесполезен, потому что ror записывает CF до того, как кто-либо прочитает этот обнуленный CF. Цикл в конце выглядит отдельным и может заключать значение al в какой-то диапазон, например <= (unsigned)'z' или >= (unsigned)'A'. Выглядит довольно неэффективно и грязно.
Глядя на некоторые примеры входных и выходных данных (перед разветвленной частью и из всего ассемблерного оператора), вы можете понять, что он делает, например, дать подсказку о том, что искать в ассемблере, чтобы понять, как он попадает из точки А в точку Б.
@PeterCordes - Боюсь, имя функции мало что дает, но похоже, что это какая-то техника запутывания. Я согласен, что это грязно, поэтому я действительно хотел бы преобразовать его хотя бы в C/C++ (а также иметь возможность использовать 64-битную версию с Visual Studio). Это можно перевести или я застрял с ним? Я могу как бы перевести последние биты, которые сравниваются со значениями ASCII, но с битами SAR и RCL я очень борюсь.
Конечно, это переводимо; C является полным по Тьюрингу. Это просто не выглядит чем-то очевидным или прямолинейным, и это больше, чем я мог удержать в голове, когда писал комментарии. Поэтому потребуется дополнительная работа и тестирование, чтобы убедиться, что это правильно. Похоже, что он обновляется iInput через итерации, поэтому после 8 RCL BX, 1 в верхней половине его младших 16 бит будет несколько ненулевых битов, и XOR начнет что-то делать. Итак, при обработке 3-го символа.
Пожалуйста, публикуйте ответы как ответы, а не изменения вопроса. Если вы не спрашиваете, правильно ли это, в этом случае ваш макрос LOWORD(x) имеет строгий псевдоним UB. Ваши байтовые макросы безопасны, если предположить, что uint8_t есть unsigned char, но вы просто усложняете жизнь компилятору без всякой причины; как я уже сказал, этот код не читает более широкие регистры после записи полного регистра, кроме mov iInput, ebx. Но вместо этого просто используйте uint16_t iInput; его старшие 16 бит никогда не затрагиваются.
MSVC определяет поведение этого приведения указателей, но нет необходимости использовать этот непереносимый код. Вы, вероятно, соблазните некоторые компиляторы сохранять и перезагружать с катастрофической производительностью. (Пересылка магазина из узкого магазина и перезагрузка более широкого значения.)
Кроме того, _EBX — это зарезервированное имя, поскольку оно начинается с подчеркивания и заглавной буквы. devblogs.microsoft.com/oldnewthing/20230109-00/?p=107685 . IDK, почему люди любят использовать имена с подчеркиванием, когда они могли бы просто использовать uint16_t bx и тому подобное uint8_t c = bx; // truncate to 8 bit
Спасибо @PeterCordes, да, я хотел сначала подтвердить правильность кода, особенно то, что он будет использоваться как в 32-битном, так и в 64-битном exe. Вы предлагаете мне просто использовать _EBX напрямую, а не устанавливать или получать LOWORD? Да, подчеркивания в финальном коде использовать не буду, это из дизассемблера :)
Я говорю, что вы должны использовать uint16_t bx и uint16_t iInput;. Старшие 16 бит обоих всегда равны нулю; посмотрите на инициализатор для iInput и тот факт, что ничто в ассемблере никогда не записывает EBX, кроме записи младших 16 бит или загрузки из iInput. Ничто никогда не может немного сместиться в верхнюю половину или иным образом установить его там. А BYTE1 — это просто bx >> 8 для uint16_t bx.
Ах, я вижу, не заметил, что ничего не написал EBX. Имеет смысл. Большое спасибо за ваш вклад, ценю это.





RCL — это операция «поворот влево с переносом». Поэтому вам нужно принять во внимание предыдущую инструкцию SAR, которая устанавливает флаг переноса (CF).
Обратите внимание, что SAR — это сдвиг вправо со знаком, поэтому потребуется операнд со знаком. Важно использовать правильные типы данных, которые точно соответствуют инструкции по разрядности и подписи.
Перевод 1 к 1 может выглядеть примерно так
int8_t dl = /* ... */;
uint16_t bx = /* ... */;
// SAR DL,1
int8_t carry_1 = dl & 1;
dl >>= 1;
// RCL BX,1
uint16_t carry_2 = bx >> 15;
bx = (bx << 1) | carry_1;
// RCL DL,1
dl = (dl << 1) | carry_2;
Вероятно, есть способ еще больше упростить их. Есть инструменты, которые могут это сделать, они предоставляют несколько удобочитаемый эквивалент C++ для декомпилированной функции.
Также любой пример инструментов, которые могут помочь, пожалуйста?
Мы не делаем записи программного обеспечения здесь, в SO. Но, в виде исключения, я использовал шестигранники на работе раньше. Хотя это не бесплатно.
Вероятно, есть способ еще больше упростить их. - Действительно, нам никогда не нужно сдвигать dl, поскольку sar и rcl сокращаются для старших 7 бит, просто смешиваются биты, чтобы заменить младший бит. например dl = (dl & 0xfe) | (bx>>15). (Также не имеет значения, что это был арифметический (со знаком) сдвиг вправо, а не логический (беззнаковый), поскольку сдвинутый бит смещается обратно, и, что очень важно, он не читается позже; следующее использование для FLAGS только для записи , sar в следующем блоке.) Предполагая, что компилятор на самом деле не использовал бы shr / adc ebx,ebx (или более медленный RCL), это может быть быстрее.
@Kakalokia: на самом деле, даже с исходным кодом Rusty C, gcc и clang просто маскируют и ИЛИ новый младший бит из bx >> 15, не сдвигая dl вправо, а затем влево. godbolt.org/z/z6oxq91zd (Глядя на это в автономной функции, принимающей аргументы, это означает некоторые накладные расходы на расширение нулями входящих аргументов, когда соглашение о вызовах не гарантирует, что они свободны от мусора или знака -расширение возвращаемого значения.) GCC и clang используют AArch64 bfi (вставка битового поля) для этого при компиляции для этой ISA.
3 вместо того, чтобы без всякой причины вводить счет в cl.) Но для AArch64 они на самом деле не вращаются. Ах да, конечно, потому что AArch64 имеет только 32- и 64-битные повороты, поэтому они должны использовать ubfx (беззнаковое извлечение битового поля) и orr со сдвинутым входным операндом, чтобы выполнить поворот за 2 инструкции. (@Какалокия)
Спасибо обоим. Я обновил вопрос окончательным переводом, который мне удалось получить. Это очень уродливо и неэффективно, но, по крайней мере, это не ассемблер. Мне нужно привести его в порядок, но, по крайней мере, сейчас он выводит тот же результат, что и версия на ассемблере. Я думаю, что правильно понял все типы и знак/разрядность, а также использовал LOWORD @PeterCordes.
Не пропустите предыдущую инструкцию
SAR DL, 1, которая настраивает перенос для следующейRCL.