Que signifie "rep ret"?

Je testais du code sur Visual Studio 2008 et j'ai remarqué security_cookie. Je peux comprendre le but de cela, mais je ne comprends pas quel est le but de cette instruction.

    rep ret /* REP to avoid AMD branch prediction penalty */

Bien sûr, je peux comprendre le commentaire :) mais que fait ce préfixe exaclty dans le contexte du ret et que se passe-t-il si ecx est != 0? Apparemment, le nombre de boucles de ecx est ignoré lorsque je le débogue, ce qui est à prévoir.

Le code où j'ai trouvé ceci était ici (injecté par le compilateur pour de sécurité):

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
    }
}
35
demandé sur Devolus 2013-12-11 21:48:54

3 réponses

Il y a un blog entier nommé d'après cette instruction. Et le premier post décrit la raison derrière elle: http://repzret.org/p/repzret/

Fondamentalement, il y avait un problème dans le prédicteur de branche D'AMD lorsqu'un seul octet ret suivait immédiatement un saut conditionnel comme dans le code que vous avez cité (et quelques autres situations), et la solution consistait à ajouter le préfixe rep, qui est ignoré par le CPU mais corrige la pénalité du prédicteur.

39
répondu Igor Skochinsky 2013-12-11 18:16:42

Apparemment, les prédicteurs de branche de certains processeurs AMD se comportent mal lorsque la cible ou le fallthrough d'une branche est une instruction ret, et l'ajout du préfixe rep évite cela.

, quant à la signification de rep ret, il n'y a aucune mention de cette séquence d'instruction dans le Intel Jeu d'Instructions de Référence, et la documentation de rep n'est pas très utile:

Le comportement du préfixe REP n'est pas défini lorsqu'il est utilisé avec des instructions non-string.

Ce signifie au moins que le rep n'a pas à se comporter de manière répétée.

Maintenant, à partir de la référence du jeu D'instructions AMD (1.2.6 préfixes de répétition):

Les préfixes ne doivent être utilisés qu'avec de telles instructions de chaîne.

En général, les préfixes de répétition ne doivent être utilisés que dans les instructions de chaîne listées dans les tableaux 1-6, 1-7 et 1-8 ci-dessus [qui ne contiennent pas de ret].

Donc cela semble vraiment être un comportement indéfini mais on peut supposer que, en pratique, les processeurs ignorent simplement les préfixes rep sur les instructions ret.

18
répondu Trillian 2013-12-11 18:10:48

Comme le souligne la réponse de Trillian, AMD K8 et K10 ont un problème avec la prédiction de branche lorsque ret est une cible de branche ou suit une branche conditionnelle.

Le guide d'optimisation D'AMD pour K10 (Barcelona) recommande 3 octets ret 0 dans ces cas, qui affiche zéro octet de la pile ainsi que le retour. Cette version est nettement pire que rep ret sur Intel. Ironiquement, c'est aussi pire que rep ret sur les processeurs AMD ultérieurs (Bulldozer et suivants.) C'est donc une bonne chose personne n'a changé pour utiliser ret 0 Basé sur la mise à jour du Guide d'optimisation de la famille 10 D'AMD.


Les manuels du processeur avertissent que les futurs processeurs pourraient interpréter différemment une combinaison d'un préfixe et d'une instruction qu'il ne modifie pas. C'est vrai en théorie, mais personne ne va faire un processeur qui ne peut pas exécuter beaucoup de binaires existants.

Gcc utilise toujours rep ret par défaut (Sans -mtune=intel, ou -march=haswell ou quelque chose). Donc, la plupart des binaires Linux ont un repz ret en eux quelque part.

Gcc cessera probablement d'utiliser rep ret dans quelques années, une fois que K10 sera complètement obsolète. Après encore 5 ou 10 ans, presque tous les binaires seront construits avec un gcc plus récent que cela. Un autre 15 ans après cela, un fabricant de CPU pourrait penser à réutiliser la séquence d'octets f3 c3 en tant que (partie) d'une instruction différente.

Il y aura toujours des binaires à source fermée hérités utilisant rep ret qui n'ont pas de versions plus récentes disponibles, et que quelqu'un doit conserver en cours d'exécution, si. Donc, quelle que soit la nouvelle fonctionnalité f3 c3 != rep ret dont fait partie devrait être désactivée (par exemple avec un paramètre BIOS), et que ce paramètre change réellement le comportement du décodeur d'instructions pour reconnaître f3 c3 comme rep ret. Si cette rétrocompatibilité pour les binaires hérités n'est pas possible (car cela ne peut pas être fait efficacement en termes de puissance et de transistors), IDK quel genre de laps de temps vous regarderiez. Beaucoup plus longtemps que les années 15, à moins que ce ne soit un processeur pour une partie seulement de la marché.

Il est donc sûr d'utiliser rep ret, parce que tout le monde le fait déjà. Utiliser ret 0 est une mauvaise idée. Dans le nouveau code, il est peut-être toujours une bonne idée d'utiliser rep ret pendant quelques années. Il n'y a probablement pas trop de processeurs AMD PhenomII encore là, mais ils sont assez lents sans erreurs d'adresse de retour supplémentaires ou w / e le problème est.


Le coût est assez faible. Il ne finit pas par prendre de place supplémentaire dans la plupart des cas, car il est généralement suivi de nop rembourrage de toute façon. Cependant, dans les cas où cela entraîne un remplissage supplémentaire, ce sera le pire des cas où 15B de remplissage est nécessaire pour atteindre la limite 16B suivante. gcc ne peut aligner que par 8B dans ce cas. (avec .p2align 4,,10; pour aligner sur 16B s'il faut 10 octets nop ou moins, alors un .p2align 3 pour toujours aligner sur 8B. utilisez gcc -S -o- pour produire une sortie asm vers stdout pour voir quand il le fait.)

Donc, si nous devinons que celui en 16 rep ret finissent par créer un remplissage supplémentaire où un ret aurait juste appuyez sur l'alignement désiré, et que le remplissage supplémentaire va à une limite 8B, cela signifie que chaque rep a un coût moyen de 8 * 1/16 = un demi-octet.

rep ret n'est pas utilisé assez souvent pour ajouter jusqu'à beaucoup de chose. Par exemple, firefox avec toutes les bibliothèques qu'il a mappées n'a que des instances ~9k de rep ret. Donc, c'est environ 4K octets, à travers de nombreux fichiers. (Et moins de RAM que cela, car beaucoup de ces fonctions dans les bibliothèques dynamiques ne sont jamais appelées.)

# 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

Ça compte {[3] } dans toutes les fonctions de toutes les bibliothèques que firefox a mappées, pas seulement les fonctions qu'il appelle. Ceci est quelque peu pertinent, car une densité de code plus faible entre les fonctions signifie que vos appels sont répartis sur plus de pages de mémoire. ITLB et L2-TLB n'ont qu'un nombre limité d'entrées. La densité locale est importante pour L1I$ (et le cache UOP D'Intel). Quoi qu'il en soit, rep ret a un très petit impact.

, Il m'a fallu une minute pour penser à une raison que /proc/<pid>/map_files/ n'est pas accessible pour le propriétaire du processus, mais /proc/<pid>/maps est. Si un processus UID = root (par exemple à partir d'un binaire Suid-root) mmap(2)S un fichier 0666 qui se trouve dans un répertoire 0700, alors setuid(nobody), toute personne exécutant ce binaire pourrait contourner la restriction d'accès imposée par l'absence d'autorisation x for other sur le répertoire.

15
répondu Peter Cordes 2016-12-30 05:02:03