Что означает `rep ret`?
Я тестировал некоторый код в Visual Studio 2008 и заметил security_cookie
. Я могу понять суть этого, но я не понимаю, какова цель этой инструкции.
rep ret /* REP to avoid AMD branch prediction penalty */
Конечно, я могу понять комментарий:), но что это за префикс exaclty в контексте с ret
, и что произойдет, если ecx
is!= 0? По-видимому, количество циклов из ecx
игнорируется, когда я его отлаживаю, что и следовало ожидать.
Код, где я нашел это, был здесь (введенный компилятором для обеспечения безопасности):
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
}
}
Ответы
Ответ 1
Там весь блог, названный в честь этой инструкции. И первое сообщение описывает причину этого: http://repzret.org/p/repzret/
В принципе, в предикторе ветвления AMD возникла проблема, когда однобайтовый ret
сразу же следовал за условным переходом, как в коде, который вы цитировали (и в нескольких других ситуациях), и обходным путем было добавить rep
, который игнорируется процессором, но фиксирует штраф предиктора.
Ответ 2
По-видимому, некоторые предсказатели ветвления процессоров AMD ведут себя плохо, когда цель ветвления или провал - это инструкция ret
, и добавление префикса rep
позволяет избежать этого.
Что касается значения rep ret
, то в этой инструкции инструкций Intel не указывается, а документация rep
не является очень полезно:
Поведение префикса REP равно undefined при использовании с нестроковыми инструкциями.
Это означает, что, по крайней мере, rep
не должен вести себя повторяющимся образом.
Теперь из справочника по набору инструкций AMD (1.2.6 Repeat Prefixes):
Префиксы должны использоваться только с такими строковыми инструкциями.
В общем случае префиксы повтора должны использоваться только в строковых инструкциях, перечисленных в таблицах 1-6, 1-7 и 1-8 выше [которые не содержат ret].
Итак, это действительно похоже на поведение undefined, но можно предположить, что на практике процессоры просто игнорируют префиксы rep
в инструкциях ret
.
Ответ 3
Как указывает триллианский ответ, 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
. Однако в тех случаях, когда это приводит к дополнительному заполнению, это будет наихудший случай, когда требуется 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 $(и Intel uop-cache). Во всяком случае, rep ret
оказывает очень незначительное влияние.
Мне потребовалась минута, чтобы подумать о причине, что /proc/<pid>/map_files/
недоступен для владельца процесса, но /proc/<pid>/maps
is. Если UID = корневой процесс (например, из двоичного файла suid-root) mmap(2)
a 0666, который в каталоге 0700, а затем setuid(nobody)
, любой, кто работает с этим двоичным файлом, может обойти ограничение доступа, наложенное отсутствием x for other
разрешение на каталог.