В x86 (сборка), является ли [repz retq] действительным? [Дубликат]

Порядок, в котором указаны взаимозависимые связанные библиотеки, неверен.

Порядок, в котором связаны библиотеки, имеет значение, если библиотеки зависят друг от друга. В общем случае, если библиотека A зависит от библиотеки B, тогда libA ДОЛЖЕН появляться перед libB в флагах компоновщика.

Например:

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};

Создайте библиотеки:

$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o

Скомпилируйте:

$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out

Чтобы повторить снова, порядок имеет значение!

35
задан Devolus 11 December 2013 в 19:48
поделиться

3 ответа

Существует целый блог, названный в честь этой инструкции. И первое сообщение описывает причину этого: http://repzret.org/p/repzret/

В принципе, в предсказании ветви AMD произошла ошибка, когда один -byte ret сразу же следует за условным прыжком, как в коде, который вы цитировали (и в нескольких других ситуациях), и обходным путем было добавить префикс rep, который игнорируется CPU, но фиксирует штраф предиктора.

39
ответ дан Igor Skochinsky 29 August 2018 в 00:24
поделиться

Как указывает ответ Триллиана, 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 $ (и uop-cache от Intel). Во всяком случае, rep ret имеет очень незначительное влияние.

Мне потребовалась минута, чтобы подумать о причине, что /proc/<pid>/map_files/ недоступен для владельца процесса, но /proc/<pid>/maps. Если UID = корневой процесс (например, из двоичного файла suid-root) mmap(2) sa 0666, который находится в каталоге 0700, то делает setuid(nobody), любой, кто работает с этим двоичным файлом, может обойти ограничение доступа, наложенное отсутствием x for other разрешение на каталог.

15
ответ дан Peter Cordes 29 August 2018 в 00:24
поделиться

По-видимому, некоторые предсказатели ветвлений процессоров AMD плохо себя ведут, когда цель или провал ветки является инструкцией ret, и добавление префикса rep позволяет избежать этого.

Что касается значения rep ret, эта инструкция не упоминается в Справочном наборе инструкций Intel , а документация по rep не очень полезна:

Поведение префикс REP не определен при использовании с нестроковыми инструкциями.

Это означает, что по крайней мере, rep не должен вести себя повторяющимся образом.

Теперь из набора инструкций AMD (1.2.6 Repeat Prefixes):

Префиксы должны использоваться только с такими строковыми инструкциями.

Как правило, префиксы повтора должны использоваться только в строковых инструкциях, перечисленных в таблицах 1-6, 1-7 и 1-8 выше [которые не содержат ret].

Так что это действительно похоже на неопределенное поведение, но можно предположить, что на практике e, процессоры просто игнорируют префиксы rep в инструкциях ret.

18
ответ дан Trillian 29 August 2018 в 00:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: