ทำไมเรายังคงเติบโตสแต็คย้อนหลังได้


46

เมื่อทำการคอมไพล์รหัส C และดูแอสเซมบลีมันทั้งหมดจะมีสแต็กเติบโตแบบย้อนกลับดังนี้:

_main:
    pushq   %rbp
    movl    $5, -4(%rbp)
     popq    %rbp
    ret

-4(%rbp)- หมายความว่าตัวชี้ฐานหรือตัวชี้สแต็คกำลังเคลื่อนย้ายที่อยู่หน่วยความจำแทนที่จะไปขึ้นหรือไม่? ทำไมถึงเป็นอย่างนั้น?

ฉันเปลี่ยน$5, -4(%rbp)ไป$5, +4(%rbp)คอมไพล์และรันโค้ดและไม่มีข้อผิดพลาด เหตุใดเราจึงต้องย้อนกลับไปในหน่วยความจำสแต็ค


2
โปรดทราบว่า-4(%rbp)จะไม่ย้ายตัวชี้ฐานเลยและ+4(%rbp)อาจไม่สามารถใช้งานได้
Margaret Bloom

14
" ทำไมเรายังต้องย้อนกลับไป " - คุณคิดว่าอะไรจะเป็นประโยชน์ในการก้าวไปข้างหน้า? ในที่สุดมันไม่สำคัญคุณเพียงแค่ต้องเลือก
Bergi

31
"ทำไมเราถึงขยายกองซ้อนไปข้างหลัง?" - เพราะถ้าเราไม่ใช่คนอื่นจะถามว่าทำไมmallocกองด้านหลังถึงเพิ่มขึ้น
นายทหารที่

2
@MargaretBloom: เห็นได้ชัดว่าบนแพลตฟอร์มของ OP รหัสเริ่มต้น CRT ไม่สนใจว่าจะปิดmainกั้น RBP ของมันหรือไม่ เป็นไปได้อย่างแน่นอน (และใช่การเขียน4(%rbp)จะทำตามค่า RBP ที่บันทึกไว้) ผิดพลาดจริง ๆ แล้วหลักนี้ไม่ได้ทำmov %rsp, %rbpดังนั้นการเข้าถึงหน่วยความจำจะสัมพันธ์กับผู้โทร RBPถ้านั่นคือสิ่งที่ OP ทดสอบจริง ๆ !!! หากสิ่งนี้ถูกคัดลอกจากคอมไพเลอร์เอาท์พุทคำแนะนำบางอย่างถูกปล่อยออกไป!
Peter Cordes

1
สำหรับฉันแล้วดูเหมือนว่า "ย้อนกลับ" หรือ "ส่งต่อ" (หรือ "ลง" และ "ขึ้น") ขึ้นอยู่กับมุมมองของคุณ หากคุณไดอะแกรมหน่วยความจำเป็นคอลัมน์ที่มีที่อยู่ต่ำอยู่ด้านบนการเพิ่มสแต็กโดยการลดลงของตัวชี้สแต็กจะคล้ายกับฟิสิคัลสแต็ก
jamesdlin

คำตอบ:


86

นี่หมายถึงตัวชี้ฐานหรือตัวชี้สแต็คกำลังเคลื่อนย้ายที่อยู่หน่วยความจำลงไปแทนที่จะขึ้นไปหรือไม่? ทำไมถึงเป็นอย่างนั้น?

ใช่pushคำแนะนำจะลดตัวชี้สแต็กและเขียนไปยังสแต็กในขณะที่popทำย้อนกลับอ่านจากสแต็กและเพิ่มตัวชี้สแต็ก

นี่เป็นเรื่องในประวัติศาสตร์สำหรับเครื่องจักรที่มีหน่วยความจำ จำกัด กองซ้อนถูกวางไว้สูงขึ้นเรื่อย ๆ ในขณะที่กองนั้นวางต่ำและโตขึ้น มี "ช่องว่างเดียวของ" หน่วยความจำว่าง "- ระหว่างกอง & กองและช่องว่างนี้มีการใช้ร่วมกันทั้งสองสามารถเติบโตเป็นช่องว่างตามความต้องการของแต่ละบุคคล ดังนั้นโปรแกรมจะเรียกใช้หน่วยความจำไม่เพียงพอเมื่อสแต็กและฮีปชนกันโดยไม่มีหน่วยความจำว่าง 

หากสแต็คและฮีปเติบโตในทิศทางเดียวกันแสดงว่ามีช่องว่างสองช่องและสแต็กไม่สามารถเติบโตเป็นช่องว่างของฮีปได้ (ในทางกลับกันก็เป็นปัญหาเช่นกัน)

ในขั้นต้นโปรเซสเซอร์ไม่มีคำแนะนำการจัดการสแต็กโดยเฉพาะ อย่างไรก็ตามในขณะที่เพิ่มการรองรับสแต็คเข้ากับฮาร์ดแวร์จึงใช้รูปแบบการเติบโตที่ลดลงและโปรเซสเซอร์ยังคงติดตามรูปแบบนี้ในปัจจุบัน

