Что означает `rep ret`?
Я тестировал код на Visual Studio 2008 и заметил security_cookie
. Я могу понять суть этого, но я не понимаю, какова цель этой инструкции.
rep ret /* REP to avoid AMD branch prediction penalty */
Конечно, я могу понять комментарий 🙂 но что это за префикс exaclty в контексте с ret
и что происходит, если ecx
is! = 0? По-видимому, количество циклов из ecx
игнорируется, когда я его отлаживаю, что и следовало ожидать.
Код, где я нашел это, был здесь (введенный компилятором для обеспечения безопасности):
- Как: pow (real, real) в x86
- Как написать самомодифицирующийся код в сборке x86
- Код C ++ для проверки гипотезы Collatz быстрее, чем assembly вручную - почему?
- Разница в производительности между MSVC и GCC для высоко оптимизированного матричного кода
- Как отключить компьютер из автономной среды?
void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie) { /* x86 version written in asm to preserve all regs */ __asm { cmp ecx, __security_cookie jne failure rep ret /* REP to avoid AMD branch prediction penalty */ failure: jmp __report_gsfailure } }
- Режимы адресации на языке сборки (IA-32 NASM)
- Что делает NOPL в системе x86?
- Windows 64-разрядный реестр против 32-разрядного реестра
- Использование LEA для значений, которые не являются адресами / указателями?
- Ссылка на содержимое ячейки памяти. (режимы адресации x86)
- Как получить доступ к массиву символов и изменить строчные буквы на верхний регистр, и наоборот
- Улучшенный REP MOVSB для memcpy
- Почему оставить «mov esp, ebp» в сборке x86?
Там есть целый блог, названный в честь этой инструкции. И первое сообщение описывает причину этого: http://repzret.org/p/repzret/
В принципе, в предикторе ветвления AMD возникла проблема, когда однобайтовый ret
сразу же следовал за условным переходом, как в коде, который вы цитировали (и в нескольких других ситуациях), и обходным путем было добавить префикс rep
, который игнорируется CPU, но фиксирует штраф предиктора.
По-видимому, некоторые предсказатели отраслевых процессоров AMD ведут себя плохо, когда целью или падением филиала является команда ret
, и добавление префикса rep
позволяет избежать этого.
Что касается значения rep ret
, в инструкции по набору инструкций Intel не упоминается эта последовательность команд , и документация rep
не очень помогает:
Поведение префикса REP не определено при использовании с нестроковыми инструкциями.
Это означает, что, по крайней мере, rep
не должен вести себя повторяющимся образом.
Теперь из справочника набора инструкций AMD (1.2.6 Repeat Prefixes):
Префиксы должны использоваться только с такими строковыми инструкциями.
В общем случае префиксы повтора должны использоваться только в строковых инструкциях, перечисленных в таблицах 1-6, 1-7 и 1-8 выше [которые не содержат ret].
Так что это действительно похоже на неопределенное поведение, но можно предположить, что на практике процессоры просто игнорируют префиксы rep
на инструкциях ret
.
Как указывает ответ Триллиана, AMD K8 и K10 имеют проблему с предсказанием ветвей, когда ret
является целью ветвления или следует условной ветви.
Руководство по оптимизации AMD для K10 (Barcelona) рекомендует 3-байтный ret 0
в тех случаях, который выдает нулевые байты из стека, а также возвращает. Эта версия значительно хуже, чем rep ret
на Intel. По иронии судьбы, это также хуже, чем rep ret
на более поздних процессорах AMD (Bulldozer и далее). Поэтому неплохо, что никто не изменил использование ret 0
основе обновления руководства AMD Family 10 для оптимизации.
Руководства для процессора предупреждают, что будущие процессоры могут по-разному интерпретировать комбинацию префикса и инструкции, которую он не модифицирует. Это верно в теории, но никто не собирается создавать процессор, который не может запускать много существующих двоичных файлов.
gcc по-прежнему использует rep ret
по умолчанию (без -mtune=intel
или -march=haswell
или что-то еще). Таким образом, большинство бинарных файлов Linux имеют где-то repz ret
.
gcc, вероятно, перестанет использовать rep ret
в течение нескольких лет, как только K10 будет полностью устаревшим. Спустя еще 5 или 10 лет почти все двоичные файлы будут построены с использованием gcc более новой версии. Еще через 15 лет производитель ЦП может подумать о повторении последовательности байтов f3 c3
как (часть) другой инструкции.
По-прежнему будут существовать старые двоичные файлы с закрытым исходным кодом, использующие rep ret
которые не имеют более свежих сборок, и что кто-то должен продолжать работать. Таким образом, какова бы ни была новая функция f3 c3 != rep ret
, она должна быть отключена (например, с настройкой BIOS), и эта настройка действительно изменяет поведение инструкции-декодера, чтобы распознать f3 c3
как rep ret
. Если эта обратная совместимость для устаревших двоичных файлов невозможна (потому что она не может быть эффективно реализована с точки зрения мощности и транзисторов), IDK, на какой временной шкале вы будете смотреть. Гораздо больше, чем 15 лет, если только это не было процессором только для части рынка.
Поэтому безопасно использовать rep ret
, потому что все остальные уже делают это. Использование ret 0
– плохая идея. В новом коде может еще неплохо использовать rep ret
еще пару лет. Вероятно, не так уж много процессоров AMD PhenomII по-прежнему вокруг, но они достаточно медленны без лишних ошибочных ошибок обратного адреса или проблемы с сетью.
Стоимость довольно маленькая. В большинстве случаев он не занимает лишнего места, потому что в любом случае за ним обычно следует nop
padding. Однако в тех случаях, когда это приводит к дополнительному заполнению, это будет наихудший случай, когда требуется 15 бит заполнения для достижения следующей границы 16B. В этом случае gcc может выравниваться только на 8B. (с .p2align 4,,10;
для выравнивания до 16B, если он будет принимать 10 или меньше nop-байтов, тогда .p2align 3
всегда будет соответствовать 8B. Используйте gcc -S -o-
для вывода asm-выхода в stdout, чтобы увидеть, когда он делает это.)
Поэтому, если мы предположим, что один из 16 rep ret
конечном итоге создаст дополнительное дополнение, в котором ret
только что достигнет желаемого выравнивания, и что дополнительное заполнение идет до границы 8B, это означает, что каждый rep
имеет среднюю стоимость 8 * 1 / 16 = половина байт.
rep ret
не используется достаточно часто, чтобы скомпенсировать многое. Например, firefox со всеми библиотеками, которые он отобразил, имеет только ~ 9k экземпляров rep ret
. Так что это около 4k байт, во многих файлах. (И меньше оперативной памяти, чем это, поскольку многие из этих функций в динамических библиотеках никогда не называются.)
# disassemble every shared object mapped by a process. ffproc=/proc/$(pgrep firefox)/ objdump -d "$ffproc/exe" $(sudo ls -l "$ffproc"/map_files/ | awk '/\.so/ {print $NF}' | sort -u) | grep 'repz ret' -c objdump: '(deleted)': No such file # I forgot to restart firefox after the libexpat security update 9649
Это считает rep ret
во всех функциях во всех библиотеках, которые отображал firefox, а не только о функциях, которые он когда-либо звонил. Это несколько актуально, потому что более низкая плотность кода по функциям означает, что ваши вызовы распределены по большему количеству страниц памяти. ITLB и L2-TLB имеют ограниченное количество записей. Локальная плотность имеет значение для L1I $ (и uop-cache от Intel). Во всяком случае, rep ret
оказывает очень незначительное влияние.
Мне потребовалась минута, чтобы подумать о причине, что /proc/
недоступен для владельца процесса, но /proc/
. Если UID = корневой процесс (например, из двоичного файла suid-root) mmap(2)
sa 0666, который находится в каталоге 0700, то делает setuid(nobody)
, любой, кто работает с этим двоичным файлом, может обойти ограничение доступа, наложенное отсутствием x for other
разрешений в каталоге.