sbb edx, edx
Ваш анализ этой инструкции правильно. SBB
означает «вычесть с займом». Он вычитает источник из адресата таким образом, который учитывает флаг переноса (CF
).
Таким образом, это эквивалентно dst = dst - (src + CF)
, поэтому это edx = edx - (edx + CF)
или просто edx = -CF
.
Не позволяйте этому обмануть вас, что исходные и целевые операнды являются как edx
здесь! SBB same, same
- довольно распространенная идиома в коде, сгенерированном компилятором, для выделения флага переноса (CF
), особенно когда они пытаются создать нераспаковывающийся код. Существуют альтернативные способы сделать это, а именно инструкцию SETC
, которая , вероятно, быстрее на большинстве архитектур x86 (см. Комментарии для более тщательного раскрытия), но не значительная сумма. Компиляторы от разных поставщиков (и, возможно, даже разных версий), как правило, предпочитают одно или другое и используют это везде, когда вы не выполняете настройку, специфичную для архитектуры.
neg edx
Опять же, ваш анализ этой инструкции правильно. Это довольно простой. NEG
выполняет отрицание с двумя дополнениями в операнде. Поэтому это всего лишь edx = -edx
.
В этом случае, мы знаем, что edx
первоначально содержала -CF
, что означает, что его первоначальное значение было либо 0
или -1
(потому что CF
всегда либо 0 или 1, включены или выключены). Отрицание означает, что edx
теперь содержит 0
или 1
.
То есть, если CF
был первоначально набор, edx
теперь будет содержать 1
; в противном случае он будет содержать 0
. Это действительно завершение упомянутой выше идиомы; вам необходимо NEG
, чтобы полностью изолировать значение CF
.
test edx, edx
TEST
инструкция такая же, как AND
инструкции, за исключением того, что он не влияет на операнд-то назначения устанавливает только флаги.
Но это еще один частный случай. TEST same, same
- это a standard idiom in optimized code, чтобы эффективно определить, является ли значение в регистре равным 0. Вы могли бы написать CMP edx, 0
, что бы наивно делал человеческий программист, но test
быстрее. (Почему это работает?Из-за таблицы истинности побитового И. Единственный случай, когда value & value == 0
- это когда value
равно 0.)
Таким образом, это приводит к установке флажков. В частности, он устанавливает флаг нуля (ZF
), если edx
равен 0, и очищает его, если edx
отличен от нуля.
Таким образом, если CF
было первоначально комплект, ZF
теперь будет чистым; в противном случае он будет установлен. Возможно, более простой способ взглянуть на это состоит в том, что эти три инструкции устанавливают ZF
в противоположность исходному значению CF
.
Ниже приведены два возможных потоков данных:
CF
== 0 → edx
= → edx = 0
ZF = 1
CF
== 1 → edx
= -1 → → edx = 1
ZF = 0
jz 0x810f31c
Наконец, это условный переход на основе значения ZF
. Если установлено ZF
, оно переходит на 0x810f31c
; в противном случае он переходит к следующей инструкции.
Сложив все вместе, то этот код проверяет дополнение флага переноса (CF
) через косвенный маршрут, который включает в себя нулевой флаг (ZF
). Он разветвляется, если флаг переноса был изначально ясен, и проваливается, если флаг переноса был первоначально установлен.
Вот как это работает. Тем не менее, я не могу объяснить , почему компилятор решил сгенерировать код таким образом. Представляется субоптимальным на нескольких уровнях. Наиболее очевидно, что компилятор мог просто испустить инструкцию JNC
(прыгать, если не переносить). Хотя мы с Питером Кордесом и в других комментариях и комментариях высказывали разные замечания, я не думаю, что имеет смысл включить все это в ответ, если не будет предоставлена более подробная информация о происхождении этого кода.
Какой источник это исходит? Какие параметры компиляции? (Оптимизация включена? Любые '-mtune = bdver2' или что-то еще? Процессоры AMD распознают' sbb то же самое, что и независимое от старого значения reg, и зависят только от CF. На процессоре Intel, вероятно, будет быстрее 'setc dl' /' movzx edx, dl' (или лучше, [обрезать edx перед запуском setc, желательно с распознаванием идиомы обнуления во избежание частичного схождения регистра) (http://stackoverflow.com/a/33668295/ 224132)), поэтому я удивлен, что g ++ будет использовать эту последовательность для чего угодно.) Странно, что он использует TEST, поскольку NEG уже устанавливает флаги. –
Что-нибудь использует EDX после этого? Это должно быть, иначе gcc просто использовал бы JC, верно? Может быть, источником был такой беспорядок, что gcc не удалось его оптимизировать? –
Вещь 'sbb' /' neg' - очень распространенная идиома MSVC для генерации нераспределенного кода, но я обычно не вижу, что GCC использует ее - она предпочитает 'setc', как предложил Питер. В любом случае, так как это не бесконтактный код, искажения, чтобы избежать 'jc', не имеют большого смысла. Мне также кажется, что команда 'test' избыточна. Вы сказали, что код пришел из GCC 4.6.3, но откуда появился код * ввода *? У вас есть исходный исходный код C или C++, или вы декомпилируете непрозрачный двоичный файл? –