การจัดสรรสแต็คทำงานใน Linux อย่างไร


18

ระบบปฏิบัติการสำรองพื้นที่เสมือนที่ถูกต้องตามจำนวนที่แน่นอนสำหรับกองซ้อนหรืออย่างอื่นหรือไม่? ฉันสามารถสร้าง stack overflow โดยใช้ตัวแปรท้องถิ่นขนาดใหญ่ได้หรือไม่?

ฉันได้เขียนCโปรแกรมขนาดเล็กเพื่อทดสอบสมมติฐานของฉัน มันทำงานบน 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;
}

เรียกใช้โปรแกรมให้&a[0] = f0ceabe0และ&a[n-1] = f16eabdf

แม็พ proc แสดงสแต็ก: 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

จากนั้นฉันก็พยายามเพิ่ม n = 11240 * 1024

เรียกใช้โปรแกรมให้&a[0] = b6b36690และ&a[n-1] = b763068f

แม็พ proc แสดงสแต็ก: 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -sพิมพ์10240ในพีซีของฉัน

อย่างที่คุณเห็นในทั้งสองกรณีขนาดกองซ้อนนั้นใหญ่กว่าขนาดที่ulimit -sให้ไว้ และสแต็กจะเติบโตขึ้นพร้อมกับตัวแปรท้องถิ่นที่ใหญ่กว่า ส่วนบนสุดของสแต็กจะออกมาอีก 3-5kB &a[0](AFAIK โซนสีแดงคือ 128B)

ดังนั้นแผนที่สแต็กนี้ได้รับการจัดสรรอย่างไร

คำตอบ:


14

ปรากฏว่าขีด จำกัด หน่วยความจำสแต็กไม่ได้รับการจัดสรร (อย่างไรก็ตามมันไม่สามารถทำได้โดยไม่ จำกัด สแต็ก) https://www.kernel.org/doc/Documentation/vm/overcommit-accountingพูดว่า:

การเติบโตของสแต็กภาษา C ทำ mremap โดยนัย หากคุณต้องการการรับประกันที่แน่นอนและวิ่งไปใกล้ขอบคุณต้อง mmap สแต็คของคุณสำหรับขนาดที่ใหญ่ที่สุดที่คุณคิดว่าคุณจะต้อง สำหรับการใช้งานสแต็คทั่วไปมันไม่สำคัญมากนัก แต่มันก็เป็นเรื่องมุมหากคุณใส่ใจจริงๆ

อย่างไรก็ตามการ mmapping สแต็กจะเป็นเป้าหมายของคอมไพเลอร์ (ถ้ามีตัวเลือกสำหรับสิ่งนั้น)

แก้ไข: หลังจากการทดสอบบางอย่างในเครื่องเดเบียน x84_64 ฉันพบว่าสแต็คเติบโตขึ้นโดยไม่มีการเรียกระบบใด ๆ (ตามstrace) ดังนั้นนี่หมายความว่าเคอร์เนลโตขึ้นโดยอัตโนมัติ (นี่คือสิ่งที่ "นัย" หมายถึงด้านบน) คือไม่มีโดยชัดเจนmmap/ mremapจากกระบวนการ

มันค่อนข้างยากที่จะหาข้อมูลรายละเอียดยืนยันสิ่งนี้ ฉันแนะนำให้ทำความเข้าใจกับ Linux Virtual Memory Managerโดย Mel Gorman ฉันคิดว่าคำตอบอยู่ในส่วน 4.6.1 การจัดการ Page Faultด้วยข้อยกเว้น "ภูมิภาคไม่ถูกต้อง แต่อยู่ข้างพื้นที่ที่ขยายได้เช่นสแต็ก" และการกระทำที่สอดคล้องกัน "ขยายพื้นที่และจัดสรรหน้า" ดูเพิ่มเติม D.5.2 การขยายสแต็

การอ้างอิงอื่น ๆ เกี่ยวกับการจัดการหน่วยความจำ Linux (แต่ไม่มีอะไรเกี่ยวกับสแต็ก):

แก้ไข 2: การใช้งานนี้มีข้อเสียเปรียบ: ในกรณีมุมอาจไม่สามารถตรวจพบการชนกันของสแต็กฮีปแม้ในกรณีที่สแต็กจะใหญ่กว่าขีด จำกัด ! เหตุผลก็คือการเขียนตัวแปรในสแต็กอาจสิ้นสุดในหน่วยความจำฮีปที่จัดสรรซึ่งในกรณีนี้ไม่มีข้อบกพร่องของเพจและเคอร์เนลไม่สามารถรู้ได้ว่าสแต็กจำเป็นต้องขยาย ดูตัวอย่างของฉันในการอภิปรายSilent stack-heap collision ภายใต้ GNU / Linuxฉันเริ่มในรายการ gcc-help เพื่อหลีกเลี่ยงปัญหานั้นคอมไพเลอร์จำเป็นต้องเพิ่มโค้ดบางส่วนที่การเรียกใช้ฟังก์ชัน สิ่งนี้สามารถทำได้ด้วย-fstack-checkสำหรับ GCC (ดูการตอบกลับของ Ian Lance Taylor และหน้ารายละเอียด GCC สำหรับผู้ชาย)


ดูเหมือนว่าคำตอบที่ถูกต้องสำหรับคำถามของฉัน แต่มันทำให้ฉันสับสนมากขึ้น การโทร mremap จะถูกเรียกเมื่อใด มันจะเป็นตึกระฟ้าที่อยู่ในโปรแกรมหรือไม่
Amos

@amos ฉันคิดว่าการเรียก mremap จะถูกเรียกใช้หากจำเป็นต้องมีการเรียกใช้ฟังก์ชั่นหรือเมื่อมีการเรียกใช้ alloca ()
vinc17

มันอาจเป็นความคิดที่ดีที่จะพูดถึง mmap สำหรับคนที่ไม่รู้
Faheem Mitha

@FaheemMitha ฉันได้เพิ่มข้อมูลบางอย่าง สำหรับผู้ที่ไม่ทราบว่า mmap คืออะไรให้ดูคำถามที่พบบ่อยเกี่ยวกับหน่วยความจำที่กล่าวถึงข้างต้น ที่นี่สำหรับสแต็กมันจะเป็น "การทำแผนที่แบบไม่ระบุชื่อ" เพื่อให้พื้นที่ที่ไม่ได้ใช้จะไม่ใช้หน่วยความจำกายภาพ แต่ตามที่อธิบายโดย Mel Gorman เคอร์เนลทำการแมป (หน่วยความจำเสมือน) และการจัดสรรทางกายภาพในเวลาเดียวกัน .
vinc17

1
@max ฉันได้ลองใช้โปรแกรมของ OP ด้วยการulimit -sให้ 10240 เช่นภายใต้เงื่อนไขของ OP และฉันได้รับ SIGSEGV ตามที่คาดไว้ (นี่คือสิ่งที่ POSIX ต้องการ: "ถ้าเกินขีด จำกัด นี้ SIGSEGV จะถูกสร้างขึ้นสำหรับเธรด ") ฉันสงสัยข้อผิดพลาดในเคอร์เนลของ OP
vinc17

6

Linux kernel 4.2

โปรแกรมทดสอบน้อยที่สุด

จากนั้นเราสามารถทดสอบกับโปรแกรม NASM 64 บิตขั้นต่ำ:

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

ตรวจสอบให้แน่ใจว่าคุณปิด ASLR และลบตัวแปรสภาพแวดล้อมตามที่จะไปอยู่ในสแต็กและใช้พื้นที่:

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

ขีด จำกัด อยู่ต่ำกว่าของฉันเล็กน้อยulimit -s(8MiB สำหรับฉัน) ดูเหมือนว่าจะเป็นเช่นนี้เนื่องจากข้อมูลที่ระบุไว้ใน System V พิเศษเริ่มวางบนสแต็กเพิ่มเติมจากสภาพแวดล้อม: พารามิเตอร์บรรทัดคำสั่ง Linux 64 ในแอสเซมบลี | กองล้นมากเกินไป

หากคุณจริงจังเกี่ยวกับเรื่องนี้สิ่งที่ต้องทำให้ภาพ initrd น้อยที่สุดที่จะเริ่มต้นการเขียนจากด้านบนสแต็คและไปลงแล้วใช้มันกับ QEMU + GDB ใส่ในวงที่พิมพ์อยู่กองและจุดพักที่dprintf acct_stack_growthมันจะรุ่งโรจน์

ที่เกี่ยวข้อง:


2

โดยค่าเริ่มต้นขนาดสแต็กสูงสุดถูกกำหนดให้เป็น 8MB ต่อกระบวนการ
แต่มันสามารถเปลี่ยนแปลงได้โดยใช้ulimit:

แสดงค่าเริ่มต้นเป็น kB:

$ ulimit -s
8192

ตั้งเป็นไม่ จำกัด :

ulimit -s unlimited

ส่งผลกระทบต่อเชลล์และ subshells ปัจจุบันและกระบวนการลูกของพวกเขา
( ulimitเป็นคำสั่ง shell builtin)

คุณสามารถแสดงช่วงที่อยู่จริงของสแต็กที่ใช้กับ:
cat /proc/$PID/maps | grep -F '[stack]'
บน Linux


ดังนั้นเมื่อโปรแกรมถูกโหลดโดยเชลล์ปัจจุบัน OS จะทำให้ส่วนหน่วยความจำของulimit -sKB ใช้ได้สำหรับโปรแกรม ในกรณีของฉันคือ 10240KB แต่เมื่อฉันประกาศอาร์เรย์ท้องถิ่นchar a[10240*1024]และตั้งค่าa[0]=1โปรแกรมออกอย่างถูกต้อง ทำไม?
Amos

ลองตั้งองค์ประกอบสุดท้ายด้วย และตรวจสอบให้แน่ใจว่าพวกเขาไม่ได้รับการปรับให้เหมาะสม
vinc17

@amos ฉันคิดว่า vinc17 หมายความว่าคุณตั้งชื่อพื้นที่หน่วยความจำที่จะไม่พอดีกับสแต็คในโปรแกรมของคุณแต่ในขณะที่คุณไม่ได้เข้าถึงในส่วนที่ไม่พอดีเครื่องจะไม่สังเกตเห็นว่า - ไม่ แม้แต่รับข้อมูลนั้น
Volker Siegel

@amos ลองint n = 10240*1024; char a[n]; memset(a,'x',n);... seg ข้อผิดพลาด
goldilocks

2
@amos ดังนั้นอย่างที่คุณเห็นa[]ไม่มีการจัดสรรในสแต็ก 10MB ของคุณ คอมไพเลอร์อาจเห็นว่าไม่มีการเรียกซ้ำและทำการจัดสรรพิเศษหรืออย่างอื่นเช่นสแต็กไม่ต่อเนื่องหรือทางอ้อม
vinc17
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.