Перевод сборки RCL на C/C++

Я пытаюсь вручную перевести некоторую сборку на 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++?

Не пропустите предыдущую инструкцию SAR DL, 1, которая настраивает перенос для следующей RCL.

Weather Vane 02.02.2023 20:16

На [неофициальных] страницах ISA для RCL et. al., у них есть пример кода C для объяснения инструкций: c9x.me/x86/html/file_module_x86_id_273.html Также используйте unsigned short вместо short

Craig Estey 02.02.2023 20:16

Показанная функция ничего не делает с результатами.

Weather Vane 02.02.2023 20:20

Что вы делаете с BX и DL, когда закончите? Какова цель `mov al, dl`? Вскоре после этого вы перезаписываете al на MOV AX, BX.

Jim Rhodes 02.02.2023 20:21

Этот код не имеет никакого эффекта, так как он не является результатом именованной переменной C, к которой можно получить доступ вне блока asm. И это mov поэтому значение, оставленное в EAX, не является возвращаемым значением. В любом случае, void похож на rcl bx,1, немного смещаясь от CF (флаг переноса) к нижней части BX, как вы показываете. Смещенный бит переходит в CF, поэтому adc bx,bx помещает сдвинутый бит в конец DL.

Peter Cordes 02.02.2023 20:21

Спасибо всем за информацию. Я намеренно сделал функцию настолько простой, насколько это возможно, так как в основном хотел понять, как перевести часть RCL, поэтому не думал, что остальная часть функции нужна. Чтобы я полностью понял, что именно подразумевается под SAR DL, 1 установкой флага переноса для следующего RCL? Означает ли это, что если SAR DL, 1 переполняется, флаг переноса устанавливается для следующей инструкции?

Kakalokia 02.02.2023 20:24

Нет, читайте инструкцию по эксплуатации: felixcloutier.com/x86/sal:sar:shl:shr для смен и felixcloutier.com/x86/rcl:rcr:rol:ror. CF = бит, сдвинутый наружу. Итак, DL & 1 для значения DL перед сдвигом. К вашему сведению, это стандартное поведение для сдвигов в большинстве ISA с флагом переноса.

Peter Cordes 02.02.2023 20:25

Довольно часто полезно выяснить на высоком уровне, что в целом выполняет asm, а затем повторно реализовать это на C, поэтому мы спрашиваем, что происходит позже. Вместо того, чтобы транслитерировать каждую операцию одну за другой. Однако, если вам нужно сделать это в качестве первого шага, надеюсь, оптимизирующий компилятор сможет разобраться в беспорядке. Надеюсь, ассемблер не зависит от записи неполных регистров и последующего чтения полного регистра, иначе вам придется реализовать эту часть на C, объединяя записи неполных регистров (в DL) обратно в uint32_t EDX или создавая объединение.

Peter Cordes 02.02.2023 20:31

@PeterCordes - Полную функцию см. в редактировании. Я считаю, что функция пытается выполнить некоторое преобразование строки, но трудно сказать, так как я не слишком хорошо знаком с ассемблером. С точки зрения перевода битов SAR и RCL, если я понял, что мне нужно сделать >> битовый сдвиг для инструкции SAR, однако я не очень уверен в том, как после этого запросить флаг переноса, чтобы я мог использовать его для следующего эквивалента RCL в С/С++. Надеюсь, это имеет смысл. Спасибо.

Kakalokia 02.02.2023 20:44

Я не писал функцию, так что все ее грехи не мои (к счастью) :)

Kakalokia 02.02.2023 20:45

Проливает ли имя функции какой-либо свет на цель? Является ли это своего рода «расшифровкой» данных, запутанных данных, или это просто действительно неэффективный и/или запутанный код? Окончательное значение DL/EDX не используется, поэтому окончательный rcl dl, 1 бесполезен. Кроме того, mov al, dl бесполезен, потому что ничто не читает его или AL перед MOV AX,BX. clc бесполезен, потому что ror записывает CF до того, как кто-либо прочитает этот обнуленный CF. Цикл в конце выглядит отдельным и может заключать значение al в какой-то диапазон, например <= (unsigned)'z' или >= (unsigned)'A'. Выглядит довольно неэффективно и грязно.

Peter Cordes 02.02.2023 20:59

Глядя на некоторые примеры входных и выходных данных (перед разветвленной частью и из всего ассемблерного оператора), вы можете понять, что он делает, например, дать подсказку о том, что искать в ассемблере, чтобы понять, как он попадает из точки А в точку Б.

Peter Cordes 02.02.2023 21:01

@PeterCordes - Боюсь, имя функции мало что дает, но похоже, что это какая-то техника запутывания. Я согласен, что это грязно, поэтому я действительно хотел бы преобразовать его хотя бы в C/C++ (а также иметь возможность использовать 64-битную версию с Visual Studio). Это можно перевести или я застрял с ним? Я могу как бы перевести последние биты, которые сравниваются со значениями ASCII, но с битами SAR и RCL я очень борюсь.

Kakalokia 02.02.2023 21:06

Конечно, это переводимо; C является полным по Тьюрингу. Это просто не выглядит чем-то очевидным или прямолинейным, и это больше, чем я мог удержать в голове, когда писал комментарии. Поэтому потребуется дополнительная работа и тестирование, чтобы убедиться, что это правильно. Похоже, что он обновляется iInput через итерации, поэтому после 8 RCL BX, 1 в верхней половине его младших 16 бит будет несколько ненулевых битов, и XOR начнет что-то делать. Итак, при обработке 3-го символа.

Peter Cordes 02.02.2023 21:28

Пожалуйста, публикуйте ответы как ответы, а не изменения вопроса. Если вы не спрашиваете, правильно ли это, в этом случае ваш макрос LOWORD(x) имеет строгий псевдоним UB. Ваши байтовые макросы безопасны, если предположить, что uint8_t есть unsigned char, но вы просто усложняете жизнь компилятору без всякой причины; как я уже сказал, этот код не читает более широкие регистры после записи полного регистра, кроме mov iInput, ebx. Но вместо этого просто используйте uint16_t iInput; его старшие 16 бит никогда не затрагиваются.

Peter Cordes 03.02.2023 00:32

MSVC определяет поведение этого приведения указателей, но нет необходимости использовать этот непереносимый код. Вы, вероятно, соблазните некоторые компиляторы сохранять и перезагружать с катастрофической производительностью. (Пересылка магазина из узкого магазина и перезагрузка более широкого значения.)

Peter Cordes 03.02.2023 00:39

Кроме того, _EBX — это зарезервированное имя, поскольку оно начинается с подчеркивания и заглавной буквы. devblogs.microsoft.com/oldnewthing/20230109-00/?p=107685 . IDK, почему люди любят использовать имена с подчеркиванием, когда они могли бы просто использовать uint16_t bx и тому подобное uint8_t c = bx; // truncate to 8 bit

Peter Cordes 03.02.2023 00:41

Спасибо @PeterCordes, да, я хотел сначала подтвердить правильность кода, особенно то, что он будет использоваться как в 32-битном, так и в 64-битном exe. Вы предлагаете мне просто использовать _EBX напрямую, а не устанавливать или получать LOWORD? Да, подчеркивания в финальном коде использовать не буду, это из дизассемблера :)

Kakalokia 03.02.2023 00:42

Я говорю, что вы должны использовать uint16_t bx и uint16_t iInput;. Старшие 16 бит обоих всегда равны нулю; посмотрите на инициализатор для iInput и тот факт, что ничто в ассемблере никогда не записывает EBX, кроме записи младших 16 бит или загрузки из iInput. Ничто никогда не может немного сместиться в верхнюю половину или иным образом установить его там. А BYTE1 — это просто bx >> 8 для uint16_t bx.

Peter Cordes 03.02.2023 00:45

Ах, я вижу, не заметил, что ничего не написал EBX. Имеет смысл. Большое спасибо за ваш вклад, ценю это.

Kakalokia 03.02.2023 00:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
20
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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++ для декомпилированной функции.

Также любой пример инструментов, которые могут помочь, пожалуйста?

Kakalokia 02.02.2023 22:23

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

rustyx 02.02.2023 22:30

Вероятно, есть способ еще больше упростить их. - Действительно, нам никогда не нужно сдвигать dl, поскольку sar и rcl сокращаются для старших 7 бит, просто смешиваются биты, чтобы заменить младший бит. например dl = (dl & 0xfe) | (bx>>15). (Также не имеет значения, что это был арифметический (со знаком) сдвиг вправо, а не логический (беззнаковый), поскольку сдвинутый бит смещается обратно, и, что очень важно, он не читается позже; следующее использование для FLAGS только для записи , sar в следующем блоке.) Предполагая, что компилятор на самом деле не использовал бы shr / adc ebx,ebx (или более медленный RCL), это может быть быстрее.

Peter Cordes 02.02.2023 23:28

@Kakalokia: на самом деле, даже с исходным кодом Rusty C, gcc и clang просто маскируют и ИЛИ новый младший бит из bx >> 15, не сдвигая dl вправо, а затем влево. godbolt.org/z/z6oxq91zd (Глядя на это в автономной функции, принимающей аргументы, это означает некоторые накладные расходы на расширение нулями входящих аргументов, когда соглашение о вызовах не гарантирует, что они свободны от мусора или знака -расширение возвращаемого значения.) GCC и clang используют AArch64 bfi (вставка битового поля) для этого при компиляции для этой ISA.

Peter Cordes 02.02.2023 23:35
godbolt.org/z/aG1TPP5GY включает поворот между двумя итерациями этого. Компиляторы для x86-64 не замечают особых оптимизаций, они по-прежнему выполняют 8-битный ROR. (Немедленным 3 вместо того, чтобы без всякой причины вводить счет в cl.) Но для AArch64 они на самом деле не вращаются. Ах да, конечно, потому что AArch64 имеет только 32- и 64-битные повороты, поэтому они должны использовать ubfx (беззнаковое извлечение битового поля) и orr со сдвинутым входным операндом, чтобы выполнить поворот за 2 инструкции. (@Какалокия)
Peter Cordes 02.02.2023 23:47

Спасибо обоим. Я обновил вопрос окончательным переводом, который мне удалось получить. Это очень уродливо и неэффективно, но, по крайней мере, это не ассемблер. Мне нужно привести его в порядок, но, по крайней мере, сейчас он выводит тот же результат, что и версия на ассемблере. Я думаю, что правильно понял все типы и знак/разрядность, а также использовал LOWORD @PeterCordes.

Kakalokia 03.02.2023 00:33

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