Segmentation Fault ทำงานอย่างไรภายใต้ประทุน?


266

ฉันไม่สามารถหาข้อมูลใด ๆ นอกเหนือไปจาก "MMU ของ CPU ส่งสัญญาณ" และ "เคอร์เนลนำมันไปยังโปรแกรมที่ละเมิดทำให้ยุติ"

"Segmentation fault"ฉันคิดว่ามันอาจจะส่งสัญญาณไปยังเปลือกและเปลือกจัดการกับมันโดยยกเลิกกระบวนการที่กระทำผิดและการพิมพ์ ดังนั้นฉันทดสอบสมมติฐานโดยการเขียนเชลล์ที่น้อยที่สุดที่ฉันเรียกว่าcrsh (crap shell) เชลล์นี้ไม่ได้ทำอะไรเลยนอกจากเอาข้อมูลผู้ใช้และป้อนไปยังsystem()วิธีการ

#include <stdio.h>
#include <stdlib.h>

int main(){
    char cmdbuf[1000];
    while (1){
        printf("Crap Shell> ");
        fgets(cmdbuf, 1000, stdin);
        system(cmdbuf);
    }
}

ดังนั้นฉันจึงวิ่งเปลือกนี้ใน terminal เปลือย (โดยไม่ต้องbashวิ่งภายใต้) จากนั้นฉันก็ดำเนินการเรียกใช้โปรแกรมที่สร้าง segfault หากสมมติฐานของฉันถูกต้องสิ่งนี้อาจจะเป็น) ความผิดพลาดcrshปิด xterm, b) ไม่พิมพ์"Segmentation fault"หรือ c) ทั้งสองอย่าง

braden@system ~/code/crsh/ $ xterm -e ./crsh
Crap Shell> ./segfault
Segmentation fault
Crap Shell> [still running]

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


43
crshเป็นความคิดที่ดีสำหรับการทดลองประเภทนี้ ขอขอบคุณที่แจ้งให้เราทุกคนทราบเกี่ยวกับเรื่องนี้และแนวคิดที่อยู่เบื้องหลัง
Bruce Ediger

30
เมื่อฉันเห็นครั้งแรกcrshฉันคิดว่ามันจะออกเสียงว่า "ผิดพลาด" ฉันไม่แน่ใจว่าเป็นชื่อที่เหมาะสมหรือไม่
jpmc26

56
นี่เป็นการทดลองที่ดี ... แต่คุณควรรู้ว่าอะไรsystem()อยู่ภายใต้ประทุน ปรากฎว่าsystem()จะวางไข่กระบวนการเชลล์! ดังนั้นกระบวนการเชลล์ของคุณวางไข่กระบวนการเชลล์อื่นและกระบวนการเชลล์นั้น (อาจเป็น/bin/shอย่างนั้น) เป็นกระบวนการที่รันโปรแกรม วิธีการ/bin/shหรือbashงานคือการใช้fork()และexec()(หรือฟังก์ชั่นอื่นในexecve()ครอบครัว)
Dietrich Epp

4
@BradenBest: อย่างแน่นอน อ่านหน้าคู่มือman 2 waitก็จะรวมถึงแมโครและWIFSIGNALED() WTERMSIG()
Dietrich Epp

4
@DietrichEpp อย่างที่คุณพูด! ฉันลองเพิ่มการตรวจสอบเพื่อ(WIFSIGNALED(status) && WTERMSIG(status) == 11)ให้พิมพ์สิ่งที่โง่ ( "YOU DUN GOOFED AND TRIGGERED A SEGFAULT") เมื่อฉันรันsegfaultโปรแกรมจากภายในcrshมันพิมพ์ออกมาอย่างแน่นอน ในขณะเดียวกันคำสั่งที่ออกโดยปกติจะไม่สร้างข้อความแสดงข้อผิดพลาด
Braden ที่ดีที่สุด

คำตอบ:


248

