web-dev-qa-db-fra.com

Comment accéder aux adresses physiques depuis l'espace utilisateur sous Linux?

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?

28
lang2

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;
}
36
Dmytro Sirenko

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:

  • désactiver CONFIG_STRICT_DEVMEM (défini par défaut sur Ubuntu 17.04)
  • passez l'option de ligne de commande nopat kernel pour x86

Les ports d'E/S fonctionnent toujours sans ceux-ci.

Voir aussi: le mmap de/dev/mem échoue avec un argument invalide pour l'adresse virt_to_phys, mais l'adresse est alignée sur la page

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:

  • mémoire Userland
    • allouer la variable volatile sur un processus utilisateur
    • obtenir l'adresse physique avec /proc/<pid>/maps + /proc/<pid>/pagemap
    • modifiez la valeur à l'adresse physique avec devmem, et regardez le processus userland réagir
  • mémoire Kernelland
    • allouer la mémoire du noyau avec kmalloc
    • obtenir l'adresse physique avec virt_to_phys et le renvoyer au userland
    • modifier l'adresse physique avec devmem
    • interroger la valeur du module du noyau
  • IO mem et périphérique de plateforme virtuelle QEM
    • créer un périphérique de plate-forme avec des adresses de registre physique connues
    • utilisez devmem pour écrire dans le registre
    • regarder printfs sortir du périphérique virtuel en réponse