หนึ่งอาจโต้แย้งว่าบนเครื่อง 64- บิตมีพื้นที่ที่อยู่เพียงพอที่จะอนุญาตให้มีหลายช่องว่าง - และเป็นหลักฐานช่องว่างหลายจำเป็นต้องเป็นกรณีเมื่อกระบวนการมีหลายกระทู้ แม้ว่าสิ่งนี้จะไม่เพียงพอที่จะเปลี่ยนแปลงสิ่งต่างๆรอบตัวเนื่องจากระบบหลาย ๆ ช่องทาง แต่ทิศทางการเติบโตนั้นเป็นกฎเกณฑ์โดยพลการ


คุณจะต้องเปลี่ยนการจัดการคำแนะนำในการสั่งซื้อเพื่อเปลี่ยนทิศทางของสแต็คหรืออื่น ๆ ให้ขึ้นกับการใช้งานของสแต็คของ CPU ที่อุทิศตนผลักดันและ popping คำแนะนำ (เช่นpush, pop, call, ret, อื่น ๆ )

โปรดทราบว่าสถาปัตยกรรมชุดคำสั่ง MIPS ไม่ได้ทุ่มเทpush& popดังนั้นจึงเป็นประโยชน์ในการขยายสแต็คในทิศทางใดทิศทางหนึ่ง - คุณยังอาจต้องการโครงร่างหน่วยความจำหนึ่งช่องว่างสำหรับกระบวนการเธรดเดียว แต่สามารถขยายสแต็กขึ้นและกอง ลงต่ำ อย่างไรก็ตามหากคุณทำเช่นนั้นรหัสC varargsบางตัวอาจต้องมีการปรับเปลี่ยนในแหล่งที่มาหรือในการผ่านพารามิเตอร์ภายใต้ประทุน

(อันที่จริงแล้วเนื่องจากไม่มีการจัดการสแต็กโดยเฉพาะบน MIPS เราสามารถใช้การเพิ่มขึ้นก่อนหรือหลังหรือการลดลงก่อนหรือหลังสำหรับการกดลงบนสแต็คตราบใดที่เราใช้การย้อนกลับที่แน่นอนสำหรับการหลุดออกจากสแต็ค ระบบปฏิบัติการเคารพรูปแบบการใช้สแต็คที่เลือกแน่นอนในระบบฝังตัวบางตัวและระบบการศึกษาบางระบบสแต็ค MIPS จะโตขึ้น)


32
มันไม่ได้เป็นเพียงpushและpopสถาปัตยกรรมมากที่สุด แต่ยังห่างไกลความสำคัญมากขึ้นขัดจังหวะการจัดการcall, retและสิ่งอื่นได้อบในการมีปฏิสัมพันธ์กับสแต็ค
Deduplicator

3
ARM สามารถมีรสสแต็คทั้งสี่ทั้งหมดได้
Margaret Bloom

14
สำหรับสิ่งที่คุ้มค่าฉันไม่คิดว่า "ทิศทางการเติบโตนั้นไม่มีกฎเกณฑ์" ในแง่ที่ว่าทั้งสองทางเลือกนั้นดีพอ ๆ กัน การขยายขนาดลงมีคุณสมบัติที่โอเวอร์โฟลว์ส่วนท้ายของบัฟเฟอร์ clobbers สแต็กเฟรมก่อนหน้ารวมถึงที่อยู่ผู้ส่งที่บันทึกไว้ การเติบโตขึ้นมามีคุณสมบัติที่ล้นส่วนท้ายของบัฟเฟอร์ clobbers ที่จัดเก็บในที่เดียวกันหรือภายหลัง (ถ้าบัฟเฟอร์ไม่ได้อยู่ในล่าสุดอาจมีคนที่ใหม่กว่า) เรียกเฟรมและอาจเป็นเพียงพื้นที่ที่ไม่ได้ใช้ หน้าหลังสแต็ก) ดังนั้นจากมุมมองด้านความปลอดภัยการเติบโตขึ้นจึงเป็นที่ต้องการอย่างมาก
..

6
@R .. : การเติบโตขึ้นไม่ได้กำจัดการใช้ประโยชน์จากบัฟเฟอร์ที่เกินขอบเขตเนื่องจากฟังก์ชั่นที่มีช่องโหว่มักจะไม่ออกจากฟังก์ชัน: พวกเขาเรียกใช้ฟังก์ชันอื่นโดยวางที่อยู่ผู้ส่งคืนไว้เหนือบัฟเฟอร์ ฟังก์ชัน Leaf ที่รับพอยน์เตอร์จากผู้ที่โทรเข้ามานั้นอาจกลายเป็นเรื่องง่ายที่จะเขียนทับที่อยู่ผู้ส่งคืน เช่นถ้าฟังก์ชั่นจัดสรรบัฟเฟอร์บนสแต็กและส่งผ่านไปยังgets()หรือไม่strcpy()ที่ไม่ได้รับการ inline แล้วผลตอบแทนในฟังก์ชั่นห้องสมุดเหล่านั้นจะใช้ที่อยู่ที่ส่งคืนเขียนทับ ขณะนี้มีสแต็กที่เพิ่มขึ้นต่ำลงก็คือเมื่อผู้โทรกลับ
Peter Cordes