ซีพียูที่ทันสมัยทั้งหมดมีความสามารถในการขัดจังหวะคำสั่งเครื่องที่ดำเนินการอยู่ในปัจจุบัน พวกเขาบันทึกสถานะเพียงพอ (โดยปกติ แต่ไม่เสมอไปบนสแต็ก) เพื่อให้สามารถดำเนินการต่อได้ในภายหลังราวกับว่าไม่มีอะไรเกิดขึ้น (คำสั่งที่ถูกขัดจังหวะจะเริ่มใหม่จากศูนย์) จากนั้นพวกเขาก็เริ่มดำเนินการตัวจัดการขัดจังหวะซึ่งเป็นเพียงรหัสเครื่องมากขึ้น แต่วางไว้ในตำแหน่งพิเศษเพื่อให้ CPU รู้ว่ามันอยู่ที่ไหนล่วงหน้า ตัวจัดการขัดจังหวะมักเป็นส่วนหนึ่งของเคอร์เนลของระบบปฏิบัติการ: ส่วนประกอบที่ทำงานด้วยสิทธิ์ที่ยิ่งใหญ่ที่สุดและรับผิดชอบในการควบคุมการทำงานของส่วนประกอบอื่น ๆ ทั้งหมด 1,2

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

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

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

แต่สมมติว่ากระบวนการทำผิดกฎจริงๆ มันพยายามเข้าถึงหน้าเว็บที่ไม่เคยมี RAM ใดถูกแมปไว้หรือพยายามเรียกใช้หน้าที่ถูกทำเครื่องหมายว่าไม่มีรหัสเครื่องหรืออะไรก็ตาม ตระกูลของระบบปฏิบัติการที่รู้จักกันทั่วไปว่า "Unix" ทุกคนใช้สัญญาณเพื่อจัดการกับสถานการณ์นี้ 4สัญญาณคล้ายกับการขัดจังหวะ แต่ถูกสร้างขึ้นโดยเคอร์เนลและสอดแทรกโดยกระบวนการแทนที่จะสร้างโดยฮาร์ดแวร์และสอดแทรกโดยเคอร์เนล กระบวนการสามารถกำหนดตัวจัดการสัญญาณในรหัสของตัวเองและบอกเคอร์เนลว่าพวกเขาอยู่ที่ไหน ตัวจัดการสัญญาณเหล่านั้นจะดำเนินการขัดจังหวะการไหลปกติของการควบคุมเมื่อจำเป็น สัญญาณทั้งหมดมีจำนวนและชื่อสองชื่อหนึ่งชื่อเป็นอักษรย่อและอีกชื่อหนึ่งเป็นวลีลับน้อยกว่าเล็กน้อย สัญญาณที่สร้างขึ้นเมื่อกระบวนการแบ่งกฎการป้องกันหน่วยความจำคือ (ตามการประชุม) หมายเลข 11 และชื่อของมันคือSIGSEGVและ "การแบ่งกลุ่มผิด" 5,6

ความแตกต่างที่สำคัญระหว่างสัญญาณและการขัดจังหวะคือว่ามีพฤติกรรมเริ่มต้นสำหรับทุกสัญญาณ หากระบบปฏิบัติการล้มเหลวในการกำหนดตัวจัดการสำหรับการขัดจังหวะทั้งหมดนั่นคือข้อผิดพลาดในระบบปฏิบัติการและคอมพิวเตอร์ทั้งหมดจะทำงานล้มเหลวเมื่อ CPU พยายามเรียกใช้ตัวจัดการที่หายไป แต่กระบวนการไม่มีภาระผูกพันในการกำหนดตัวจัดการสัญญาณสำหรับสัญญาณทั้งหมด หากเคอร์เนลสร้างสัญญาณสำหรับกระบวนการและสัญญาณนั้นถูกทิ้งไว้ที่พฤติกรรมเริ่มต้นเคอร์เนลจะดำเนินการต่อไปและทำทุกอย่างที่เป็นค่าเริ่มต้นและไม่รบกวนกระบวนการ พฤติกรรมเริ่มต้นของสัญญาณส่วนใหญ่เป็น "ไม่ทำอะไรเลย" หรือ "ยุติกระบวนการนี้และอาจสร้างคอร์ดัมพ์" SIGSEGVเป็นหนึ่งในหลัง

ดังนั้นในการสรุปเรามีกระบวนการที่ทำลายกฎการป้องกันหน่วยความจำ CPU ระงับกระบวนการและสร้างการขัดจังหวะแบบซิงโครนัส เคอร์เนลที่สอดแทรกและสร้างSIGSEGVสัญญาณสำหรับกระบวนการ สมมติว่ากระบวนการไม่ได้ตั้งค่าตัวจัดการสัญญาณสำหรับSIGSEGVดังนั้นเคอร์เนลดำเนินพฤติกรรมเริ่มต้นซึ่งจะยุติกระบวนการ สิ่งนี้มีเอฟเฟกต์เหมือนกับการ_exitเรียกของระบบ: เปิดไฟล์ถูกปิด, หน่วยความจำถูกจัดสรรคืน, ฯลฯ

