Vous pouvez mapper un fichier de périphérique sur une mémoire de processus utilisateur à l'aide de mmap(2)
appel système. Habituellement, les fichiers de périphérique sont des mappages de mémoire physique sur le système de fichiers. Sinon, vous devez écrire un module de noyau qui crée un tel fichier ou fournit un moyen de mapper la mémoire nécessaire à un processus utilisateur.
Une autre méthode consiste à remapper des parties de /dev/mem vers une mémoire utilisateur.
Edit :Exemple de mmaping /dev/mem (ce programme doit avoir accès à /dev/mem, par exemple avoir les droits root) :
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s <phys_addr> <offset>\n", argv[0]);
return 0;
}
off_t offset = strtoul(argv[1], NULL, 0);
size_t len = strtoul(argv[2], NULL, 0);
// Truncate offset to a multiple of the page size, or mmap will fail.
size_t pagesize = sysconf(_SC_PAGE_SIZE);
off_t page_base = (offset / pagesize) * pagesize;
off_t page_offset = offset - page_base;
int fd = open("/dev/mem", O_SYNC);
unsigned char *mem = mmap(NULL, page_offset + len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, page_base);
if (mem == MAP_FAILED) {
perror("Can't map memory");
return -1;
}
size_t i;
for (i = 0; i < len; ++i)
printf("%02x ", (int)mem[page_offset + i]);
return 0;
}
busybox devmem
busybox devmem
est un petit utilitaire CLI qui mmaps /dev/mem
.
Vous pouvez l'obtenir dans Ubuntu avec :sudo apt-get install busybox
Utilisation :lire 4 octets à partir de l'adresse physique 0x12345678
:
sudo busybox devmem 0x12345678
Ecrire 0x9abcdef0
à cette adresse :
sudo busybox devmem 0x12345678 w 0x9abcdef0
Source :https://github.com/mirror/busybox/blob/1_27_2/miscutils/devmem.c#L85
mmap MAP_SHARED
Lors du mmapping /dev/mem
, vous souhaiterez probablement utiliser :
open("/dev/mem", O_RDWR | O_SYNC);
mmap(..., PROT_READ | PROT_WRITE, MAP_SHARED, ...)
MAP_SHARED
permet aux écritures d'aller immédiatement dans la mémoire physique, ce qui facilite l'observation et a plus de sens pour les écritures de registre matériel.
CONFIG_STRICT_DEVMEM
et nopat
Pour utiliser /dev/mem
pour afficher et modifier la RAM normale sur le noyau v4.9, vous devez d'abord :
- désactiver
CONFIG_STRICT_DEVMEM
(défini par défaut sur Ubuntu 17.04) - passer le
nopat
option de ligne de commande du noyau pour x86
Les ports IO fonctionnent toujours sans ceux-ci.
Voir aussi :mmap de /dev/mem échoue avec un argument non valide pour l'adresse virt_to_phys, mais l'adresse est alignée sur la page
Vidage du cache
Si vous essayez d'écrire dans la RAM au lieu d'un registre, la mémoire peut être mise en cache par le CPU :Comment vider le cache du CPU pour une région d'espace d'adressage sous Linux ? et je ne vois pas de moyen très portable/facile de le vider ou de marquer la région comme non cache :
- Comment écrire l'espace mémoire du noyau (adresse physique) dans un fichier en utilisant O_DIRECT ?
- Comment vider le cache du processeur pour une région d'espace d'adressage sous Linux ?
- Est-il possible d'allouer, dans l'espace utilisateur, un bloc de mémoire non cacheable sous Linux ?
Alors peut-être /dev/mem
ne peut pas être utilisé de manière fiable pour transmettre des mémoires tampons aux appareils ?
Cela ne peut malheureusement pas être observé dans QEMU, puisque QEMU ne simule pas les caches.
Comment le tester
Maintenant, pour la partie amusante. Voici quelques configurations intéressantes :
- Mémoire Userland
- allouer
volatile
variable sur un processus userland - obtenir l'adresse physique avec
/proc/<pid>/maps
+/proc/<pid>/pagemap
- modifier la valeur à l'adresse physique avec
devmem
, et regardez le processus userland réagir
- allouer
- Mémoire de Kernelland
- allouer de la mémoire noyau avec
kmalloc
- obtenir l'adresse physique avec
virt_to_phys
et renvoyez-le à userland - modifier l'adresse physique avec
devmem
- interroger la valeur du module du noyau
- allouer de la mémoire noyau avec
- Périphérique de plate-forme virtuelle IO mem et QEMU
- créer un appareil de plate-forme avec des adresses de registre physiques connues
- utiliser
devmem
écrire au registre - regarder
printf
s sortent du périphérique virtuel en réponse
Bonus :déterminer l'adresse physique pour une adresse virtuelle
Existe-t-il une API pour déterminer l'adresse physique à partir de l'adresse virtuelle sous Linux ?