5
@PeterCordes: ความคิดเห็นของฉันจริง ๆ แล้วตั้งข้อสังเกตว่าเฟรมสแต็กในระดับเดียวกันหรือมากกว่าล่าสุดกว่าบัฟเฟอร์ที่ล้นก็ยังคงสามารถอุดตันได้ แต่ก็มีน้อยกว่ามาก ในกรณีที่ฟังก์ชั่น clobbering เป็นฟังก์ชั่นลีฟที่เรียกโดยตรงโดยฟังก์ชันที่บัฟเฟอร์คือ (เช่นstrcpy) บนซุ้มโค้งที่ที่อยู่ผู้ส่งคืนถูกเก็บไว้ในรีจิสเตอร์เว้นแต่จะต้องมีการรั่วไหล ที่อยู่
. ..

8

ในระบบเฉพาะของคุณสแต็กจะเริ่มต้นจากที่อยู่หน่วยความจำสูงและ "ขยาย" ลงไปจนถึงที่อยู่หน่วยความจำเหลือน้อย (กรณีสมมาตรจากต่ำไปสูงยังมีอยู่)

และเนื่องจากคุณเปลี่ยนจาก -4 และ +4 และมันวิ่งไม่ได้หมายความว่าถูกต้อง เลย์เอาต์ของหน่วยความจำของโปรแกรมที่ทำงานนั้นมีความซับซ้อนมากขึ้นและขึ้นอยู่กับปัจจัยอื่น ๆ ที่อาจมีส่วนทำให้คุณไม่ได้ชนกับโปรแกรมที่แสนง่ายนี้ในทันที


1

ตัวชี้สแต็กชี้ที่ขอบเขตระหว่างหน่วยความจำสแต็กที่จัดสรรและไม่ได้ถูกจัดสรร การขยายมันลงหมายความว่ามันเริ่มต้นที่จุดเริ่มต้นของโครงสร้างแรกในพื้นที่สแต็กที่จัดสรรโดยมีรายการอื่น ๆ ที่ได้รับการจัดสรรตามที่อยู่ที่มีขนาดใหญ่กว่า การมีพอยน์เตอร์ชี้ไปที่จุดเริ่มต้นของโครงสร้างที่จัดสรรนั้นเป็นเรื่องธรรมดามากกว่าอีกทางหนึ่ง

ในหลาย ๆ ระบบในทุกวันนี้มีการลงทะเบียนแยกต่างหากสำหรับเฟรมสแต็กซึ่งค่อนข้างเชื่อถือได้ว่าจะไม่คลี่คลายเพื่อที่จะหาสายโซ่ที่มีการจัดเก็บตัวแปรภายในเครื่องสลับกัน วิธีนี้ลงทะเบียนกองกรอบการตั้งค่าบนสถาปัตยกรรมบางหมายความว่ามันจะจบลงชี้ที่อยู่เบื้องหลังการจัดเก็บตัวแปรท้องถิ่นเมื่อเทียบกับตัวชี้สแต็คก่อนที่มัน ดังนั้นการใช้การลงทะเบียนเฟรมกองซ้อนนี้จึงต้องมีการสร้างดัชนีเชิงลบ

โปรดทราบว่าสแต็กเฟรมและการจัดทำดัชนีเป็นลักษณะด้านข้างของภาษาคอมพิวเตอร์ที่คอมไพล์ดังนั้นจึงเป็นตัวสร้างโค้ดของคอมไพเลอร์ที่ต้องจัดการกับ "ความไม่เป็นธรรมชาติ" แทนที่จะเป็นโปรแกรมเมอร์ภาษาแอสเซมบลีที่ไม่ดี

ดังนั้นในขณะที่มีเหตุผลทางประวัติศาสตร์ที่ดีสำหรับการเลือกสแต็คที่จะเติบโตลง (และบางส่วนจะถูกเก็บไว้หากคุณเขียนโปรแกรมในภาษาแอสเซมบลีและไม่ต้องตั้งค่ากรอบสแต็คที่เหมาะสม)


2
"ในหลาย ๆ ระบบในทุกวันนี้มีการลงทะเบียนแบบแยกสำหรับเฟรมสแต็ก" ที่คุณล้าหลัง รูปแบบข้อมูลการดีบักที่สมบูรณ์ยิ่งขึ้นได้ขจัดความต้องการตัวชี้เฟรมในปัจจุบัน
ปีเตอร์กรี
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.