GNU/Linux >> Belajar Linux >  >> Linux

Mengapa pemetaan MAP_GROWSDOWN tidak tumbuh?

Saya tahu OP telah menerima salah satu jawaban, tapi sayangnya tidak menjelaskan mengapa MAP_GROWSDOWN tampaknya bekerja kadang-kadang. Karena pertanyaan Stack Overflow ini adalah salah satu hit pertama di mesin telusur, izinkan saya menambahkan jawaban saya untuk orang lain.

Dokumentasi MAP_GROWSDOWN perlu diperbarui. Khususnya:

Peningkatan ini dapat diulangi hingga pemetaan tumbuh dalam satu halaman dari ujung atas pemetaan bawah berikutnya, di mana menyentuh halaman "pelindung" akan menghasilkan sinyal SIGSEGV.

Pada kenyataannya, kernel tidak mengizinkan MAP_GROWSDOWN pemetaan untuk tumbuh lebih dekat dari stack_guard_gap halaman dari pemetaan sebelumnya. Nilai defaultnya adalah 256, tetapi dapat diganti pada baris perintah kernel. Karena kode Anda tidak menentukan alamat yang diinginkan untuk pemetaan, kernel akan memilih satu alamat secara otomatis, tetapi kemungkinan besar berakhir dalam 256 halaman dari akhir pemetaan yang ada.

EDIT :

Selain itu, kernel sebelum v5.0 menolak akses ke alamat yang lebih dari 64k+256 byte di bawah penunjuk tumpukan. Lihat komit kernel ini untuk detailnya.

Program ini bekerja pada x86 bahkan dengan kernel pra-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;
}

Ganti:

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

Dengan

volatile char *c_ptr_1 = mapped_ptr;

Karena:

Alamat pengirim satu halaman lebih rendah dari area memori yang sebenarnya dibuat di ruang alamat virtual proses. Menyentuh alamat di halaman "penjaga" di bawah pemetaan akan menyebabkan pemetaan bertambah satu halaman.

Perhatikan bahwa saya menguji solusinya dan berfungsi seperti yang diharapkan pada kernel 4.15.0-45-generic.


Pertama-tama, Anda tidak ingin MAP_GROWSDOWN , dan ini bukan cara kerja tumpukan utas utama. Menganalisis pemetaan memori dari suatu proses dengan pmap. [tumpukan] Tidak ada yang menggunakannya, dan hampir tidak ada yang harus Gunakan. Hal-hal di halaman manual yang mengatakan "digunakan untuk tumpukan" salah dan harus diperbaiki.

Saya menduga itu mungkin bermasalah (karena tidak ada yang menggunakannya sehingga biasanya tidak ada yang peduli atau bahkan menyadarinya jika rusak.)

Kode Anda berfungsi untuk saya jika saya mengubah mmap panggilan untuk memetakan lebih dari 1 halaman. Secara khusus, saya mencoba 4096 * 100 . Saya menjalankan Linux 5.0.1 (Arch Linux) di bare metal (Skylake).

/proc/PID/smaps memang menunjukkan gd bendera.

Dan kemudian (ketika melangkah asm) maps entri benar-benar berubah ke alamat awal yang lebih rendah tetapi alamat akhir yang sama, jadi secara harfiah tumbuh ke bawah ketika saya mulai dengan pemetaan 400k. Ini memberikan alokasi awal 400rb di atas alamat pengirim, yang bertambah menjadi 404kiB saat program berjalan. (Ukuran untuk _GROWSDOWN pemetaan tidak batas pertumbuhan atau semacamnya.)

https://bugs.centos.org/view.php?id=4767 mungkin terkait; sesuatu berubah antara versi kernel di CentOS 5.3 dan 5.5. Dan/atau ada hubungannya dengan bekerja di VM (5.3) vs. tidak tumbuh dan rusak pada bare metal (5.5).

Saya menyederhanakan C untuk menggunakan ptr[-4095] dll:

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
}

Kompilasi dengan gcc -Og memberikan asm yang bagus untuk satu langkah.

BTW, berbagai rumor tentang bendera yang telah dihapus dari glibc jelas salah. Sumber ini mengkompilasi, dan jelas bahwa itu juga didukung oleh kernel, tidak diabaikan secara diam-diam. (Meskipun perilaku yang saya lihat dengan ukuran 4096 bukannya 400kiB persis konsisten dengan bendera yang diabaikan secara diam-diam. Namun gd VmFlag masih ada di smaps , sehingga tidak diabaikan pada tahap itu.)

Saya memeriksa dan ada ruang untuk tumbuh tanpa mendekati pemetaan lain. Jadi IDK kenapa tidak berkembang padahal pemetaan GD hanya 1 halaman. Saya mencoba beberapa kali dan selalu tersegfault. Dengan pemetaan awal yang lebih besar, hal itu tidak pernah salah.

Kedua kali dengan menyimpan ke mmap mengembalikan nilai (halaman pertama dari pemetaan yang tepat), kemudian menyimpan 4095 byte di bawahnya.


Linux
  1. Mengapa Cd Bukan Program?

  2. Mengapa `md5sum` Tidak Memberikan Hash yang Sama Seperti Internet?

  3. Mengapa `keluar &` Tidak Berfungsi?

  1. Mengapa Tomcat bekerja dengan port 8080 tetapi tidak dengan 80?

  2. Mengapa dikatakan Kita tidak boleh memasukkan batasan.h! di dirent.h?

  3. Mengapa Windows tidak mengenali file di dalam partisi Linux?

  1. Mengapa yum updateinfo tidak menampilkan semua pembaruan?

  2. Mengapa regex ini tidak berfungsi di linux?

  3. Mengapa pvremove memiliki kekuatan duplikat di halaman manual?