GNU/Linux >> Linux Esercitazione >  >> Linux

Perché la mappatura MAP_GROWSDOWN non cresce?

So che l'OP ha già accettato una delle risposte, ma sfortunatamente non spiega perché MAP_GROWSDOWN sembra funzionare a volte. Poiché questa domanda Stack Overflow è uno dei primi risultati nei motori di ricerca, consentitemi di aggiungere la mia risposta per gli altri.

La documentazione di MAP_GROWSDOWN necessita di aggiornamento. In particolare:

Questa crescita può essere ripetuta finché la mappatura non cresce all'interno di una pagina dell'estremità superiore della successiva mappatura inferiore, a quel punto toccando la pagina di "guardia" si otterrà un segnale SIGSEGV.

In realtà, il kernel non consente un MAP_GROWSDOWN mapping per avvicinarsi a stack_guard_gap pagine di distanza dalla mappatura precedente. Il valore predefinito è 256, ma può essere sovrascritto dalla riga di comando del kernel. Poiché il tuo codice non specifica alcun indirizzo desiderato per la mappatura, il kernel ne sceglie uno automaticamente, ma è molto probabile che finisca entro 256 pagine dalla fine di una mappatura esistente.

MODIFICA :

Inoltre, i kernel precedenti alla v5.0 negano l'accesso a un indirizzo che è più di 64k+256 byte sotto il puntatore dello stack. Vedi questo commit del kernel per i dettagli.

Questo programma funziona su x86 anche con kernel precedenti alla 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;
}

Sostituisci:

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

Con

volatile char *c_ptr_1 = mapped_ptr;

Perché:

L'indirizzo di ritorno è inferiore di una pagina rispetto all'area di memoria effettivamente creata nello spazio degli indirizzi virtuali del processo. Toccando un indirizzo nella pagina "guardia" sotto la mappatura, la mappatura aumenterà di una pagina.

Tieni presente che ho testato la soluzione e funziona come previsto sul kernel 4.15.0-45-generic.


Prima di tutto, non vuoi MAP_GROWSDOWN , e non è così che funziona lo stack di thread principale. Analisi della mappatura della memoria di un processo con pmap. [stack] Niente lo usa e praticamente niente dovrebbe usalo. La roba nella pagina man che dice che è "usata per gli stack" è sbagliata e dovrebbe essere corretta.

Sospetto che possa essere difettoso (perché non lo usa nessuno, quindi di solito a nessuno importa o si accorge se si rompe.)

Il tuo codice funziona per me se cambio il mmap chiama per mappare più di 1 pagina. Nello specifico, ho provato 4096 * 100 . Utilizzo Linux 5.0.1 (Arch Linux) su bare metal (Skylake).

/proc/PID/smaps mostra un gd bandiera.

E poi (quando si fa un passo singolo dell'asm) il maps entry in realtà cambia in un indirizzo iniziale più basso ma con lo stesso indirizzo finale, quindi sta letteralmente crescendo verso il basso quando inizio con una mappatura di 400k. Ciò fornisce un'allocazione iniziale di 400.000 sopra l'indirizzo di ritorno, che cresce fino a 404 kiB durante l'esecuzione del programma. (La dimensione per un _GROWSDOWN la mappatura non il limite di crescita o qualcosa di simile.)

https://bugs.centos.org/view.php?id=4767 potrebbe essere correlato; qualcosa è cambiato tra le versioni del kernel in CentOS 5.3 e 5.5. E/o aveva qualcosa a che fare con il lavorare in una VM (5.3) rispetto alla mancata crescita e al guasto su bare metal (5.5).

Ho semplificato la C per usare ptr[-4095] ecc:

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
}

Compilazione con gcc -Og dà asm che è carino a passo singolo.

A proposito, varie voci sulla rimozione della bandiera da glibc sono ovviamente sbagliate. Questa fonte si compila ed è chiaro che è supportata anche dal kernel, non ignorato silenziosamente. (Anche se il comportamento che vedo con la dimensione 4096 invece di 400 kiB è esattamente coerente con il flag che viene silenziosamente ignorato. Tuttavia il gd VmFlag è ancora presente in smaps , quindi non viene ignorato in quella fase.)

Ho controllato e c'era spazio per farlo crescere senza avvicinarsi a un'altra mappatura. Quindi IDK perché non è cresciuto quando la mappatura GD era solo di 1 pagina. Ho provato un paio di volte e ogni volta si è verificato un segfault. Con la mappatura iniziale più ampia non ha mai sbagliato.

Entrambe le volte erano con uno store al valore di ritorno mmap (la prima pagina della mappatura vera e propria), quindi uno store 4095 byte al di sotto di quello.


Linux
  1. Perché l'espressione regolare funziona in X ma non in Y?

  2. La mappatura dei metadati con Avconv non funziona?

  3. Linux:perché Rsync su Linux non conserva tutti i timestamp (ora di creazione)?

  4. Perché Tomcat funziona con la porta 8080 ma non con la 80?

  5. Perché Windows non riconosce i file all'interno delle partizioni Linux?

Perché dice Non dobbiamo includere limiti.h! in dirent.h?

Perché la modifica di javascript negli Strumenti per sviluppatori di Chrome non funziona?

Perché 'dd' non funziona per la creazione di USB avviabile?

Perché wget'ing un'immagine mi dà un file, non un'immagine?

Perché questa espressione regolare non funziona su Linux?

Perché non bloccare l'ICMP?