จนถึงจุดนี้ไม่มีอะไรพิมพ์ข้อความใด ๆ ที่มนุษย์สามารถมองเห็นได้และเชลล์ (หรือโดยทั่วไปกระบวนการหลักของกระบวนการที่เพิ่งถูกยกเลิก) ไม่ได้มีส่วนเกี่ยวข้องเลย SIGSEGVไปที่กระบวนการที่ทำลายกฎไม่ใช่หลัก อย่างไรก็ตามขั้นตอนต่อไปในลำดับนั้นคือการแจ้งให้ทราบถึงกระบวนการแม่ว่าลูกของมันถูกยกเลิก นี้สามารถเกิดขึ้นในรูปแบบที่แตกต่างกันหลายแห่งที่ง่ายที่สุดคือเมื่อผู้ปกครองแล้วรอสำหรับการแจ้งเตือนนี้ใช้หนึ่งในwaitสายระบบ ( wait, waitpid, wait4ฯลฯ ) ในกรณีนั้นเคอร์เนลจะทำให้การเรียกของระบบนั้นกลับมาและจัดหากระบวนการหลักด้วยหมายเลขรหัสที่เรียกว่าสถานะการออก. 7สถานะการออกแจ้งให้ผู้ปกครองทราบว่าเหตุใดกระบวนการลูกจึงถูกยกเลิก ในกรณีนี้มันจะได้เรียนรู้ว่าเด็กถูกยกเลิกเนื่องจากพฤติกรรมเริ่มต้นของSIGSEGVสัญญาณ

กระบวนการหลักอาจรายงานเหตุการณ์ต่อมนุษย์โดยการพิมพ์ข้อความ โปรแกรมเชลล์ทำสิ่งนี้เกือบทุกครั้ง คุณcrshไม่ได้ใส่รหัสในการทำเช่นนั้น แต่มันเกิดขึ้นแล้วเพราะรูทีนไลบรารี่ C systemเรียกใช้เชลล์ที่มีคุณสมบัติครบถ้วน/bin/sh, "ภายใต้ประทุน" crshเป็นปู่ย่าตายายในสถานการณ์นี้ การแจ้งเตือนกระบวนการหลักจะถูกสอดแทรกด้วย/bin/shซึ่งจะพิมพ์ข้อความตามปกติ แล้ว/bin/shตัวเองออกเพราะมันมีอะไรมากกว่าที่จะทำและการดำเนินงานห้องสมุด C ของsystemได้รับว่าการแจ้งเตือนทางออก คุณสามารถเห็นการแจ้งเตือนการออกในรหัสของคุณโดยตรวจสอบค่าส่งคืนของsystem; แต่มันจะไม่บอกคุณว่ากระบวนการหลานจะตายใน segfault เพราะมันถูกใช้โดยกระบวนการเชลล์ระดับกลาง


