Sur un système basé sur ARM exécutant Linux, j'ai un périphérique dont la mémoire est mappée à une adresse physique. À partir d'un programme de l'espace utilisateur où toutes les adresses sont virtuelles, comment puis-je lire le contenu de cette adresse?
Vous pouvez mapper un fichier de périphérique à une mémoire de processus utilisateur à l'aide de l'appel système mmap(2)
. Habituellement, les fichiers de périphérique sont des mappages de mémoire physique au 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 façon consiste à remapper des parties de/dev/mem dans 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
Écrire 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 voudrez probablement utiliser:
open("/dev/mem", O_RDWR | O_SYNC);
mmap(..., PROT_READ | PROT_WRITE, MAP_SHARED, ...)
MAP_SHARED
permet d'écrire immédiatement dans la mémoire physique, ce qui facilite l'observation et rend plus logique les écritures de registre matériel.
CONFIG_STRICT_DEVMEM
et nopat
Utiliser /dev/mem
pour afficher et modifier le standard RAM sur le noyau v4.9, vous devez pointer:
CONFIG_STRICT_DEVMEM
(défini par défaut sur Ubuntu 17.04)nopat
kernel pour x86Les ports d'E/S fonctionnent toujours sans ceux-ci.
Vidage du cache
Si vous essayez d'écrire dans 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 rincer ou de marquer la région comme non cache:
Alors peut-être /dev/mem
ne peut pas être utilisé de manière fiable pour transmettre des tampons de mémoire aux périphériques?
Cela ne peut malheureusement pas être observé dans QEMU, car QEMU ne simule pas les caches.
Comment le tester
Maintenant pour la partie amusante. Voici quelques configurations intéressantes:
volatile
sur un processus utilisateur/proc/<pid>/maps
+ /proc/<pid>/pagemap
devmem
, et regardez le processus userland réagirkmalloc
virt_to_phys
et le renvoyer au userlanddevmem
devmem
pour écrire dans le registreprintf
s sortir du périphérique virtuel en réponse