Comment sauvegarder les registres sur x86 64 pour une routine d'interruption de service?
je regarde un vieux code d'un projet scolaire, et en essayant de le compiler sur mon ordinateur portable j'ai rencontré quelques problèmes. Il a été écrit à l'origine pour une ancienne version 32 bits de gcc. Bref, j'essayais de convertir une partie de l'assemblage en code compatible 64 bits et j'ai eu quelques problèmes.
Voici le code original:
pusha
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushl %ss
pusha
n'est pas valide en mode 64 bits. Donc, quelle serait la bonne façon de faire ceci dans x86_64 assembly alors qu'en 64 bits mode?
Il y a obtenu d'être une raison pourquoi pusha
n'est pas valide en mode 64 bits, j'ai donc un sentiment manuellement en poussant tous les registres peut être pas une bonne idée.
4 réponses
Apprendre code qui fait ce genre de chose. Par exemple:
- Linux (rechercher
SAVE_ARGS_IRQ
): entry_64.S - OpenSolaris (rechercher
INTR_PUSH
): privregs.h - FreeBSD (rechercher
IDT_VEC
): exception.S (similaire est vecteur.S in NetBSD)
en fait, "pousser manuellement" le regs est le seul moyen sur AMD64 depuis PUSHA
n'existe pas là-bas. AMD64 n'est pas unique à cet égard - la plupart des CPU non-x86 nécessitent des sauvegardes/restaurations registre par Registre à un moment donné.
mais si vous inspectez de près le code source référencé, vous constaterez que tous les gestionnaires d'interruptions n'ont pas besoin de sauvegarder/restaurer l'ensemble du jeu de registres, donc il y a de la place pour des optimisations.
AMD avait besoin d'espace pour ajouter de nouveaux codes Op pour REX
préfixes et quelques autres nouvelles instructions quand ils ont développé les extensions x86 64 bits. Ils ont changé la signification de certains opérateurs de ces nouvelles instructions.
plusieurs des instructions étaient simplement des instructions abrégées ou n'étaient pas nécessaires. PUSHA
a été l'une des victimes. On ne sait pas très bien pourquoi ils ont banni PUSHA
cependant, il ne semble pas y avoir de chevauchement entre les nouvelles instruction opcodes. Peut-être qu'ils sont réservés à l' PUSHA
et POPA
opcodes pour une utilisation future, car ils sont complètement redondants et ne seront pas plus rapides et ne se produiront pas assez fréquemment dans le code pour avoir de l'importance.
l'ordre de PUSHA
a l'ordre de l'instruction de codage: eax
,ecx
,edx
,ebx
,esp
,ebp
,esi
,edi
. Notez qu'il redondante poussé esp
! Vous avez besoin de savoir esp
pour trouver les données dont il a poussé!
si vous convertissez code de 64 bits PUSHA
le code n'est pas bon de toute façon, vous devez le mettre à jour pour pousser les nouveaux registres r8
thru r15
. Vous devez également sauvegarder et restaurer un État SSE beaucoup plus grand,xmm8
thru xmm15
. En supposant que vous allez tabasser.
si le code du gestionnaire d'interruption est simplement un raccourci qui renvoie au code C, vous n'avez pas besoin de sauvegarder tous les registres. Vous pouvez supposer que le compilateur C générera du code qui préservera rbx
,rbp
, rsi
,rdi
et r12
thru r15
. Vous devez seulement sauver et restaurer rax
,rcx
,rdx
et r8
thru r11
. (Note: sur les plateformes Linux ou autres systèmes V ABI, le compilateur préservera rbx
,rbp
,r12
-r15
, vous pouvez vous attendre rsi
et rdi
ridiculiser).
les registres de segment n'ont aucune valeur en mode long (si le thread interrompu est en mode de compatibilité 32 bits, vous devez préserver les registres de segment, merci ughoavgfhw). En fait, ils se sont débarrassés de la plupart de la segmentation en mode long, mais FS
est toujours réservé pour les systèmes d'exploitation à utiliser comme adresse de base pour les données locales de thread. La valeur de registre lui-même n'a pas d'importance, la base de FS
et GS
sont définies par le biais de MSRs 0xC0000100
et 0xC0000101
. En supposant que vous n'utiliserez pas FS
vous n'avez pas besoin de vous en inquiéter, rappelez-vous juste que n'importe quel thread local de données accédé par le code C pourrait utiliser n'importe quelle les tls de random thread. Faites attention car les bibliothèques d'exécution C utilisent TLS pour certaines fonctionnalités (exemple: strtok utilise généralement TLS).
Chargement d'une valeur dans FS
ou GS
(même en mode utilisateur) va remplacer le FSBASE
ou GSBASE
MSR. Depuis certains systèmes d'exploitation utilisent GS
en tant que stockage" processeur local " (ils ont besoin d'un moyen d'avoir un pointeur vers une structure pour chaque CPU), ils ont besoin de le garder quelque part qui ne sera pas bloqué par le chargement GS
en mode utilisateur. De résolvez ce problème, il y a deux MSR réservés pour le GSBASE
enregistrer: un actif et un caché. En mode noyau, le noyau est GSBASE
est tenu dans l'habituel GSBASE
MSR et le mode utilisateur de base est dans l'autre (caché) GSBASE
MSR. Lorsque le contexte passe du mode noyau à un mode utilisateur, et lors de la sauvegarde d'un contexte de mode utilisateur et de l'entrée en mode noyau, le code de changement de contexte doit exécuter L'instruction SWAPGS, qui échange les valeurs des GSBASE
MSR. Depuis le noyau GSBASE
est bien caché dans L'autre MSR en mode utilisateur, le code du mode utilisateur ne peut pas assommer le noyau GSBASE
en chargeant une valeur dans GS
. Lorsque le mode CPU renoue avec le noyau, le code de sauvegarde du contexte s'exécute SWAPGS
et restaurer le noyau GSBASE
.
pusha
n'est pas valide en mode 64 bits, car il est redondant. Pousser chaque registre individuellement est exactement la chose à faire.
.macro pushaq
push %rax
push %rcx
push %rdx
push %rbx
push %rbp
push %rsi
push %rdi
.endm # pushaq
et
.macro popaq
pop %rdi
pop %rsi
pop %rbp
pop %rbx
pop %rdx
pop %rcx
pop %rax
.endm # popaq
et éventuellement ajouter les autres registres r8-15 si on a besoin de