Bagaimana cara kerja alokasi tumpukan di Linux?

18

Apakah OS menyimpan jumlah ruang virtual yang valid untuk stack atau sesuatu yang lain? Apakah saya dapat menghasilkan stack overflow hanya dengan menggunakan variabel lokal besar?

Saya telah menulis sebuah Cprogram kecil untuk menguji asumsi saya. Ini berjalan pada X86-64 CentOS 6.5.

#include <string.h>
#include <stdio.h>
int main()
{
    int n = 10240 * 1024;
    char a[n];
    memset(a, 'x', n);
    printf("%x\n%x\n", &a[0], &a[n-1]);
    getchar();
    return 0;
}

Menjalankan program memberi &a[0] = f0ceabe0dan&a[n-1] = f16eabdf

Peta proc menunjukkan tumpukan: 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

Kemudian saya mencoba meningkatkan n = 11240 * 1024

Menjalankan program memberi &a[0] = b6b36690dan&a[n-1] = b763068f

Peta proc menunjukkan tumpukan: 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -stercetak 10240di PC saya.

Seperti yang Anda lihat, dalam kedua kasus ukuran tumpukan lebih besar dari yang ulimit -sdiberikan. Dan tumpukan tumbuh dengan variabel lokal yang lebih besar. Bagian atas tumpukan entah bagaimana lebih dari 3-5kB &a[0](AFAIK zona merah adalah 128B).

Jadi bagaimana peta tumpukan ini dialokasikan?

Amos
sumber

Jawaban:

14

Tampaknya batas memori tumpukan tidak dialokasikan (toh, tidak bisa dengan tumpukan tidak terbatas). https://www.kernel.org/doc/Documentation/vm/overcommit-accounting mengatakan:

Pertumbuhan tumpukan bahasa C melakukan mremap implisit. Jika Anda ingin jaminan absolut dan berjalan mendekati tepi Anda HARUS mmap tumpukan Anda untuk ukuran terbesar yang Anda pikir Anda perlukan. Untuk penggunaan stack pada umumnya, ini tidak terlalu menjadi masalah, tetapi ini adalah kasus sudut jika Anda benar-benar peduli

Namun mmapping tumpukan akan menjadi tujuan dari kompiler (jika memiliki opsi untuk itu).

EDIT: Setelah beberapa tes pada mesin Debian x84_64, saya telah menemukan bahwa stack tumbuh tanpa panggilan sistem apa pun (sesuai dengan strace). Jadi, ini berarti bahwa kernel menumbuhkannya secara otomatis (inilah yang dimaksud "implisit" di atas), yaitu tanpa eksplisit mmap/ mremapdari proses.

Cukup sulit untuk menemukan informasi terperinci yang mengonfirmasi hal ini. Saya merekomendasikan Memahami Linux Virtual Memory Manager oleh Mel Gorman. Saya kira jawabannya ada di Bagian 4.6.1 Menangani Kesalahan Halaman , dengan pengecualian "Wilayah tidak valid tetapi di samping wilayah yang dapat diperluas seperti tumpukan" dan tindakan yang sesuai "Perluas wilayah dan alokasikan halaman". Lihat juga D.5.2 Memperluas Tumpukan .

Referensi lain tentang manajemen memori Linux (tetapi hampir tidak ada tentang stack):

EDIT 2: Implementasi ini memiliki kelemahan: dalam kasus sudut, tabrakan tumpukan-tumpukan mungkin tidak terdeteksi, bahkan dalam kasus di mana tumpukan akan lebih besar dari batas! Alasannya adalah bahwa penulisan dalam variabel dalam stack mungkin berakhir pada memori heap yang dialokasikan, dalam hal ini tidak ada kesalahan halaman dan kernel tidak dapat mengetahui bahwa stack perlu diperpanjang. Lihat contoh saya dalam diskusi Tabrakan tumpukan-tumpukan diam di bawah GNU / Linux yang saya mulai di daftar bantuan gcc. Untuk menghindarinya, kompiler perlu menambahkan beberapa kode saat panggilan fungsi; ini dapat dilakukan dengan -fstack-checkuntuk GCC (lihat balasan Ian Lance Taylor dan halaman manual GCC untuk detailnya).

