vdso – Présentation de l’objet partagé dynamique ELF
virtuel
#include <sys/auxv.h>
void *vdso = (uintptr_t) getauxval(AT_SYSINFO_EHDR);
Le « vDSO » (objet partagé dynamique virtuel,
« virtual dynamic shared object ») est une petite
bibliothèque partagée que le noyau projette automatiquement dans
l’espace d’adresses de toutes les applications en espace
utilisateur. Les applications n’ont normalement pas besoin de
s’occuper elles-mêmes de ces détails puisque le vDSO est
d’habitude appelé par la bibliothèque C. Ainsi,
vous pouvez écrire du code normalement en utilisant les fonctions
standards et la bibliothèque C s’occupera
d’utiliser toutes les fonctionnalités disponibles par
l’intermédiaire du vDSO.
Pourquoi le vDSO existe ? Certains appels système fournis par le
noyau finissent par être utilisés fréquemment par le code
en espace utilisateur, au point que ces appels peuvent avoir une emprise
excessive sur les performances. C’est à la fois dû
à la fréquence des appels qu’aux nombreux changements de
contexte à force de sortir de l’espace utilisateur pour entrer
dans le noyau.
La suite de cette documentation est orientée pour les curieux et les
auteurs de la bibliothèque C plutôt que pour les
développeurs généraux. Si vous essayez d’appeler
le vDSO dans vos propres applications plutôt que d’utiliser la
bibliothèque C, vous faites sans doute fausse route.
Réaliser des appels système peut être lent. Dans les
systèmes 32 bits x86, vous pouvez déclencher une
interruption logicielle (
int $0x80) pour indiquer au noyau que vous
voulez faire un appel système. Cependant, cette instruction est
coûteuse : elle passe par tous les chemins complets de
traitement des interruptions dans le microcode du processeur ainsi que dans le
noyau. Les nouveaux processeurs ont des instructions plus rapides (mais non
rétrocompatibles) pour initier les appels système. Plutôt
que forcer la bibliothèque C à vérifier si cette
fonctionnalité est disponible au moment de l’exécution,
la bibliothèque C peut utiliser les fonctions fournies par le
noyau dans le vDSO.
Remarquez que cette terminologie peut être source de confusion. Sur les
systèmes x86, la fonction vDSO utilisée pour déterminer
la méthode préférée pour réaliser un appel
système est appelée
« __kernel_vsyscall » alors que sous x86_64, le
terme « vsyscall » se réfère aussi
à une façon obsolète de demander au noyau l’heure
ou le processeur sur lequel est l’appelant.
One frequently used system call is
gettimeofday(2). This system call is
called both directly by user-space applications as well as indirectly by the C
library. Think timestamps or timing loops or polling—all of these
frequently need to know what time it is right now. This information is also
not secret—any application in any privilege mode (root or any
unprivileged user) will get the same answer. Thus the kernel arranges for the
information required to answer this question to be placed in memory the
process can access. Now a call to
gettimeofday(2) changes from a system
call to a normal function call and a few memory accesses.
L’adresse de base du vDSO (s’il existe) est passée par le
noyau à tous les programmes dans le vecteur auxiliaire initial
(consultez
getauxval(3)) à l’aide de l’indicateur
AT_SYSINFO_EHDR.
Vous ne devez pas supposer que le vDSO est projeté à un endroit
particulier de la projection en mémoire de l’utilisateur.
L’adresse de base sera normalement aléatoire au moment de
l’exécution à chaque fois qu’une nouvelle image de
processus est créée (au moment de
execve(2)).
C’est ainsi pour des raisons de sécurité, afin
d’éviter les attaques de « retour vers
libc ».
Pour certaines architectures, un indicateur
AT_SYSINFO est aussi
présent. Il n’est utilisé que pour localiser le point
d’entrée vsyscall et est souvent omis ou défini à
0 (signifiant qu’il n’est pas disponible). Cet indicateur est un
rappel du fonctionnement initial de vDSO (consultez
Historique
ci-dessous) et son utilisation devrait être évitée.
Puisque le vDSO est une image ELF complète, vous pouvez y rechercher des
symboles. Cela permet d’ajouter de nouveaux symboles avec les versions
de noyau plus récentes et permet à la
bibliothèque C de détecter les fonctionnalités
disponibles au moment de l’exécution lors de
l’exécution sous différentes versions de noyau.
D’habitude, la bibliothèque C fera la détection
lors du premier appel puis mettra en cache le résultat pour les appels
suivants.
Tous les appels sont aussi versionnés (en utilisant le format de version
GNU). Cela permet au noyau de mettre à jour la signature de fonction
sans casser la rétrocompatibilité. Cela signifie modifier les
arguments acceptés par la fonction et la valeur de retour. Ainsi, lors
de la recherche de symboles dans le vDSO, vous devez toujours inclure la
version pour correspondre à l’ABI attendue.
Typiquement, le vDSO suit la convention de nommage de préfixer tous les
symboles par « __vdso_ » ou
« __kernel_ » afin de les distinguer des autres
symboles standards. Par exemple, la fonction
« gettimeofday » est nommée
« __vdso_gettimeofday ».
Utilisez les conventions d’appel C standard pour appeler
n’importe laquelle de ces fonctions. Pas la peine de vous
embêter avec les registres bizarres ou les comportements de pile.
Lors de la compilation du noyau, le code vDSO est compilé et lié
automatiquement. Il se trouve souvent dans le répertoire
spécifique à l’architecture :
find arch/$ARCH/ -name '*vdso*.so*' -o -name '*gate*.so*'
Le nom du vDSO dépend des architectures. Il est souvent visible dans des
endroits comme la sortie de
ldd(1) de la glibc. Le nom exact ne devrait
affecter aucun code, donc pas la peine de le coder en dur.
ABI utilisateur |
Nom vDSO |
|
aarch64 |
linux-vdso.so.1 |
arm |
linux-vdso.so.1 |
ia64 |
linux-gate.so.1 |
mips |
linux-vdso.so.1 |
ppc/32 |
linux-vdso32.so.1 |
ppc/64 |
linux-vdso64.so.1 |
riscv |
linux-vdso.so.1 |
s390 |
linux-vdso32.so.1 |
s390x |
linux-vdso64.so.1 |
sh |
linux-gate.so.1 |
i386 |
linux-gate.so.1 |
x86-64 |
linux-vdso.so.1 |
x86/x32 |
linux-vdso.so.1 |
Lors du suivi des appels système avec
strace(1), les symboles
(appels système) qui sont exportés par le vDSO
n’apparaîtront
pas dans la sortie du suivi. De
même, ces appels système ne seront pas visibles par les filtres
seccomp(2).
Les sous-sections suivantes fournissent des notes spécifiques aux
architectures sur le vDSO.
Remarquez que le vDSO utilisé est basé sur l’ABI du code en
espace utilisateur et non sur l’ABI du noyau. Ainsi, par exemple, si
vous exécutez un binaire ELF 32 bits i386, vous obtiendrez le
même vDSO que vous l’exécutiez avec un noyau
32 bits i386 ou avec un noyau 64 bits x86_64. Par
conséquent, le nom de l’ABI en espace utilisateur devrait
être utilisé pour déterminer la section suivante
adéquate.
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__vdso_gettimeofday |
LINUX_2.6 (exported since Linux 4.1) |
__vdso_clock_gettime |
LINUX_2.6 (exported since Linux 4.1) |
De plus, le portage ARM a une page de code pleine de fonctions utilitaires.
Puisque ce n’est qu’une page de code brut, aucune information
ELF n’existe pour faire la recherche de symboles ou le versionnage.
Elle fournit cependant une prise en charge pour plusieurs versions.
For information on this code page, it's best to refer to the kernel
documentation as it's extremely detailed and covers everything you need to
know:
Documentation/arm/kernel_user_helpers.rst.
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_rt_sigreturn |
LINUX_2.6.39 |
__kernel_gettimeofday |
LINUX_2.6.39 |
__kernel_clock_gettime |
LINUX_2.6.39 |
__kernel_clock_getres |
LINUX_2.6.39 |
As this CPU lacks a memory management unit (MMU), it doesn't set up a vDSO in
the normal sense. Instead, it maps at boot time a few raw functions into a
fixed location in memory. User-space applications then call directly into that
region. There is no provision for backward compatibility beyond sniffing raw
opcodes, but as this is an embedded CPU, it can get away with
things—some of the object formats it runs aren't even ELF based
(they're bFLT/FLAT).
Pour des renseignements sur cette page de code, mieux vaut consulter la
documentation publique :
http://docs.blackfin.uclinux.org/doku.php?id=linux-kernel:fixed-code
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_gettimeofday |
LINUX_2.6 (exportation depuis Linux 4.4) |
__kernel_clock_gettime |
LINUX_2.6 (exportation depuis Linux 4.4) |
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_sigtramp |
LINUX_2.5 |
__kernel_syscall_via_break |
LINUX_2.5 |
__kernel_syscall_via_epc |
LINUX_2.5 |
Le portage Itanium est un peu périlleux. En plus du vDSO ci-dessus, il a
aussi des « appels système légers »
(aussi appelés « appels système
rapides » ou « fsys »). Ils peuvent
être appelés à l’aide de l’assistant vDSO
__kernel_syscall_via_epc. Les appels système indiqués ici
ont la même sémantique que si vous les appeliez directement
à l’aide de
syscall(2), donc consultez la documentation
adéquate pour chacun d’entre eux. Le tableau suivant indique les
fonctions disponibles par ce mécanisme.
fonction |
|
clock_gettime |
getcpu |
getpid |
getppid |
gettimeofday |
set_tid_address |
Le portage parisc à une page de code pleine de fonctions utilitaires
appelée une page passerelle. Plutôt que d’utiliser
l’approche classique du vecteur auxiliaire ELF, il passe
l’adresse de la page au processus à l’aide du registre
SR2. Les permissions sur la page sont telles qu’exécuter
simplement ces adresses s’exécute automatiquement avec les
droits du noyau et pas en espace utilisateur. C’est ainsi afin de
correspondre au mode de fonctionnement HP-UX.
Puisque ce n’est qu’une page de code brut, aucune information ELF
n’existe pour faire la recherche de symboles ou le versionnage. Appelez
simplement l’adresse adéquate à l’aide de
l’instruction de branche, par exemple :
ble <offset>(%sr2, %r0)
offset |
fonction |
|
00b0 |
lws_entry (opérations CAS) |
00e0 |
set_thread_pointer (utilisé par la glibc) |
0100 |
linux_gateway_entry (syscall) |
Le tableau suivant indique les symboles exportés par le vDSO. Les
fonctions marquées avec un
* ne sont disponibles que si le noyau
est PowerPC64 (64 bits).
symbole |
version |
|
__kernel_clock_getres |
LINUX_2.6.15 |
__kernel_clock_gettime |
LINUX_2.6.15 |
__kernel_clock_gettime64 |
LINUX_5.11 |
__kernel_datapage_offset |
LINUX_2.6.15 |
__kernel_get_syscall_map |
LINUX_2.6.15 |
__kernel_get_tbfreq |
LINUX_2.6.15 |
__kernel_getcpu *
|
LINUX_2.6.15 |
__kernel_gettimeofday |
LINUX_2.6.15 |
__kernel_sigtramp_rt32 |
LINUX_2.6.15 |
__kernel_sigtramp32 |
LINUX_2.6.15 |
__kernel_sync_dicache |
LINUX_2.6.15 |
__kernel_sync_dicache_p5 |
LINUX_2.6.15 |
Before Linux 5.6, the
CLOCK_REALTIME_COARSE and
CLOCK_MONOTONIC_COARSE clocks are
not supported by the
__kernel_clock_getres and
__kernel_clock_gettime interfaces; the
kernel falls back to the real system call.
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_clock_getres |
LINUX_2.6.15 |
__kernel_clock_gettime |
LINUX_2.6.15 |
__kernel_datapage_offset |
LINUX_2.6.15 |
__kernel_get_syscall_map |
LINUX_2.6.15 |
__kernel_get_tbfreq |
LINUX_2.6.15 |
__kernel_getcpu |
LINUX_2.6.15 |
__kernel_gettimeofday |
LINUX_2.6.15 |
__kernel_sigtramp_rt64 |
LINUX_2.6.15 |
__kernel_sync_dicache |
LINUX_2.6.15 |
__kernel_sync_dicache_p5 |
LINUX_2.6.15 |
Before Linux 4.16, the
CLOCK_REALTIME_COARSE and
CLOCK_MONOTONIC_COARSE clocks are
not supported by the
__kernel_clock_getres and
__kernel_clock_gettime interfaces; the
kernel falls back to the real system call.
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__vdso_rt_sigreturn |
LINUX_4.15 |
__vdso_gettimeofday |
LINUX_4.15 |
__vdso_clock_gettime |
LINUX_4.15 |
__vdso_clock_getres |
LINUX_4.15 |
__vdso_getcpu |
LINUX_4.15 |
__vdso_flush_icache |
LINUX_4.15 |
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_clock_getres |
LINUX_2.6.29 |
__kernel_clock_gettime |
LINUX_2.6.29 |
__kernel_gettimeofday |
LINUX_2.6.29 |
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_clock_getres |
LINUX_2.6.29 |
__kernel_clock_gettime |
LINUX_2.6.29 |
__kernel_gettimeofday |
LINUX_2.6.29 |
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_rt_sigreturn |
LINUX_2.6 |
__kernel_sigreturn |
LINUX_2.6 |
__kernel_vsyscall |
LINUX_2.6 |
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__kernel_sigreturn |
LINUX_2.5 |
__kernel_rt_sigreturn |
LINUX_2.5 |
__kernel_vsyscall |
LINUX_2.5 |
. |
|
. |
|
__vdso_clock_gettime |
LINUX_2.6 (exportation depuis Linux 3.15) |
__vdso_gettimeofday |
LINUX_2.6 (exportation depuis Linux 3.15) |
__vdso_time |
LINUX_2.6 (exportation depuis Linux 3.15) |
Le tableau suivant indique les symboles exportés par le vDSO. Tous ces
symboles sont aussi disponibles sans le préfixe
« __vdso_ », mais vous devriez les ignorer et vous
cantonner aux noms suivants.
symbole |
version |
|
__vdso_clock_gettime |
LINUX_2.6 |
__vdso_getcpu |
LINUX_2.6 |
__vdso_gettimeofday |
LINUX_2.6 |
__vdso_time |
LINUX_2.6 |
Le tableau suivant indique les symboles exportés par le vDSO.
symbole |
version |
|
__vdso_clock_gettime |
LINUX_2.6 |
__vdso_getcpu |
LINUX_2.6 |
__vdso_gettimeofday |
LINUX_2.6 |
__vdso_time |
LINUX_2.6 |
The vDSO was originally just a single function—the vsyscall. In older
kernels, you might see that name in a process's memory map rather than
"vdso". Over time, people realized that this mechanism was a great
way to pass more functionality to user space, so it was reconceived as a vDSO
in the current format.
syscalls(2),
getauxval(3),
proc(5)
Les documents, exemples et le code source dans l’arborescence du code
source de Linux :
Documentation/ABI/stable/vdso
Documentation/ia64/fsys.rst
Documentation/vDSO/* (includes examples of using the vDSO)
find arch/ -iname '*vdso*' -o -iname '*gate*'
La traduction française de cette page de manuel a été
créée par Christophe Blaess
<
https://www.blaess.fr/christophe/>, Stéphan Rafin
<
[email protected]>, Thierry Vignaud
<
[email protected]>, François Micaux, Alain Portal
<
[email protected]>, Jean-Philippe Guérard
<
[email protected]>, Jean-Luc Coulon (f5ibh)
<
[email protected]>, Julien Cristau
<
[email protected]>, Thomas Huriaux <
[email protected]>,
Nicolas François <
[email protected]>, Florentin
Duneau <
[email protected]>, Simon Paillard
<
[email protected]>, Denis Barbier
<
[email protected]>, David Prévot <
[email protected]> et
Jean-Paul Guillonneau <
[email protected]>
Cette traduction est une documentation libre ; veuillez vous reporter
à la
GNU
General Public License version 3 concernant les conditions de copie
et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page de manuel,
veuillez envoyer un message à
[email protected]