เชิงอรรถ

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

  2. อาจจะมีโปรแกรมที่เรียกว่า "ไฮเปอร์ไวเซอร์" หรือ "ผู้จัดการเครื่องเสมือน" ที่แม้มีสิทธิพิเศษมากกว่าเคอร์เนล แต่สำหรับวัตถุประสงค์ของคำตอบนี้ก็ถือได้ว่าเป็นส่วนหนึ่งของฮาร์ดแวร์

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

  4. ระบบปฏิบัติการเพียงระบบเดียวที่คุณต้องจัดการอีกต่อไปซึ่งไม่สามารถนำมาใช้กับ Unix ได้คือ Windows มันไม่ได้ใช้สัญญาณในสถานการณ์นี้ (อันที่จริงก็ไม่ได้มีสัญญาณ; หน้าต่าง<signal.h>อินเตอร์เฟซที่จะแกล้งอย่างสมบูรณ์โดยห้องสมุด C.) จะใช้สิ่งที่เรียกว่า " การจัดการข้อยกเว้นที่มีโครงสร้าง " แทน

  5. บางละเมิดหน่วยความจำป้องกันการสร้างSIGBUS(ข้อผิดพลาด "รถเมล์") SIGSEGVแทน เส้นแบ่งระหว่างทั้งสองถูกขีดเส้นใต้และแตกต่างกันไปตามระบบ หากคุณเคยเขียนโปรแกรมที่กำหนดจัดการสำหรับมันอาจจะเป็นความคิดที่ดีที่จะกำหนดจัดการเดียวกันSIGSEGVSIGBUS

  6. "ความผิดพลาดในการแบ่งกลุ่ม" เป็นชื่อของการขัดจังหวะที่สร้างขึ้นสำหรับการละเมิดของหน่วยความจำป้องกันโดยหนึ่งในคอมพิวเตอร์ที่วิ่งไปที่เดิม UnixอาจจะPDP-11 " การแบ่งกลุ่ม " เป็นการป้องกันหน่วยความจำชนิดหนึ่งแต่ทุกวันนี้คำว่า "การแบ่งเซ็กเมนต์ผิด " หมายถึงการละเมิดการป้องกันหน่วยความจำประเภทใด ๆ โดยทั่วไป

  7. วิธีอื่นทั้งหมดที่กระบวนการผู้ปกครองอาจได้รับแจ้งของเด็กที่มีการยกเลิกท้ายด้วยการเรียกผู้ปกครองwaitและรับสถานะทางออก เป็นเพียงสิ่งอื่นที่เกิดขึ้นก่อน


@zvol: โฆษณา 2) ฉันไม่คิดว่ามันถูกต้องที่จะบอกว่า CPU รู้อะไรเกี่ยวกับกระบวนการ คุณควรพูดว่ามันเรียกใช้ตัวจัดการขัดจังหวะซึ่งโอนการควบคุม
user323094

9
@ user323094 CPU แบบมัลติคอร์ที่ทันสมัยรู้จริงเกี่ยวกับกระบวนการค่อนข้างน้อย เพียงพอในสถานการณ์เช่นนี้พวกเขาสามารถระงับเฉพาะเธรดการดำเนินการที่ทริกเกอร์ข้อผิดพลาดการป้องกันหน่วยความจำ นอกจากนี้ฉันพยายามที่จะไม่รับรายละเอียดในระดับต่ำ จากมุมมองของโปรแกรมเมอร์ผู้ใช้พื้นที่สิ่งที่สำคัญที่สุดที่จะเข้าใจเกี่ยวกับขั้นตอนที่ 2 คือมันเป็นฮาร์ดแวร์ที่ตรวจจับการละเมิดการป้องกันหน่วยความจำ น้อยกว่าดังนั้นการแบ่งงานที่แม่นยำระหว่างฮาร์ดแวร์เฟิร์มแวร์และระบบปฏิบัติการเมื่อมันมาถึงการระบุ "กระบวนการละเมิด"
zwol

ความละเอียดอ่อนอีกอย่างที่อาจทำให้ผู้อ่านไร้เดียงสาสับสนคือ"เคอร์เนลส่งสัญญาณ SIGSEGV ที่กระทำผิด ซึ่งใช้ศัพท์แสงปกติ แต่จริง ๆ แล้วหมายความว่าเคอร์เนลบอกตัวเองเพื่อจัดการกับสัญญาณ foo บนแถบกระบวนการ (เช่นรหัสผู้ใช้ไม่ได้เกี่ยวข้องเว้นแต่จะมีการติดตั้งตัวจัดการสัญญาณคำถามที่แก้ไขโดยเคอร์เนล) บางครั้งฉันชอบ"เพิ่มสัญญาณ SIGSEGV ในกระบวนการ"ด้วยเหตุผลดังกล่าว
dmckee

2
ความแตกต่างที่สำคัญระหว่าง SIGBUS (ข้อผิดพลาดของบัส) และ SIGSEGV (ข้อผิดพลาดในการแบ่งกลุ่ม) คือ: SIGSEGV เกิดขึ้นเมื่อ CPU รู้ว่าคุณไม่ควรเข้าถึงที่อยู่ (ดังนั้นจึงไม่ทำคำขอบัสหน่วยความจำภายนอก) SIGBUS เกิดขึ้นเมื่อ CPU ค้นหาข้อมูลเกี่ยวกับปัญหาการกำหนดแอดเดรสเมื่อมีการร้องขอของคุณบนบัสแอดเดรสภายนอกเท่านั้น ตัวอย่างเช่นการขอที่อยู่ทางกายภาพที่ไม่มีอะไรบนรถบัสตอบสนองหรือขอให้อ่านข้อมูลเกี่ยวกับขอบเขตที่ไม่ถูกต้อง (ซึ่งจะต้องใช้คำขอทางกายภาพสองคำขอแทนที่จะเป็นหนึ่ง)
Stuart Caie

