Je testais du code sur Visual Studio 2008 et j'ai remarqué security_cookie
. Je peux en comprendre le sens, mais je ne comprends pas 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 exactement ce préfixe en contexte avec le 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 normal.
Le code où je l'ai trouvé était ici (injecté par le compilateur pour la 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
}
}
Il y a tout un blog nommé d'après cette instruction. Et le premier article en décrit la raison: http://repzret.org/p/repzret/
Fondamentalement, il y avait un problème dans le prédicteur de branche d'AMD quand un seul octet ret
a immédiatement suivi un saut conditionnel comme dans le code que vous avez cité (et quelques autres situations), et la solution de contournement a été d'ajouter le rep
prefix, qui est ignoré par le CPU mais corrige la pénalité du prédicteur.
Apparemment, certains prédicteurs de branche de processeurs AMD se comportent mal lorsque la cible ou la chute 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'instructions dans le Intel Instruction Set Reference , 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 chaîne.
Cela signifie au moins que le rep
n'a pas à se comporter de manière répétitive.
Maintenant, à partir de la référence du jeu d'instructions AMD (1.2.6 Répétition des préfixes):
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 répertoriées dans les tableaux 1-6, 1-7 et 1-8 ci-dessus [qui ne contiennent pas ret].
Cela ressemble donc vraiment à un comportement non défini, mais on peut supposer que, dans la pratique, les processeurs ignorent simplement les préfixes rep
sur les instructions ret
.
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 (Barcelone) recommande dans ces cas ret 0
De 3 octets, qui libère 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 que personne ne soit passé à l'utilisation de 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 fera 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). Ainsi, la plupart des binaires Linux ont un repz ret
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 envisager de réaffecter la séquence d'octets f3 c3
Comme (partie de) une instruction différente.
Il y aura toujours d'anciens binaires fermés utilisant rep ret
Qui n'ont pas de versions plus récentes disponibles, et que quelqu'un doit continuer à exécuter, cependant. Donc, quelle que soit la nouvelle fonctionnalité f3 c3 != rep ret
, Elle devra être désactivée (par exemple avec un paramètre BIOS), et ce paramètre changera réellement le comportement du décodeur d'instructions pour reconnaître f3 c3
Comme rep ret
. Si cette compatibilité descendante pour les binaires hérités n'est pas possible (car cela ne peut pas être fait de manière efficace en termes de puissance et de transistors), identifiez le type de délai que vous envisagez. Beaucoup plus longtemps que 15 ans, à moins que ce ne soit un processeur que pour une partie du marché.
Il est donc sûr d'utiliser rep ret
, Car tout le monde le fait déjà. Utiliser ret 0
Est une mauvaise idée. Dans le nouveau code, c'est peut-être toujours une bonne idée d'utiliser rep ret
Pendant encore quelques années. Il n'y a probablement pas trop de processeurs AMD PhenomII encore autour, mais ils sont assez lents sans erreurs de prédiction d'adresse de retour supplémentaires ou sans le problème.
Le coût est assez faible. Il ne finit pas par prendre de l'espace supplémentaire dans la plupart des cas, car il est généralement suivi de nop
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 sont nécessaires pour atteindre la prochaine limite de 16B. gcc ne peut s'aligner que de 8B dans ce cas. (avec .p2align 4,,10;
pour aligner sur 16B s'il faut 10 octets nop ou moins, puis 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 estimons qu'un sur 16 rep ret
Finit par créer un rembourrage supplémentaire là où un ret
aurait juste atteint l'alignement souhaité, et que le rembourrage 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 beaucoup de choses. Par exemple, Firefox avec toutes les bibliothèques qu'il a mappées n'a que 9k instances de rep ret
. C'est donc environ 4k octets, sur de nombreux fichiers. (Et moins 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
Cela compte rep ret
Dans toutes les fonctions de toutes les bibliothèques que firefox a mappées, pas seulement les fonctions qu'il appelle jamais. Ceci est quelque peu pertinent, car une densité de code plus faible entre les fonctions signifie que vos appels sont répartis sur davantage 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 uop-cache d'Intel). Quoi qu'il en soit, rep ret
A un très petit impact.
Il m'a fallu une minute pour réfléchir à une raison pour laquelle /proc/<pid>/map_files/
N'est pas accessible au propriétaire du processus, mais /proc/<pid>/maps
L'est. Si un processus UID = root (par exemple à partir d'un binaire suid-root) mmap(2)
est 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é par le manque d'autorisation x for other
sur le répertoire.