2016-12-08 8 views
1

я анализирую последовательность x86 инструкций и запутаться со следующим кодом:x86 SBB с того же регистра в качестве первого и второго операнда

135328495: sbb edx, edx 
135328497: neg edx 
135328499: test edx, edx 
135328503: jz 0x810f31c 

Я понимаю, что sbb равно des = des - (src + CF), другими словами, первая инструкция как-то положила -CF в edx. Затем это negtive-CF в CF, и test ли CF равно нулю?

Но учтите, что jz проверяет флаг ZF, а не CF! Итак, в основном, что пытается сделать предыдущая последовательность кода? Это законная последовательность действий x86, выпущенная g++ версии 4.6.3.

Код C++ на самом деле из проекта botan. Вы можете найти общий код сборки (пример расшифровки ботанов RSA) по номеру here. В дизассемблированном коде существует довольно много такой последовательности команд.

+3

Какой источник это исходит? Какие параметры компиляции? (Оптимизация включена? Любые '-mtune = bdver2' или что-то еще? Процессоры AMD распознают' sbb то же самое, что и независимое от старого значения reg, и зависят только от CF. На процессоре Intel, вероятно, будет быстрее 'setc dl' /' movzx edx, dl' (или лучше, [обрезать edx перед запуском setc, желательно с распознаванием идиомы обнуления во избежание частичного схождения регистра) (http://stackoverflow.com/a/33668295/ 224132)), поэтому я удивлен, что g ++ будет использовать эту последовательность для чего угодно.) Странно, что он использует TEST, поскольку NEG уже устанавливает флаги. –

+0

Что-нибудь использует EDX после этого? Это должно быть, иначе gcc просто использовал бы JC, верно? Может быть, источником был такой беспорядок, что gcc не удалось его оптимизировать? –

+4

Вещь 'sbb' /' neg' - очень распространенная идиома MSVC для генерации нераспределенного кода, но я обычно не вижу, что GCC использует ее - она ​​предпочитает 'setc', как предложил Питер. В любом случае, так как это не бесконтактный код, искажения, чтобы избежать 'jc', не имеют большого смысла. Мне также кажется, что команда 'test' избыточна. Вы сказали, что код пришел из GCC 4.6.3, но откуда появился код * ввода *? У вас есть исходный исходный код C или C++, или вы декомпилируете непрозрачный двоичный файл? –

ответ

4
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 = 0ZF = 1
  • CF == 1 → edx = -1 → → edx = 1
  • ZF = 0
jz 0x810f31c 

Наконец, это условный переход на основе значения ZF. Если установлено ZF, оно переходит на 0x810f31c; в противном случае он переходит к следующей инструкции.

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

Вот как это работает. Тем не менее, я не могу объяснить , почему компилятор решил сгенерировать код таким образом. Представляется субоптимальным на нескольких уровнях. Наиболее очевидно, что компилятор мог просто испустить инструкцию JNC (прыгать, если не переносить). Хотя мы с Питером Кордесом и в других комментариях и комментариях высказывали разные замечания, я не думаю, что имеет смысл включить все это в ответ, если не будет предоставлена ​​более подробная информация о происхождении этого кода.

+0

'sbb edx, edx' /' neg' явно хуже, чем 'setc', в регистр с xor-zeroed [(последняя часть этого ответа)] (http://stackoverflow.com/a/33668295/224132). SBB имеет ложную зависимость от EDX во всем, кроме AMD, а SBB - 2 раза на Intel pre-Broadwell. 'sbb same, same' может быть полезной идиомой, когда вы хотите all-ones (т. е. -1), но используя NEG, перетаскивает ее, чтобы быть в лучшем случае равным. Это небольшая разница, но я бы не сказал, что это подделка, потому что нет процессоров, где SBB/NEG лучше, и многие из них хуже.Обратите внимание, что даже на AMD оба SBB и NEG находятся на критическом пути, в отличие от XOR/SETC. –

+0

Я думаю, что OP означало, что NEG изменяет значение «-CF» в EDX на исходное значение CF, все еще в EDX. Не то, чтобы он устанавливал CF в EFLAGS. Поэтому я думаю, что ОП понимал это правильно, но сформулировал это неоднозначно. –

+0

@peter Хм, я отчетливо помню и чтение, и подтверждение для себя в тестах, что 'xor' /' setc' был медленнее, чем 'sbb' /' neg'. И это было не для процессоров AMD, это определенно Intel. Это было почти наверняка старым; исходный источник, возможно, говорил о PII, а мои тесты были на PIII и P4 (а P4 - странная птица). Тот факт, что MSVC сгенерировал этот код с тех пор навсегда, также настоятельно указывает на то, что он был предпочтительным в свое время, вероятно, на 486 или Pentium, где они впервые сделали этот выбор. Intel предупреждала, что 'setCC' был медленным, и его следует избегать. –

2

Я понимаю, что sbb равно des = des - (src + CF), другими словами, первая инструкция каким-то образом помещает -CF в edx.

Да, edx = edx - (edx + CF) = -CF. Таким образом, sbb edx,edx установит edx до 0, когда CF = 0, и до -1 (0xFFFFFFFF), когда CF = 1. Также само вычитание приводит к новому значению CF, которое равно старому, если я не слишком запутался.

Затем это отрицательно -CF в CF и проверяет, равен ли CF нулю?

Почти да, но нет. Он отрицает edx, а не CF. Для отказа от CF есть отдельная инструкция CMC (от stc/clc/cmc).

Итак, из 0/-1 edx будет изменен на 0/1, CF снова будет установлен на 0/1 (ничего себе, я не знал, что neg устанавливает CF как ~ ZF). Также neg уже устанавливает ZF, поэтому следующее test edx,edx является избыточным.

test edx,edx не испытывает CF, но EDX (в этот момент 0 или 1), и он будет производить CF = 0 и ZF = 1/0 на величину 0/1.

Итак, вы ушли в свои мысли, придерживаясь того факта, что числовое значение в edx происходило из CF, вы все время думали о CF, но на самом деле с первого sbb вы можете забыть о старых CF, каждая следующая инструкция (включая sbb) является арифметическим, поэтому он модифицирует CF по-своему. Но те neg/test инструкции edx сфокусированы на числе в регистре, CF - только побочный продукт их расчета.

Обратите внимание, что jz проверяет флаг ZF, а не CF!

В самом деле, как это делает CF содержат 0 после последнего test, совершенно не связанную с начальным значением CF впереди sbb. С другой стороны, ZF напрямую связан с исходным значением CF таким образом, что если код начинается с CF = 1, то последние jz не будут приняты (ZF = 0), и если код начинается с CF = 0, будут приняты последние jz (ZF = 1).

+2

Я думаю, что OP говорил о значении в EDX и просто маркировал его '-CF', а затем' CF', что означает начальное значение CF. Или, может быть, что * jz проверяет флаг ZF, а не CF * - это ключ к тому, что они не правильно его соблюдали. –

+1

@PeterCordes большое спасибо за ваш пример и ваш ответ. Я обновил вопрос для вашей информации. – computereasy