2
@StuartCaie คุณอธิบายลักษณะการทำงานของการขัดจังหวะ ; แท้จริงแล้วซีพียูจำนวนมากสร้างความแตกต่างที่คุณร่าง (แม้ว่าบางอย่างจะไม่ทำและเส้นแบ่งระหว่างสองนั้นแตกต่างกัน) อย่างไรก็ตามสัญญาณ SIGSEGV และ SIGBUS นั้นไม่ได้ถูกแมปอย่างเชื่อถือได้กับเงื่อนไขระดับ CPU ทั้งสอง เงื่อนไขเดียวที่ POSIX ต้องการ SIGBUS มากกว่า SIGSEGV คือเมื่อคุณmmapไฟล์ในพื้นที่หน่วยความจำที่ใหญ่กว่าไฟล์จากนั้นเข้าถึง "หน้าทั้งหมด" เกินกว่าจุดสิ้นสุดของไฟล์ (POSIX ค่อนข้างคลุมเครือเกี่ยวกับเมื่อ SIGSEGV / SIGBUS / SIGILL / อื่น ๆ เกิดขึ้น)
zwol

42

เชลล์มีบางสิ่งที่เกี่ยวข้องกับข้อความนั้นและcrshเรียกเชลล์ทางอ้อมซึ่งอาจเป็นbashไปได้

ฉันเขียนโปรแกรม C ขนาดเล็กที่มักจะผิดพลาด seg:

#include <stdio.h>

int
main(int ac, char **av)
{
        int *i = NULL;

        *i = 12;

        return 0;
}

เมื่อฉันเรียกใช้จากเปลือกเริ่มต้นของzshฉันฉันได้รับสิ่งนี้:

4 % ./segv
zsh: 13512 segmentation fault  ./segv

เมื่อฉันเรียกใช้จากbashฉันได้สิ่งที่คุณบันทึกไว้ในคำถามของคุณ:

bediger@flq123:csrc % ./segv
Segmentation fault

ฉันกำลังจะไปเขียนจัดการสัญญาณในรหัสของฉันแล้วฉันตระหนักว่าsystem()โทรห้องสมุดใช้โดยcrshexec เป็นเปลือกตาม/bin/sh man 3 systemนั่น/bin/shคือการพิมพ์ "การแบ่งกลุ่มผิดพลาด" เนื่องจากcrshไม่ใช่อย่างแน่นอน

หากคุณเขียนใหม่crshเพื่อใช้การexecve()เรียกของระบบเพื่อเรียกใช้โปรแกรมคุณจะไม่เห็นสตริง "การแบ่งกลุ่มความผิดพลาด" system()มันมาจากเปลือกเรียกโดย


5
ฉันแค่คุยเรื่องนี้กับ Dietrich Epp ฉันแฮ็ค crsh รุ่นที่ใช้ร่วมกันexecvpและทำการทดสอบอีกครั้งเพื่อหาว่าในขณะที่เชลล์ยังไม่พัง (หมายถึง SIGSEGV ไม่เคยถูกส่งไปยังเชลล์) มันไม่พิมพ์ "Segmentation Fault" ไม่มีการพิมพ์อะไรเลย สิ่งนี้ดูเหมือนว่าบ่งชี้ว่าเชลล์ตรวจพบเมื่อลูกของมันถูกฆ่าและมีหน้าที่รับผิดชอบในการพิมพ์ "Segmentation fault" (หรือตัวแปรบางอย่างของมัน)
Braden ที่ดีที่สุด

2
@BradenBest - ฉันทำสิ่งเดียวกันรหัสของฉันก็เลอะกว่ารหัสของคุณ ฉันไม่ได้รับข้อความเลยแม้แต่เปลือก crappier ของฉันก็ไม่ได้พิมพ์อะไรออกมา ฉันใช้waitpid()แต่ละ fork / exec และมันคืนค่าที่แตกต่างกันสำหรับกระบวนการที่มีการแบ่งส่วนผิดพลาดกว่ากระบวนการที่จบด้วยสถานะ 0
Bruce Ediger

