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.