vinc17
sumber
Itu sepertinya jawaban yang benar untuk pertanyaan saya. Tapi itu lebih membingungkan saya. Kapan panggilan mremap akan terpicu? Apakah ini syscall dibangun ke dalam program?
Amos
@ AMOS Saya berasumsi bahwa panggilan mremap akan dipicu jika perlu pada panggilan fungsi atau ketika dialokasikan () dipanggil.
vinc17
Mungkin akan lebih baik untuk menyebutkan apa itu mmap, untuk orang yang tidak tahu.
Faheem Mitha
@FaheemMitha Saya telah menambahkan beberapa informasi. Bagi mereka yang tidak tahu apa itu mmap, lihat FAQ memori yang disebutkan di atas. Di sini, untuk stack, itu akan menjadi "pemetaan anonim" sehingga ruang yang tidak digunakan tidak akan mengambil memori fisik, tetapi seperti yang dijelaskan oleh Mel Gorman, kernel melakukan pemetaan (memori virtual) dan alokasi fisik pada saat yang sama .
vinc17
1
@max Saya sudah mencoba program OP dengan ulimit -smemberikan 10.240, seperti di bawah kondisi OP, dan saya mendapatkan SIGSEGV seperti yang diharapkan (inilah yang diperlukan oleh POSIX: "Jika batas ini terlampaui, SIGSEGV akan dihasilkan untuk utas. "). Saya menduga ada bug di kernel OP.
vinc17
6

Kernel Linux 4.2

Program tes minimal

Kami kemudian dapat mengujinya dengan program NASM 64-bit minimal:

global _start
_start:
    sub rsp, 0x7FF000
    mov [rsp], rax
    mov rax, 60
    mov rdi, 0
    syscall

Pastikan Anda mematikan ASLR dan menghapus variabel lingkungan karena itu akan tersimpan di stack dan menghabiskan ruang:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
env -i ./main.out

Batasnya ada di suatu tempat sedikit di bawah saya ulimit -s(8MiB untuk saya). Sepertinya ini karena ekstra data yang ditentukan Sistem V pada awalnya diletakkan di tumpukan di samping lingkungan: Linux 64 parameter baris perintah di Majelis | Stack Overflow

Jika Anda serius tentang ini, TODO membuat gambar initrd minimal yang mulai menulis dari tumpukan atas dan turun, dan kemudian jalankan dengan QEMU + GDB . Letakkan dprintfdi loop mencetak alamat stack, dan breakpoint di acct_stack_growth. Itu akan mulia.

Terkait:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
sumber
2

Secara default, ukuran tumpukan maksimal dikonfigurasikan menjadi 8MB per proses,
tetapi dapat diubah menggunakan ulimit:

Menampilkan default di kB:

$ ulimit -s
8192

Setel ke tidak terbatas:

ulimit -s unlimited

mempengaruhi shell saat ini dan subkulit dan proses anak mereka.
( ulimitadalah perintah shell builtin)

Anda dapat menunjukkan kisaran alamat tumpukan aktual yang digunakan dengan:
cat /proc/$PID/maps | grep -F '[stack]'
di Linux.

Volker Siegel
sumber
Jadi ketika sebuah program dimuat oleh shell saat ini, OS akan membuat segmen memori ulimit -sKB berlaku untuk program tersebut. Dalam kasus saya ini 10240KB. Tetapi ketika saya mendeklarasikan array char a[10240*1024]dan set lokal a[0]=1, program keluar dengan benar. Mengapa?
Amos
Cobalah untuk mengatur elemen terakhir juga. Dan pastikan bahwa mereka tidak dioptimalkan.
vinc17
@amos Saya pikir apa artinya vinc17 adalah bahwa Anda menamai wilayah memori yang tidak muat pada tumpukan di program Anda , tetapi karena Anda tidak benar-benar mengaksesnya di bagian yang tidak cocok , mesin tidak pernah memperhatikan bahwa - tidak bahkan mendapatkan informasi itu .
Volker Siegel
@ am Coba int n = 10240*1024; char a[n]; memset(a,'x',n);... kesalahan seg.
goldilocks
2
@amos Jadi, seperti yang Anda lihat, a[]belum dialokasikan di tumpukan 10MB Anda. Kompiler mungkin telah melihat bahwa tidak mungkin ada panggilan rekursif dan telah melakukan alokasi khusus, atau sesuatu yang lain seperti tumpukan diskontinyu atau tipuan.
vinc17