Что не так с этой эмуляцией инструкции CMPXCHG16B?
Я пытаюсь запустить бинарную программу, которая использует CMPXCHG16B
инструкция в одном месте, к сожалению, мой Athlon 64 X2 3800+ не поддерживает его. Что здорово, потому что я вижу это как вызов программирования. Инструкция не кажется такой сложной для реализации с помощью прыжка в пещеру, поэтому я так и сделал, но что-то не сработало, программа просто заморозилась в цикле. Может быть, кто-то может сказать мне, если я реализовал свой CMPXCHG16B
неправильно?
Во-первых, фактический кусок машинного кода, который я пытаюсь эмулировать, таков:
f0 49 0f c7 08 lock cmpxchg16b OWORD PTR [r8]
Выдержка из руководства Intel с описанием CMPXCHG16B
:
Сравните RDX:RAX с m128. Если равно, установите ZF и загрузите RCX:RBX в m128. В противном случае очистите ZF и загрузите m128 в RDX:RAX.
Сначала я заменяю все 5 байтов инструкции на переход к каве-коду своей процедурой эмуляции, к счастью, переход занимает ровно 5 байтов! Прыжок на самом деле call
инструкция e8
, но может быть jmp
e9
, оба работают.
e8 96 fb ff ff call 0xfffffb96(-649)
Это относительный переход с 32-битным смещением со знаком, закодированным в дополнение к двум, смещение указывает на кодовую пещеру относительно адреса следующей инструкции.
Далее код эмуляции, на который я прыгаю:
PUSH R10
PUSH R11
MOV r10, QWORD PTR [r8]
MOV r11, QWORD PTR [r8+8]
TEST R10, RAX
JNE ELSE
TEST R11, RDX
JNE ELSE
MOV QWORD PTR [r8], RBX
MOV QWORD PTR [r8+8], RCX
JMP END
ELSE:
MOV RAX, r10
MOV RDX, r11
END:
POP R11
POP R10
RET
Лично я доволен этим и считаю, что он соответствует функциональной спецификации, приведенной в руководстве. Восстанавливает стек и два регистра r10
а также r11
в первоначальный порядок, а затем возобновляет выполнение. Увы, это не работает! То есть код работает, но программа действует так, как будто она ждет чаевых и сжигает электричество. Что указывает на то, что моя эмуляция не была идеальной, и я случайно сломал ее. Вы видите что-то не так с этим?
Я замечаю, что это атомарный вариант этого - принадлежащий lock
префикс. Я надеюсь, что это что-то еще, кроме утверждения, что я сделал не так. Или есть способ подражать атомарности тоже?
2 ответа
Невозможно подражатьlock cmpxchg16b
, Это возможно, если все обращения к целевому адресу синхронизируются с отдельной блокировкой, но это включает в себя все другие инструкции, включая неатомарные хранилища для любой половины объекта и атомарные операции чтения-изменения-записи (например, xchg
, lock cmpxchg
, lock add
, lock xadd
) с одной половиной (или другой частью) 16-байтового объекта.
Вы можете подражать cmpxchg16b
(без lock
), как вы сделали здесь, с исправлениями из ответа @Fifoernik. Это интересное учебное упражнение, но не очень полезное на практике, потому что реальный код, который использует cmpxchg16b
всегда использует его с lock
префикс.
Неатомарная замена будет работать большую часть времени, потому что редко получается, что строка кэша, аннулированная другим ядром, попадает в маленькое временное окно между двумя соседними инструкциями. Это не означает, что это безопасно, это просто означает, что действительно трудно отлаживать, когда иногда происходит сбой. Если вы просто хотите, чтобы игра работала для вашего собственного использования и допускала случайные блокировки / ошибки, это может быть полезно. Во всем, где важна правильность, вам не повезло.
Как насчет MFENCE? Кажется, это то, что мне нужно.
MFENCE
до, после или между загрузками и хранилищами не помешает другому потоку увидеть наполовину записанное значение ("разрыв") или изменить данные после того, как ваш код принял решение об успешном сравнении, но до того, как это произойдет магазин. Это может сузить окно уязвимости, но не может закрыть его, потому что MFENCE
только предотвращает изменение порядка видимости наших собственных магазинов и грузов. Это не может помешать магазину из другого ядра стать видимым для нас после наших загрузок, но до наших магазинов. Это требует атомного цикла чтения-изменения-записи, что lock
инструкции для
Выполнение двух 8-байтовых атомных сравнительных обменов решило бы проблему окна уязвимости, но только для каждой половины в отдельности, оставив проблему "разрывания".
Atomic 16B загружает / хранит решает проблему разрыва, но не проблему атомности между нагрузками и хранилищами. Это возможно с SSE на некоторых аппаратных средствах, но не гарантируется, что x86 ISA будет атомарным, как естественные нагрузки и хранилища 8B.
Xen, lock cmpxchg16b
эмуляция:
Виртуальная машина Xen имеет эмулятор x86, я полагаю, для случая, когда виртуальная машина запускается на одной машине и мигрирует на менее мощное оборудование. Эмулирует lock cmpxchg16b
взяв глобальную блокировку, потому что другого пути нет. Если бы был способ подражать "правильно", я уверен, что Ксен сделает это.
Как обсуждалось в этом потоке списков рассылки, решение Xen по-прежнему не работает, когда эмулируемая версия на одном ядре получает доступ к той же памяти, что и неэмулируемая инструкция на другом ядре. (Нативная версия не поддерживает глобальную блокировку).
Смотрите также этот патч в списке рассылки Xen, который изменяет lock cmpxchg8b
эмуляция для поддержки обоих lock cmpxchg8b
а также lock cmpxchg16b
,
Я также обнаружил, что эмулятор KVM x86 не поддерживает cmpxchg16b
либо, согласно результатам поиска для emulate cmpxchg16b
,
Я думаю, что все это является хорошим доказательством того, что мой анализ верен, и что невозможно безопасно подражать ему.
Я вижу эти вещи не так с вашим кодом, чтобы подражать cmpxchg16b
инструкция:
Вам нужно использовать
cmp
вместоtest
чтобы получить правильное сравнение.Вам необходимо сохранить / восстановить все флаги, кроме ZF. Руководство упоминает:
Флаги CF, PF, AF, SF и OF не затрагиваются.
Руководство содержит следующее:
IF (64-Bit Mode and OperandSize = 64) THEN TEMP128 ← DEST IF (RDX:RAX = TEMP128) THEN ZF ← 1; DEST ← RCX:RBX; ELSE ZF ← 0; RDX:RAX ← TEMP128; DEST ← TEMP128; FI; FI
Таким образом, чтобы действительно написать код, который "соответствует функциональной спецификации, приведенной в руководстве", требуется запись в m128. Хотя эта конкретная запись является частью заблокированной версии lock cmpxchg16b
Конечно, это не принесет пользы атомарности эмуляции! Простая эмуляция lock cmpxchg16b
Таким образом, это невозможно. Смотрите ответ @PeterCordes.
Эта инструкция может использоваться с префиксом LOCK, чтобы позволить инструкции выполняться атомарно. Чтобы упростить интерфейс с шиной процессора, целевой операнд получает цикл записи без учета результата сравнения.
ELSE:
MOV RAX, r10
MOV RDX, r11
MOV QWORD PTR [r8], r10
MOV QWORD PTR [r8+8], r11
END: