GNU/Linux >> Tutoriels Linux >  >> Linux

Pourquoi le mappage MAP_GROWSDOWN n'augmente-t-il pas ?

Je sais que l'OP a déjà accepté l'une des réponses, mais malheureusement, cela n'explique pas pourquoi MAP_GROWSDOWN semble fonctionner parfois. Étant donné que cette question Stack Overflow est l'un des premiers résultats dans les moteurs de recherche, permettez-moi d'ajouter ma réponse pour les autres.

La documentation de MAP_GROWSDOWN a besoin d'être mis à jour. En particulier :

Cette croissance peut être répétée jusqu'à ce que le mappage atteigne une page de l'extrémité supérieure du mappage inférieur suivant, auquel cas toucher la page "garde" entraînera un signal SIGSEGV.

En réalité, le noyau n'autorise pas un MAP_GROWSDOWN mappage pour se rapprocher de stack_guard_gap pages éloignées du mappage précédent. La valeur par défaut est 256, mais elle peut être remplacée sur la ligne de commande du noyau. Étant donné que votre code ne spécifie aucune adresse souhaitée pour le mappage, le noyau en choisit une automatiquement, mais il est fort probable qu'il se retrouve dans les 256 pages suivant la fin d'un mappage existant.

MODIFIER :

De plus, les noyaux antérieurs à la version 5.0 refusent l'accès à une adresse située à plus de 64 ko + 256 octets sous le pointeur de pile. Voir ce commit du noyau pour plus de détails.

Ce programme fonctionne sur x86 même avec les noyaux antérieurs à la version 5.0 :

#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>

#define PAGE_SIZE   4096UL
#define GAP     512 * PAGE_SIZE

static void print_maps(void)
{
    FILE *f = fopen("/proc/self/maps", "r");
    if (f) {
        char buf[1024];
        size_t sz;
        while ( (sz = fread(buf, 1, sizeof buf, f)) > 0)
            fwrite(buf, 1, sz, stdout);
        fclose(f);
    }
}

int main()
{
    char *p;
    void *stack_ptr;

    /* Choose an address well below the default process stack. */
    asm volatile ("mov  %%rsp,%[sp]"
        : [sp] "=g" (stack_ptr));
    stack_ptr -= (intptr_t)stack_ptr & (PAGE_SIZE - 1);
    stack_ptr -= GAP;
    printf("Ask for a page at %p\n", stack_ptr);
    p = mmap(stack_ptr, PAGE_SIZE, PROT_READ | PROT_WRITE,
         MAP_PRIVATE | MAP_STACK | MAP_ANONYMOUS | MAP_GROWSDOWN,
         -1, 0);
    printf("Mapped at %p\n", p);
    print_maps();
    getchar();

    /* One page is already mapped: stack pointer does not matter. */
    *p = 'A';
    printf("Set content of that page to \"%s\"\n", p);
    print_maps();
    getchar();

    /* Expand down by one page. */
    asm volatile (
        "mov  %%rsp,%[sp]"  "\n\t"
        "mov  %[ptr],%%rsp" "\n\t"
        "movb $'B',-1(%%rsp)"   "\n\t"
        "mov  %[sp],%%rsp"
        : [sp] "+&g" (stack_ptr)
        : [ptr] "g" (p)
        : "memory");
    printf("Set end of guard page to \"%s\"\n", p - 1);
    print_maps();
    getchar();

    return 0;
}

Remplacer :

volatile char *c_ptr_1 = mapped_ptr - 4096; //1 page below

Avec

volatile char *c_ptr_1 = mapped_ptr;

Parce que :

L'adresse de retour est inférieure d'une page à la zone mémoire réellement créée dans l'espace d'adressage virtuel du processus. Toucher une adresse dans la page "garde" sous le mappage fera grandir le mappage d'une page.

Notez que j'ai testé la solution et qu'elle fonctionne comme prévu sur le noyau 4.15.0-45-generic.


Tout d'abord, vous ne voulez pas MAP_GROWSDOWN , et ce n'est pas ainsi que fonctionne la pile de threads principale. Analyser le mappage mémoire d'un processus avec pmap. [stack] Rien ne l'utilise, et à peu près rien ne devrait utilise le. Les éléments de la page de manuel indiquant qu'il est "utilisé pour les piles" sont faux et doivent être corrigés.

Je soupçonne qu'il pourrait être bogué (parce que rien ne l'utilise donc généralement personne ne s'en soucie ou même ne le remarque s'il se casse.)

Votre code fonctionne pour moi si je change le mmap appeler pour mapper plus d'une page. Plus précisément, j'ai essayé 4096 * 100 . J'utilise Linux 5.0.1 (Arch Linux) sur métal nu (Skylake).

/proc/PID/smaps affiche un gd drapeau.

Et puis (lorsque l'asm pas à pas) le maps l'entrée change en fait vers une adresse de début inférieure mais la même adresse de fin, donc elle augmente littéralement vers le bas lorsque je commence avec un mappage de 400k. Cela donne une allocation initiale de 400 000 ci-dessus l'adresse de retour, qui atteint 404 ko lorsque le programme s'exécute. (La taille pour un _GROWSDOWN le mappage n'est pas la limite de croissance ou quelque chose comme ça.)

https://bugs.centos.org/view.php?id=4767 peut être lié ; quelque chose a changé entre les versions du noyau dans CentOS 5.3 et 5.5. Et/ou cela avait quelque chose à voir avec le fait de travailler dans une machine virtuelle (5.3) plutôt que de ne pas grandir et de tomber en panne sur du métal nu (5.5).

J'ai simplifié le C pour utiliser ptr[-4095] etc :

int main(void){
    volatile char *ptr = mmap(NULL, 4096*100,
                            PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
                            -1, 0);
    if(ptr == MAP_FAILED){
        int error_code = errno;
        fprintf(stderr, "Cannot do MAP_FIXED mapping."
                        "Error code = %d, details = %s\n", error_code, strerror(error_code));
                        exit(EXIT_FAILURE);
    }

    ptr[0] = 'a';      //address returned by mmap
    ptr[-4095] = 'b';  // grow by 1 page
}

Compiler avec gcc -Og donne un asm agréable à une seule étape.

BTW, diverses rumeurs selon lesquelles le drapeau aurait été supprimé de la glibc sont évidemment fausses. Cette source compile, et il est clair qu'elle est également prise en charge par le noyau, et non ignorée en silence. (Bien que le comportement que je vois avec la taille 4096 au lieu de 400 ko soit exactement cohérent avec le fait que l'indicateur est ignoré en silence. Cependant, le gd VmFlag est toujours là dans smaps , il n'est donc pas ignoré à ce stade.)

J'ai vérifié et il y avait de la place pour qu'il grandisse sans se rapprocher d'un autre mappage. Alors IDK pourquoi il n'a pas grandi alors que le mappage GD n'était que d'une page. J'ai essayé plusieurs fois et il y a eu erreur de segmentation à chaque fois. Avec le mappage initial plus grand, il n'y a jamais eu de faute.

Les deux fois étaient avec un magasin à la valeur de retour mmap (la première page du mappage proprement dit), puis un magasin 4095 octets en dessous.


Linux
  1. Pourquoi le CD n'est-il pas un programme ?

  2. Linux - Pourquoi Setuid ne fonctionne-t-il pas ??

  3. Linux – Pourquoi la locale Es_mx fonctionne-t-elle mais pas Es ?

  4. Pourquoi find -exec mv {} ./target/ + ne fonctionne-t-il pas ?

  5. Pourquoi la modification de javascript dans les outils de développement Chrome ne fonctionne-t-elle pas ?

Pourquoi Tomcat fonctionne-t-il avec le port 8080 mais pas 80 ?

Pourquoi dit-il Nous ne devons pas inclure de limites.h ! dans dire.h?

Pourquoi Windows ne reconnaît-il pas les fichiers à l'intérieur des partitions Linux ?

Pourquoi wget'ing une image me donne-t-il un fichier, pas une image ?

Pourquoi cette regex ne fonctionne-t-elle pas sous Linux ?

Pourquoi pvremove a-t-il une force en double dans la page de manuel ?