Что означает `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 разрешение на каталог.