GNU/Linux >> Tutoriels Linux >  >> Linux

Comment implémenter malloc déterministe

Je me demande ce que vous essayez d'accomplir. Si votre processus est déterministe, le schéma d'allocation/désallocation doit être le même.

La seule différence possible pourrait être l'adresse retournée par malloc . Mais vous ne devriez probablement pas en dépendre (le moyen le plus simple étant de ne pas utiliser de pointeurs comme carte clé ou autre structure de données). Et même dans ce cas, il ne devrait y avoir de différence que si l'allocation ne se fait pas via sbrk (la glibc utilise mmap anonyme pour les grandes allocations), ou si vous utilisez mmap (car par défaut l'adresse est sélectionnée par le noyau).

Si vous voulez vraiment avoir exactement la même adresse, une option consiste à avoir un grand tampon statique et à écrire un répartiteur personnalisé qui utilise la mémoire de ce tampon. Cela a l'inconvénient de vous obliger à connaître à l'avance la quantité maximale de mémoire dont vous aurez besoin. Dans un exécutable non-PIE (gcc -fno-pie -no-pie ), un tampon statique aura la même adresse à chaque fois. Pour un exécutable PIE, vous pouvez désactiver la randomisation de la disposition de l'espace d'adressage du noyau pour le chargement des programmes. Dans une bibliothèque partagée, la désactivation de l'ASLR et l'exécution du même programme deux fois devraient conduire aux mêmes choix par l'éditeur de liens dynamique pour savoir où mapper les bibliothèques.

Si vous ne connaissez pas à l'avance la taille maximale de la mémoire que vous souhaitez utiliser, ou si vous ne souhaitez pas recompiler à chaque fois que cette taille augmente, vous pouvez également utiliser mmap pour mapper un grand tampon anonyme à une adresse fixe. Passez simplement la taille du tampon et l'adresse à utiliser comme paramètre à votre processus et utilisez la mémoire renvoyée pour implémenter votre propre malloc dessus.

static void* malloc_buffer = NULL;
static size_t malloc_buffer_len = 0;

void* malloc(size_t size) {
    // Use malloc_buffer & malloc_buffer_len to implement your
    // own allocator. If you don't read uninitialized memory,
    // it can be deterministic.
    return memory;
}

int main(int argc, char** argv) {
    size_t buf_size = 0;
    uintptr_t buf_addr = 0;
    for (int i = 0; i < argv; ++i) {
        if (strcmp(argv[i], "--malloc-size") == 0) {
            buf_size = atoi(argv[++i]);
        }
        if (strcmp(argv[i], "--malloc-addr") == 0) {
            buf_addr = atoi(argv[++i]);
        }
    }

    malloc_buffer = mmap((void*)buf_addr, buf_size, PROT_WRITE|PROT_READ,
                         MAP_FIXED|MAP_PRIVATE, 0, 0);
    // editor's note: omit MAP_FIXED since you're checking the result anyway
    if (malloc_buffer == MAP_FAILED || malloc_buffer != (void*)but_addr) {
        // Could not get requested memory block, fail.
        exit(1);
    }

    malloc_size = buf_size;
}

En utilisant MAP_FIXED , nous disons au noyau de remplacer tous les mappages existants qui se chevauchent avec ce nouveau à buf_addr .

(NDLR :MAP_FIXED n'est probablement pas ce que vous voulez . Spécifier buf_addr comme indice au lieu de NULL demande déjà cette adresse si possible. Avec MAP_FIXED , mmap renverra soit une erreur, soit l'adresse que vous lui avez donnée. Le malloc_buffer != (void*)but_addr check a du sens pour les non-FIXED cas, qui ne remplacera pas un mappage existant de votre code ou une bibliothèque partagée ou quoi que ce soit d'autre. Linux 4.17 introduit MAP_FIXED_NOREPLACE que vous pouvez utiliser pour que mmap renvoie une erreur au lieu de la mémoire à la mauvaise adresse que vous ne voulez pas utiliser. Mais laissez toujours l'enregistrement pour que votre code fonctionne sur les noyaux plus anciens.)

Si vous utilisez ce bloc pour implémenter votre propre malloc et n'utilisez pas d'autre opération non déterministe dans votre code, vous pouvez avoir un contrôle total sur les valeurs du pointeur.

Cela suppose que votre modèle d'utilisation de malloc / free est déterministe. Et que vous n'utilisez pas de bibliothèques non déterministes.

Cependant, je pense qu'une solution plus simple consiste à garder vos algorithmes déterministes et à ne pas dépendre des adresses. C'est possible. J'ai travaillé sur un projet à grande échelle où plusieurs ordinateurs devaient mettre à jour l'état de manière déterministe (afin que chaque programme ait le même état, tout en ne transmettant que des entrées). Si vous n'utilisez pas de pointeur pour autre chose que le référencement d'objets (le plus important est de ne jamais utiliser la valeur du pointeur pour quoi que ce soit, pas comme un hachage, pas comme une clé dans une carte, ...), alors votre état restera déterministe .

À moins que ce que vous vouliez faire soit de pouvoir prendre un instantané de toute la mémoire du processus et de faire un diff binaire pour repérer la divergence. Je pense que c'est une mauvaise idée, car comment saurez-vous que les deux ont atteint le même point dans leur calcul ? Il est beaucoup plus facile de comparer la sortie, ou de faire en sorte que le processus puisse calculer un hachage de l'état et l'utiliser pour vérifier qu'ils sont synchronisés car vous pouvez contrôler quand cela est fait (et donc cela devient également déterministe, sinon votre mesure est non déterministe).


Ce qui n'est pas déterministe n'est pas seulement malloc mais mmap (l'appel système de base pour obtenir plus d'espace mémoire ; ce n'est pas une fonction, c'est un appel système, il est donc élémentaire ou atomique du point de vue de l'application ; vous ne pouvez donc pas le réécrire dans l'application) à cause de la randomisation de la disposition de l'espace d'adressage sous Linux.

Vous pouvez le désactiver avec

 echo 0 > /proc/sys/kernel/randomize_va_space

en tant que root, ou via sysctl.

Si vous ne désactivez pas la randomisation de la disposition de l'espace d'adressage, vous êtes bloqué.

Et vous avez posé une question similaire précédemment, où j'ai expliqué que votre malloc -s ne sera pas toujours déterministe.

Je pense toujours que pour certaines applications pratiques, malloc ne peut pas être déterministe. Imaginez par exemple un programme ayant une table de hachage codée par le pid -s des processus enfants qu'il lance. La collision dans cette table ne sera pas la même dans tous vos processus, etc.

Donc je crois que vous ne réussirez pas à faire malloc déterministe dans votre sens, quoi que vous essayiez (à moins que vous ne vous restreigniez à une classe très étroite d'applications au point de contrôle, si étroite que votre logiciel ne sera pas très utile).


En termes simples, comme d'autres l'ont dit :si l'exécution des instructions de votre programme est déterministe, alors la mémoire est renvoyée par malloc() sera déterministe. Cela suppose que l'implémentation de votre système n'a pas d'appel à random() Ou quelque chose à cet effet. Si vous n'êtes pas sûr, lisez le code ou la documentation du malloc de votre système .

C'est à l'exception possible de l'ASLR, comme d'autres l'ont également déclaré. Si vous n'avez pas les privilèges root, vous pouvez le désactiver par processus via le personality(2) syscall et le paramètre ADDR_NO_RANDOMIZE. Voir ici pour plus d'informations sur les personnalités.

Edit :Je devrais également dire, si vous n'êtes pas au courant :ce que vous faites s'appelle bisimulation et est une technique bien étudiée. Si vous ne connaissiez pas la terminologie, il pourrait être utile d'avoir ce mot-clé pour la recherche.


Linux
  1. Comment lire une adresse IP à l'envers ?

  2. Comment se connecter à cPanel

  3. Comment refuser une adresse IP dans cPanel

  4. Comment ajouter une adresse IP à la liste blanche dans SmarterMail

  5. Comment acheter une adresse IP dédiée

Comment changer l'adresse MAC sous Linux

Comment changer l'adresse IP sous Linux

Comment obtenir votre adresse IP sous Linux

Comment trouver une adresse IP sous Linux

Guide pratique :Administration du réseau FreeBSD

Comment ajouter une nouvelle adresse IP dans Virtualmin ?