21

ฉันไม่สามารถหาข้อมูลใด ๆ นอกเหนือไปจาก "MMU ของ CPU ส่งสัญญาณ" และ "เคอร์เนลนำมันไปยังโปรแกรมที่ละเมิดทำให้ยุติ"

นี่เป็นบทสรุปที่อ่านไม่ออก กลไกสัญญาณ Unix นั้นแตกต่างจากเหตุการณ์เฉพาะ CPU ที่เริ่มต้นกระบวนการ

โดยทั่วไปเมื่อเข้าถึงที่อยู่ที่ไม่ดี (หรือเขียนไปยังพื้นที่แบบอ่านอย่างเดียวพยายามที่จะดำเนินการส่วนที่ไม่สามารถดำเนินการได้ ฯลฯ ) CPU จะสร้างเหตุการณ์เฉพาะ CPU บางตัว (บนสถาปัตยกรรมที่ไม่ใช่ VM แบบดั้งเดิมนี่คือ เรียกว่าการละเมิดการแบ่งส่วนเนื่องจากแต่ละ "ส่วน" (ตามธรรมเนียมข้อความ "ปฏิบัติการ" แบบอ่านได้อย่างเดียว "ข้อมูล" ที่เขียนได้และมีความยาวตัวแปรและสแต็กแบบดั้งเดิมที่ปลายด้านตรงข้ามของหน่วยความจำ) มีช่วงของที่อยู่คงที่ - ในสถาปัตยกรรมสมัยใหม่มีแนวโน้มที่จะเป็นความผิดของหน้า [สำหรับหน่วยความจำที่ไม่ได้แมป] หรือการละเมิดการเข้าถึง [สำหรับการอ่านเขียนและดำเนินการเรื่องการอนุญาต] และฉันจะมุ่งเน้นไปที่คำตอบที่เหลือ)

ตอนนี้ ณ จุดนี้เคอร์เนลสามารถทำสิ่งต่าง ๆ ความผิดพลาดหน้ายังถูกสร้างขึ้นสำหรับหน่วยความจำที่ถูกต้อง แต่ไม่ได้โหลด (เช่นสลับออกหรือในไฟล์ mmapped ฯลฯ ) และในกรณีนี้เคอร์เนลจะแมปหน่วยความจำแล้วเริ่มโปรแกรมผู้ใช้ใหม่จากคำสั่งที่ทำให้ ความผิดพลาด มิฉะนั้นจะส่งสัญญาณ สิ่งนี้จะไม่ "ส่งตรง [เหตุการณ์ดั้งเดิม] ไปยังโปรแกรมที่ละเมิด" เนื่องจากกระบวนการสำหรับการติดตั้งตัวจัดการสัญญาณนั้นแตกต่างกันและส่วนใหญ่ไม่ขึ้นกับสถาปัตยกรรมหากเทียบกับโปรแกรมที่คาดว่าจะจำลองการติดตั้งตัวจัดการขัดจังหวะ

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

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


+1 เพียงคำตอบที่เพิ่มทุกอย่างให้กับคำตอบที่ยอมรับ คำอธิบายที่ดีของประวัติ "การแบ่งส่วน" สนุกจริง: x86 จริงยังคงมีข้อ จำกัด ส่วนในโหมดการป้องกัน 32bit (มีหรือไม่มีเพจจิ้ง (หน่วยความจำเสมือน) เปิดใช้งาน) เพื่อให้คำแนะนำว่าการเข้าถึงหน่วยความจำที่สามารถสร้าง#PF(fault-code)(ผิดหน้า) หรือ#GP(0)( "ถ้าความทรงจำที่ถูกดำเนินการอยู่มีประสิทธิภาพอยู่นอก CS, ขีด จำกัด เซ็กเมนต์ DS, ES, FS หรือ GS ") โหมด 64 บิตจะลดการตรวจสอบข้อ จำกัด เซ็กเมนต์เนื่องจาก OS ใช้เพียงการเพจแทนและโมเดลหน่วยความจำแบบแบนสำหรับพื้นที่ผู้ใช้
Peter Cordes

