GNU/Linux >> Znalost Linux >  >> Linux

Proč mapování MAP_GROWSDOWN neroste?

Vím, že OP již přijal jednu z odpovědí, ale bohužel nevysvětluje proč MAP_GROWSDOWN zdá se, že někdy funguje. Protože tato otázka Stack Overflow je jedním z prvních hitů ve vyhledávačích, dovolte mi přidat svou odpověď pro ostatní.

Dokumentace MAP_GROWSDOWN potřebuje aktualizaci. Konkrétně:

Tento růst lze opakovat, dokud mapování nepřeroste na stránku horního konce následujícího nižšího mapování, v tomto okamžiku dotyk na „strážní“ stránce povede k signálu SIGSEGV.

Ve skutečnosti jádro neumožňuje MAP_GROWSDOWN mapování, aby se přiblížilo k stack_guard_gap stránky mimo předchozí mapování. Výchozí hodnota je 256, ale lze ji přepsat na příkazovém řádku jádra. Protože váš kód neurčuje žádnou požadovanou adresu pro mapování, jádro si ji vybere automaticky, ale je docela pravděpodobné, že skončí do 256 stránek od konce existujícího mapování.

UPRAVIT :

Kromě toho jádra před verzí 5.0 odmítají přístup k adrese, která je více než 64 k+256 bajtů pod ukazatelem zásobníku. Podrobnosti naleznete v tomto potvrzení jádra.

Tento program funguje na x86 i s jádry staršími než 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;
}

Nahradit:

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

S

volatile char *c_ptr_1 = mapped_ptr;

Protože:

Vrácená adresa je o jednu stránku nižší než paměťová oblast, která je skutečně vytvořena ve virtuálním adresním prostoru procesu. Klepnutím na adresu na stránce „strážce“ pod mapováním způsobíte, že se mapování rozroste o stránku.

Všimněte si, že jsem toto řešení testoval a funguje podle očekávání na jádře 4.15.0-45-generic.


Za prvé, nechcete MAP_GROWSDOWN , a takto nefunguje zásobník hlavního vlákna. Analýza paměťového mapování procesu pomocí pmap. [stack] Nic to nepoužívá a v podstatě nic by nemělo použij to. Věci v manuálové stránce, které říkají, že je "použito pro zásobníky", jsou špatné a měly by být opraveny.

Mám podezření, že by to mohlo být zabugované (protože ho nic nepoužívá, takže to obvykle nikoho nezajímá a ani si toho nevšimne, pokud se rozbije.)

Váš kód mi funguje, pokud změním mmap voláním zmapujte více než 1 stránku. Konkrétně jsem zkoušel 4096 * 100 . Používám Linux 5.0.1 (Arch Linux) na holém kovu (Skylake).

/proc/PID/smaps zobrazuje gd vlajka.

A pak (při jednokrokovém asm) maps záznam se ve skutečnosti změní na nižší počáteční adresu, ale stejnou koncovou adresu, takže doslova roste směrem dolů, když začnu s mapováním 400k. To dává počáteční alokaci 400 000 výše návratová adresa, která se při spuštění programu zvětší na 404 kB. (Velikost pro _GROWSDOWN mapování není limit růstu nebo něco podobného.)

https://bugs.centos.org/view.php?id=4767 může souviset; něco se změnilo mezi verzemi jádra v CentOS 5.3 a 5.5. A/nebo to mělo něco společného s prací ve virtuálním počítači (5.3) vs. nerostoucí a chybující na holém kovu (5.5).

Zjednodušil jsem C na použití ptr[-4095] atd.:

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
}

Kompilace s gcc -Og dává asm, který je pěkný až jednokrokový.

BTW, různé fámy o tom, že příznak byl z glibc odstraněn, jsou samozřejmě mylné. Tento zdroj se zkompiluje a je jasné, že je také podporován jádrem a není tiše ignorován. (Přestože chování, které vidím u velikosti 4096 místo 400 kB, je přesně v souladu s tím, že příznak je tiše ignorován. Nicméně gd VmFlag je stále v smaps , takže to v této fázi není ignorováno.)

Zkontroloval jsem to a byl tam prostor pro růst, aniž by se přiblížil jinému mapování. Takže IDK, proč to nerostlo, když GD mapování mělo pouze 1 stránku. Zkoušel jsem to párkrát a pokaždé to segfaulted. S větším počátečním mapováním to nikdy nevadilo.

Oba časy byly s úložištěm na návratovou hodnotu mmap (první stránka vlastního mapování), poté s úložištěm o 4095 bajtů pod ním.


Linux
  1. Proč regulární výraz funguje v X, ale ne v Y?

  2. Linux – Proč Setuid nefunguje?

  3. Linux – Proč Locale Es_mx funguje, ale Es ne?

  1. Proč „ukončit &“ nefunguje?

  2. Linux – Proč Rsync na Linuxu nezachovává všechna časová razítka (čas vytvoření)?

  3. Proč Tomcat pracuje s portem 8080, ale ne s 80?

  1. Proč systém Windows nerozpozná soubory uvnitř oddílů Linux?

  2. Proč má pvremove v manuálové stránce duplicitní sílu?

  3. Proč neblokovat ICMP?