ที่จริงแล้วฉันเชื่อว่าระบบปฏิบัติการส่วนใหญ่บน x86 ใช้การแบ่งหน้าแบ่งเซ็กเมนต์: กลุ่มใหญ่ในเซกเมนต์แบนราบพื้นที่ที่อยู่เพจ นี่คือวิธีที่คุณป้องกันและแมปหน่วยความจำเคอร์เนลในแต่ละพื้นที่ที่อยู่: ริง (ระดับการป้องกัน) เชื่อมโยงกับเซ็กเมนต์ไม่ใช่เพจ
Lorenzo Dematté

นอกจากนี้ใน NT (แต่ฉันชอบที่จะรู้ว่าใน Unixes ส่วนใหญ่เหมือนกัน!) "การแบ่งเซ็กเมนต์ความผิดพลาด" อาจเกิดขึ้นได้บ่อย: มีเซ็กเมนต์ที่ป้องกัน 64k ที่จุดเริ่มต้นของพื้นที่ผู้ใช้ดังนั้นการยกเลิกการชี้ NULL (เหมาะสมหรือไม่) การแบ่งกลุ่มข้อผิดพลาด
Lorenzo Dematté

1
@ LorenzoDemattéใช่ยูนิกซ์ที่ทันสมัยเกือบทั้งหมดจะออกจากที่อยู่ที่ไม่มีการแมปอย่างถาวรที่จุดเริ่มต้นของพื้นที่ที่อยู่เพื่อรับการตรวจสอบค่า NULL อาจมีขนาดค่อนข้างใหญ่ - ในระบบ 64 บิตที่จริงแล้วอาจเป็นสี่กิกะไบต์ดังนั้นการตัดทอนโดยไม่ตั้งใจของพอยน์เตอร์ถึง 32 บิตจะถูกจับทันที อย่างไรก็ตามการแบ่งส่วนในความรู้สึก x86 ที่เข้มงวดนั้นแทบจะไม่ได้ใช้เลย มีเซ็กเมนต์แบนเดียวสำหรับพื้นที่ผู้ใช้และอีกส่วนสำหรับเคอร์เนลและอาจเป็นคู่สำหรับเทคนิคพิเศษเช่นการใช้ประโยชน์จาก FS และ GS
zwol

1
@ LorenzoDematté NT ใช้ข้อยกเว้นแทนสัญญาณ ในกรณีนี้ STATUS_ACCESS_VIOLATION
Random832

18

ความผิดพลาดของการแบ่งส่วนคือการเข้าถึงที่อยู่หน่วยความจำที่ไม่ได้รับอนุญาต (ไม่ใช่ส่วนหนึ่งของกระบวนการหรือพยายามเขียนข้อมูลแบบอ่านอย่างเดียวหรือดำเนินการข้อมูลที่ไม่สามารถดำเนินการได้ ... ) สิ่งนี้ถูกจับได้โดย MMU (หน่วยจัดการหน่วยความจำซึ่งปัจจุบันเป็นส่วนหนึ่งของ CPU) ทำให้เกิดการขัดจังหวะ การขัดจังหวะถูกจัดการโดยเคอร์เนลซึ่งส่งSIGSEGFAULTสัญญาณ (ดูsignal(2)ตัวอย่าง) ไปยังกระบวนการที่กระทำผิด ตัวจัดการเริ่มต้นสำหรับสัญญาณนี้ทิ้งแกน (ดูcore(5)) และยุติกระบวนการ

เปลือกไม่มีอะไรในมือนี้


3
ดังนั้นไลบรารี C ของคุณเช่นglibcบนเดสก์ท็อปกำหนดสตริงหรือไม่
drewbenn

7
นอกจากนี้ยังเป็นที่น่าสังเกตว่า SIGSEGV สามารถจัดการ / เพิกเฉยได้ ดังนั้นจึงเป็นไปได้ที่จะเขียนโปรแกรมที่ไม่ได้ถูกยกเลิก Java Virtual Machine เป็นหนึ่งในตัวอย่างที่น่าทึ่งที่ใช้ SIGSEGV ภายในเพื่อวัตถุประสงค์ที่แตกต่างกันดังที่กล่าวไว้ที่นี่: stackoverflow.com/questions/3731784/…
Karol Nowak

2
ในทำนองเดียวกันบน Windows,. NET ไม่ต้องกังวลกับการเพิ่มการตรวจสอบตัวชี้โมฆะในกรณีส่วนใหญ่มันแค่จับการละเมิดการเข้าถึง (เทียบเท่ากับ segfaults)
immibis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.