ตัวชี้การทดสอบความถูกต้อง (C / C ++)


92

มีวิธีใดในการตรวจสอบ (โดยทางโปรแกรมแน่นอน) ว่าตัวชี้ที่ระบุ "ถูกต้อง" การตรวจสอบค่า NULL นั้นง่าย แต่สิ่งที่เกี่ยวกับ 0x00001234? เมื่อพยายามหักล้างตัวชี้ชนิดนี้จะมีข้อยกเว้น / ข้อขัดข้องเกิดขึ้น

แนะนำให้ใช้วิธีข้ามแพลตฟอร์ม แต่เฉพาะแพลตฟอร์ม (สำหรับ Windows และ Linux) ก็ใช้ได้เช่นกัน

อัปเดตเพื่อการชี้แจง: ปัญหาไม่ได้อยู่ที่พอยน์เตอร์เก่า / เป็นอิสระ / ไม่ได้กำหนดค่าเริ่มต้น ฉันกำลังใช้ API ที่รับตัวชี้จากผู้โทรแทน (เช่นตัวชี้ไปยังสตริงตัวจัดการไฟล์ ฯลฯ ) ผู้โทรสามารถส่ง (ด้วยจุดประสงค์หรือโดยไม่ได้ตั้งใจ) ค่าที่ไม่ถูกต้องเป็นตัวชี้ ฉันจะป้องกันความผิดพลาดได้อย่างไร



ฉันคิดว่าคำตอบเชิงบวกที่ดีที่สุดสำหรับ linux นั้นได้รับจาก George Carrette หากยังไม่เพียงพอให้พิจารณาสร้างตารางสัญลักษณ์ฟังก์ชันในไลบรารีหรือแม้แต่ระดับอื่นของตารางของไลบรารีที่พร้อมใช้งานด้วยตารางฟังก์ชันของตนเอง จากนั้นตรวจสอบกับตารางที่แน่นอนเหล่านั้น แน่นอนคำตอบเชิงลบเหล่านั้นก็ถูกต้องเช่นกัน: คุณไม่สามารถแน่ใจได้ 100% ว่าตัวชี้ฟังก์ชันถูกต้องหรือไม่เว้นแต่คุณจะกำหนดข้อ จำกัด เพิ่มเติมมากมายให้กับแอปพลิเคชันผู้ใช้
minghua

ข้อกำหนด API ระบุภาระหน้าที่ที่ต้องปฏิบัติตามการนำไปใช้จริงหรือไม่ อย่างไรก็ตามฉันแสร้งทำเป็นไม่ได้รับการสันนิษฐานว่าคุณเป็นทั้งนักพัฒนาและนักออกแบบ ประเด็นของฉันฉันไม่คิดว่า API จะระบุบางสิ่งเช่น "ในกรณีที่มีการส่งตัวชี้ที่ไม่ถูกต้องเป็นอาร์กิวเมนต์ฟังก์ชันต้องจัดการปัญหาและส่งคืนค่า NULL" API มีภาระผูกพันในการให้บริการภายใต้เงื่อนไขการใช้งานที่เหมาะสมไม่ใช่โดยการแฮ็ก อย่างไรก็ตามมันไม่เป็นอันตรายที่จะโง่เขลาเล็กน้อย การใช้ข้อมูลอ้างอิงทำให้กรณีดังกล่าวมีความเสียหายน้อยลง :)
Poniros

คำตอบ:


76

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

คุณไม่สามารถตรวจสอบได้ ไม่มีวิธีใดที่คุณจะตรวจสอบได้ว่าตัวชี้ "ถูกต้อง" หรือไม่ คุณต้องเชื่อมั่นว่าเมื่อมีคนใช้ฟังก์ชันที่ใช้ตัวชี้คนเหล่านั้นจะรู้ว่ากำลังทำอะไรอยู่ หากพวกเขาส่งค่า 0x4211 ให้คุณเป็นค่าตัวชี้คุณต้องเชื่อว่ามันชี้ไปที่ที่อยู่ 0x4211 และถ้าพวกเขา "บังเอิญ" ชนวัตถุแม้ว่าคุณจะใช้ฟังก์ชันระบบปฏิบัติการที่น่ากลัว (IsValidPtr หรืออะไรก็ตาม) คุณก็ยังคงพบข้อผิดพลาดและไม่ล้มเหลวอย่างรวดเร็ว

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


นี่อาจเป็นคำตอบที่ถูกต้อง แต่ฉันคิดว่าฟังก์ชันง่ายๆที่ตรวจสอบตำแหน่งหน่วยความจำ hexspeak ทั่วไปจะมีประโยชน์สำหรับการดีบักทั่วไป ... ตอนนี้ฉันมีตัวชี้ที่บางครั้งชี้ไปที่ 0xfeeefeee และถ้าฉันมีฟังก์ชันง่ายๆที่ฉันทำได้ ใช้พริกไทยยืนยันรอบ ๆ มันจะทำให้ง่ายขึ้นมากในการหาผู้ร้าย ... แก้ไข: แม้ว่าจะไม่ยากที่จะเขียนด้วยตัวเองฉันเดา ..
quant

ปัญหาก็คือรหัส C และ C ++ บางตัวสามารถทำเลขคณิตของตัวชี้บนที่อยู่ที่ไม่ถูกต้องโดยไม่ต้องตรวจสอบ (ตามหลักการของขยะในขยะมูลฝอย) และจะส่งผ่านตัวชี้ "แก้ไขทางคณิตศาสตร์" จากหนึ่งในหลุมนั้น ที่อยู่ที่ไม่รู้จักไม่ถูกต้อง กรณีทั่วไปที่กำลังค้นหาวิธีการจาก vtable ที่ไม่มีอยู่จริงตามที่อยู่ของวัตถุที่ไม่ถูกต้องหรืออยู่ในประเภทที่ไม่ถูกต้องหรือเพียงแค่อ่านเขตข้อมูลจากตัวชี้ไปยังโครงสร้างที่ไม่ได้ชี้ไปที่หนึ่ง
rwong

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

36

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

  1. หลังจากเรียก getpagesize () และปัดตัวชี้ไปที่ขอบเขตของหน้าคุณสามารถเรียก mincore () เพื่อดูว่าเพจถูกต้องหรือไม่และเป็นส่วนหนึ่งของชุดการทำงานของกระบวนการหรือไม่ โปรดทราบว่าสิ่งนี้ต้องใช้ทรัพยากรเคอร์เนลดังนั้นคุณควรเปรียบเทียบและพิจารณาว่าการเรียกใช้ฟังก์ชันนี้เหมาะสมกับ API ของคุณหรือไม่ หาก api ของคุณกำลังจะจัดการกับการขัดจังหวะหรือการอ่านจากพอร์ตอนุกรมไปยังหน่วยความจำคุณควรเรียกสิ่งนี้เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่สามารถคาดเดาได้
  2. หลังจากเรียก stat () เพื่อตรวจสอบว่ามีไดเร็กทอรี / proc / self หรือไม่คุณสามารถเปิดและอ่าน / proc / self / maps เพื่อค้นหาข้อมูลเกี่ยวกับพื้นที่ที่ตัวชี้อยู่ ศึกษา man page สำหรับ proc ระบบไฟล์หลอกข้อมูลกระบวนการ เห็นได้ชัดว่าสิ่งนี้มีราคาค่อนข้างแพง แต่คุณอาจหลีกเลี่ยงการแคชผลลัพธ์ของการแยกวิเคราะห์ลงในอาร์เรย์ซึ่งคุณสามารถค้นหาได้อย่างมีประสิทธิภาพโดยใช้การค้นหาแบบไบนารี พิจารณา / proc / self / smaps ด้วย หาก api ของคุณใช้สำหรับการประมวลผลประสิทธิภาพสูงโปรแกรมจะต้องการทราบเกี่ยวกับ / proc / self / numa ซึ่งจัดทำเอกสารไว้ใน man page สำหรับ numa ซึ่งเป็นสถาปัตยกรรมหน่วยความจำที่ไม่สม่ำเสมอ
  3. การเรียก get_mempolicy (MPOL_F_ADDR) เหมาะสำหรับการทำงาน api การประมวลผลประสิทธิภาพสูงที่มีเธรดการดำเนินการหลายเธรดและคุณกำลังจัดการงานของคุณให้มีความสัมพันธ์กับหน่วยความจำที่ไม่สม่ำเสมอเนื่องจากเกี่ยวข้องกับแกน CPU และทรัพยากรซ็อกเก็ต แน่นอนว่า API ดังกล่าวจะบอกคุณด้วยว่าตัวชี้นั้นถูกต้องหรือไม่

ภายใต้ Microsoft Windows มีฟังก์ชัน QueryWorkingSetEx ที่จัดทำเป็นเอกสารภายใต้ Process Status API (เช่นใน NUMA API) ในฐานะที่เป็นข้อพิสูจน์ในการเขียนโปรแกรม NUMA API ที่ซับซ้อนฟังก์ชันนี้จะช่วยให้คุณสามารถทำงาน "ตัวชี้ทดสอบความถูกต้อง (C / C ++)" แบบง่ายๆได้เช่นกันดังนั้นจึงไม่น่าจะเลิกใช้งานเป็นเวลาอย่างน้อย 15 ปี


14
คำตอบแรกที่ไม่พยายามยึดหลักศีลธรรมเกี่ยวกับคำถามนั้นและตอบได้อย่างสมบูรณ์แบบ บางครั้งผู้คนไม่ทราบว่าเราต้องการวิธีการดีบักแบบนี้เพื่อค้นหาจุดบกพร่องในไลบรารีของบุคคลที่สามหรือในรหัสเดิมเพราะแม้ valgrind จะพบเฉพาะคำแนะนำเฉพาะเมื่อเข้าถึงจริงเท่านั้นไม่ใช่เช่นหากคุณต้องการตรวจสอบตัวชี้เป็นประจำเพื่อความถูกต้อง ในตารางแคชที่ถูกเขียนทับจากที่อื่นในรหัสของคุณ ...
lumpidu

นี่ควรเป็นคำตอบที่ได้รับการยอมรับ ฉันทำมันอย่างง่าย ๆ บนแพลตฟอร์มที่ไม่ใช่ลินุกซ์ โดยพื้นฐานแล้วมันคือการเปิดเผยข้อมูลกระบวนการให้กับกระบวนการนั้นเอง ด้วยแง่มุมนี้ดูเหมือนว่า windows จะทำงานได้ดีกว่า linux โดยการเปิดเผยข้อมูลที่มีความหมายมากกว่าผ่าน API สถานะกระบวนการ
minghua

31

การป้องกันข้อขัดข้องที่เกิดจากผู้โทรส่งตัวชี้ที่ไม่ถูกต้องเป็นวิธีที่ดีในการสร้างข้อบกพร่องแบบเงียบที่หาได้ยาก

โปรแกรมเมอร์ที่ใช้ API ของคุณจะได้รับข้อความที่ชัดเจนว่ารหัสของเขาเป็นของปลอมโดยการหยุดทำงานแทนที่จะซ่อนไว้หรือไม่?


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

28

บน Win32 / 64 มีวิธีการดำเนินการนี้ พยายามอ่านตัวชี้และตรวจจับผลลัพธ์ของ SEH exeception ที่จะล้มเหลว หากไม่โยนแสดงว่าเป็นตัวชี้ที่ถูกต้อง

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

พูดสั้น ๆ ว่าอย่าทำแบบนี้;)

Raymond Chen มีบล็อกโพสต์เกี่ยวกับเรื่องนี้: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx


3
@ ทิมไม่มีวิธีทำใน C ++
JaredPar

6
เป็นเพียง "คำตอบที่ถูกต้อง" หากคุณกำหนด "ตัวชี้ที่ถูกต้อง" เป็น "ไม่ก่อให้เกิดการละเมิดการเข้าถึง / segfault" ฉันต้องการกำหนดเป็น "ชี้ไปยังข้อมูลที่มีความหมายที่จัดสรรเพื่อวัตถุประสงค์ที่คุณจะใช้" ฉันขอยืนยันว่าเป็นคำจำกัดความที่ดีกว่าของความถูกต้องของตัวชี้ ... ;)
jalf

แม้ว่าตัวชี้จะถูกต้องก็ไม่สามารถตรวจสอบได้ด้วยวิธีนี้ ลองนึกถึง thread1 () {.. if (IsValidPtr (p)) * p = 7; ... } thread2 () {sleep (1); ลบ p; ... }
Christopher

2
@ คริสโตเฟอร์จริงมาก. ฉันควรจะพูดว่า "ฉันสามารถอ่านสถานที่แห่งนั้นในความทรงจำได้ในเวลาที่ผ่านไป"
JaredPar

@JaredPar: ข้อเสนอแนะที่ไม่ดีจริงๆ สามารถทริกเกอร์หน้าป้องกันได้ดังนั้นสแต็กจะไม่ถูกขยายในภายหลังหรือสิ่งที่ดีไม่แพ้กัน
Deduplicator

16

AFAIK ไม่มีทาง คุณควรพยายามหลีกเลี่ยงสถานการณ์นี้โดยการตั้งค่าพอยน์เตอร์เป็น NULL เสมอหลังจากปล่อยหน่วยความจำ


4
การตั้งค่าตัวชี้เป็น null จะไม่ให้อะไรเลยนอกจากความรู้สึกปลอดภัยที่ผิดพลาด

นั่นไม่เป็นความจริง โดยเฉพาะอย่างยิ่งใน C ++ คุณสามารถกำหนดได้ว่าจะลบออบเจ็กต์สมาชิกหรือไม่โดยการตรวจสอบค่าว่าง นอกจากนี้โปรดทราบว่าใน C ++ สามารถลบพอยน์เตอร์ที่เป็นโมฆะได้ดังนั้นการลบอ็อบเจ็กต์ในตัวทำลายจึงเป็นที่นิยมโดยไม่มีเงื่อนไข
Ferdinand Beyer

4
int * p = int ใหม่ (0); int * p2 = p; ลบ p; p = โมฆะ; ลบ p2; // crash

1
zabzonk และ ?? สิ่งที่เขาพูดคือคุณสามารถลบตัวชี้ว่างได้ p2 ไม่ใช่ตัวชี้ว่าง แต่เป็นตัวชี้ที่ไม่ถูกต้อง คุณต้องตั้งค่าเป็นโมฆะก่อน
Johannes Schaub - litb

2
หากคุณมีนามแฝงของหน่วยความจำที่ชี้ไปจะมีเพียงนามแฝงเดียวเท่านั้นที่ตั้งค่าเป็น NULL นามแฝงอื่น ๆ จะห้อยอยู่
jdehaan

7

เกี่ยวกับคำตอบเล็กน้อยในหัวข้อนี้:

IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () สำหรับ Windows

คำแนะนำของฉันคืออยู่ห่างจากพวกเขามีคนโพสต์ข้อความนี้ไว้แล้ว: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

โพสต์อื่นในหัวข้อเดียวกันและโดยผู้เขียนคนเดียวกัน (ฉันคิดว่า) คือโพสต์นี้: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx ("IsBadXxxPtr ควรเรียกว่า CrashProgramRandomly ").

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


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

6

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

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

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

  • แยกกระบวนการโดยใช้ซ็อกเก็ต (มักส่งผ่านข้อมูลเป็นข้อความ)

  • แยกกระบวนการโดยใช้หน่วยความจำที่ใช้ร่วมกัน (หากมีข้อมูลจำนวนมากที่จะส่งผ่าน)

  • กระบวนการเดียวกันแยกเธรดผ่านคิวข้อความ (หากมีข้อความสั้นบ่อยๆ)

  • กระบวนการเดียวกันเธรดแยกจากกันข้อมูลที่ส่งผ่านทั้งหมดที่จัดสรรจากพูลหน่วยความจำ

  • กระบวนการเดียวกันผ่านการเรียกโพรซีเดอร์โดยตรง - ข้อมูลที่ส่งผ่านทั้งหมดที่จัดสรรจากพูลหน่วยความจำ

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

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


6

ใน Unix คุณควรจะสามารถใช้ kernel syscall ที่ตรวจสอบตัวชี้และส่งคืนค่า EFAULT เช่น:

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>

bool isPointerBad( void * p )
{
   int fh = open( p, 0, 0 );
   int e = errno;

   if ( -1 == fh && e == EFAULT )
   {
      printf( "bad pointer: %p\n", p );
      return true;
   }
   else if ( fh != -1 )
   {
      close( fh );
   }

   printf( "good pointer: %p\n", p );
   return false;
}

int main()
{
   int good = 4;
   isPointerBad( (void *)3 );
   isPointerBad( &good );
   isPointerBad( "/tmp/blah" );

   return 0;
}

กลับมา:

bad pointer: 0x3
good pointer: 0x7fff375fd49c
good pointer: 0x400793

อาจมี syscall ที่ดีกว่าที่จะใช้ open () [อาจจะเข้าถึง] เนื่องจากมีโอกาสที่อาจนำไปสู่ ​​codepath การสร้างไฟล์จริงและข้อกำหนดในการปิดที่ตามมา


1
นี่คือการแฮ็กที่ยอดเยี่ยม ฉันชอบที่จะเห็นคำแนะนำเกี่ยวกับ syscalls ต่างๆเพื่อตรวจสอบช่วงความจำโดยเฉพาะอย่างยิ่งหากสามารถรับประกันได้ว่าจะไม่มีผลข้างเคียง คุณสามารถเปิดไฟล์ descriptor ไว้เพื่อเขียน / dev / null เพื่อทดสอบว่าบัฟเฟอร์อยู่ในหน่วยความจำที่อ่านได้หรือไม่ แต่อาจมีวิธีแก้ปัญหาที่ง่ายกว่า สิ่งที่ดีที่สุดที่ฉันสามารถหาได้คือ symlink (ptr, "") ซึ่งจะตั้งค่า errno เป็น 14 สำหรับที่อยู่ที่ไม่ถูกต้องหรือ 2 ในที่อยู่ที่ดี แต่การเปลี่ยนแปลงเคอร์เนลอาจสลับลำดับการตรวจสอบได้
เพรสตัน

1
@Preston ใน DB2 ฉันคิดว่าเราเคยใช้การเข้าถึงของ unistd.h () ฉันใช้ open () ด้านบนเพราะมันคลุมเครือน้อยกว่าเล็กน้อย แต่คุณอาจจะคิดถูกที่มี syscalls ที่เป็นไปได้มากมายให้ใช้ Windows เคยมี API การตรวจสอบตัวชี้ที่ชัดเจน แต่กลับกลายเป็นว่าไม่ปลอดภัยสำหรับเธรด (ฉันคิดว่ามันใช้ SEH เพื่อพยายามเขียนแล้วเรียกคืนขอบเขตของช่วงหน่วยความจำ)
Peeter Joot

4

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

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

นี่คือวิธีที่ฉันพบว่าต้องทำ (บน Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/

เพื่อให้ข้อมูลสรุป:

#include <signal.h>

using namespace std;

void terminate(int param)
/// Function executed if a segmentation fault is encountered during the cast to an instance.
{
  cerr << "\nThe function received a corrupted reference - please check the user-supplied  dll.\n";
  cerr << "Terminating program...\n";
  exit(1);
}

...
void MyFunction()
{
    void (*previous_sigsegv_function)(int);
    previous_sigsegv_function = signal(SIGSEGV, terminate);

    <-- insert risky stuff here -->

    signal(SIGSEGV, previous_sigsegv_function);
}

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


อย่าใช้exit()มันหลีกเลี่ยง RAII และอาจทำให้ทรัพยากรรั่วไหล
Sebastian Mach

น่าสนใจ - มีวิธีอื่นในการยุติอย่างเรียบร้อยในสถานการณ์นี้หรือไม่? และคำสั่งทางออกเป็นปัญหาเดียวกับการทำเช่นนี้หรือไม่? ฉันสังเกตเห็นว่าฉันได้รับ "-1" - นั่นเป็นเพราะ 'ทางออก' หรือไม่?
Mike Sadler

อ๊ะฉันรู้ว่านี่เป็นสถานการณ์ที่ยอดเยี่ยมทีเดียว ฉันเพิ่งเห็นexit()และกระดิ่งปลุก C ++ แบบพกพาของฉันก็เริ่มดังขึ้น มันควรจะโอเคในสถานการณ์เฉพาะของ Linux นี้ซึ่งโปรแกรมของคุณจะออกไปยังไงก็ตามขออภัยสำหรับเสียงรบกวน
Sebastian Mach

1
สัญญาณ (2) ไม่สามารถพกพาได้ ใช้ sigaction (2) man 2 signalบน Linux มีย่อหน้าอธิบายว่าทำไม
rptb1

1
ในสถานการณ์เช่นนี้ฉันมักจะเรียกว่า abort (3) แทนที่จะเป็น exit (3) เนื่องจากมีแนวโน้มที่จะสร้าง backtrace การดีบักบางประเภทที่คุณสามารถใช้เพื่อวินิจฉัยปัญหาการชันสูตรพลิกศพ ใน Unixen ส่วนใหญ่การยกเลิก (3) จะดัมพ์คอร์ (หากอนุญาตให้ใช้การถ่ายโอนข้อมูลหลักได้) และบน Windows จะเสนอให้เปิดตัวดีบักเกอร์หากติดตั้ง
rptb1

2

ไม่มีข้อกำหนดใน C ++ เพื่อทดสอบความถูกต้องของตัวชี้เหมือนกรณีทั่วไป เห็นได้ชัดว่าเราสามารถสันนิษฐานได้ว่า NULL (0x00000000) ไม่ดีและคอมไพเลอร์และไลบรารีต่างๆชอบใช้ "ค่าพิเศษ" ที่นี่และที่นั่นเพื่อให้การดีบักง่ายขึ้น (ตัวอย่างเช่นถ้าฉันเคยเห็นตัวชี้แสดงเป็น 0xCECECECE ใน Visual Studio ฉันรู้ ฉันทำอะไรผิด) แต่ความจริงก็คือเนื่องจากตัวชี้เป็นเพียงดัชนีในหน่วยความจำจึงแทบเป็นไปไม่ได้เลยที่จะบอกเพียงแค่ดูที่ตัวชี้ว่าเป็นดัชนี "ถูก" หรือไม่

มีกลเม็ดมากมายที่คุณสามารถทำได้ด้วย dynamic_cast และ RTTI เพื่อให้แน่ใจว่าวัตถุที่ชี้ไปนั้นเป็นประเภทที่คุณต้องการ แต่ทั้งหมดนั้นต้องการให้คุณชี้ไปที่สิ่งที่ถูกต้องตั้งแต่แรก

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


ค่าคงที่ของตัวชี้ที่เป็นโมฆะใน C ++ (หรือ C สำหรับสสารนั้น) แสดงด้วยค่าคงที่อินทิกรัลศูนย์ การใช้งานจำนวนมากใช้เลขฐานสองทั้งหมดเพื่อแสดงถึงสิ่งนี้ แต่ไม่ใช่สิ่งที่ต้องพึ่งพา
David Thornley

2

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


2

การตั้งค่าตัวชี้เป็น NULL ก่อนและหลังใช้เป็นเทคนิคที่ดี สิ่งนี้ทำได้ง่ายใน C ++ หากคุณจัดการพอยน์เตอร์ภายในคลาสเช่น (สตริง):

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void SetText( const char *text);
    char *GetText() const { return MyText; }
    void Clear();

private:
    char * MyText;
};


SomeClass::SomeClass()
{
    MyText = NULL;
}


SomeClass::~SomeClass()
{
    Clear();
}

void SomeClass::Clear()
{
    if (MyText)
        free( MyText);

    MyText = NULL;
}



void SomeClass::Settext( const char *text)
{
    Clear();

    MyText = malloc( strlen(text));

    if (MyText)
        strcpy( MyText, text);
}

คำถามที่อัปเดตทำให้คำตอบของฉันผิดแน่นอน (หรืออย่างน้อยก็คำตอบสำหรับคำถามอื่น) ฉันเห็นด้วยกับคำตอบที่พูดโดยทั่วไปว่าปล่อยให้พวกเขาผิดพลาดหากพวกเขาละเมิด api คุณไม่สามารถหยุดคนที่ตีตัวเองด้วยค้อนได้ ...
Tim Ring

2

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

ทำไม? เพราะอย่างที่คนอื่นบอกว่าไม่มีวิธีมาตรฐานในการรู้ว่าคุณได้รับตัวชี้ที่ถูกต้องหรือตัวชี้ที่ชี้ไปที่ขยะ

แต่บางครั้งคุณไม่มีทางเลือก - API ของคุณต้องยอมรับตัวชี้

ในกรณีเหล่านี้เป็นหน้าที่ของผู้โทรที่จะต้องส่งผ่านตัวชี้ที่ดี NULL อาจได้รับการยอมรับเป็นค่า แต่ไม่ใช่ตัวชี้ไปที่ขยะ

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

// API that does not allow NULL
void PublicApiFunction1(Person* in_person)
{
  assert(in_person != NULL);
  assert(in_person->Invariant());

  // Actual code...
}

// API that allows NULL
void PublicApiFunction2(Person* in_person)
{
  assert(in_person == NULL || in_person->Invariant());

  // Actual code (must keep in mind that in_person may be NULL)
}

re: "ส่งชนิดข้อมูลธรรมดา ... เช่นสตริง" แต่ในสตริง C ++ ส่วนใหญ่มักจะส่งผ่านเป็นตัวชี้ไปยังอักขระ (ถ่าน *) หรือ (const char *) ดังนั้นคุณจึงกลับไปที่ตัวชี้การส่งผ่าน และตัวอย่างของคุณส่ง in_person เป็นข้อมูลอ้างอิงไม่ใช่เป็นตัวชี้ดังนั้นการเปรียบเทียบ (in_person! = NULL) จึงหมายความว่ามีการเปรียบเทียบวัตถุ / ตัวชี้บางอย่างที่กำหนดไว้ในคลาส Person
Jesse Chisholm

@JesseChisholm By string ฉันหมายถึงสตริงคือ std :: string ฉันไม่แนะนำให้ใช้ char * เป็นวิธีในการจัดเก็บสตริงหรือส่งผ่านไปรอบ ๆ อย่าทำอย่างนั้น
Daniel Daranas

@JesseChisholm ด้วยเหตุผลบางอย่างฉันทำผิดพลาดเมื่อฉันตอบคำถามนี้เมื่อห้าปีก่อน เห็นได้ชัดว่ามันไม่สมเหตุสมผลที่จะตรวจสอบว่าบุคคล & เป็นโมฆะ ที่จะไม่รวบรวม ฉันหมายถึงการใช้พอยน์เตอร์ไม่ใช่การอ้างอิง ฉันแก้ไขมันแล้ว
Daniel Daranas

1

อย่างที่คนอื่นพูดคุณไม่สามารถตรวจพบตัวชี้ที่ไม่ถูกต้องได้อย่างน่าเชื่อถือ พิจารณาบางรูปแบบที่ตัวชี้ที่ไม่ถูกต้องอาจใช้:

คุณสามารถมีตัวชี้โมฆะได้ นั่นคือสิ่งที่คุณสามารถตรวจสอบและดำเนินการบางอย่างได้อย่างง่ายดาย

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

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

คุณอาจมีตัวชี้ที่ผิดกฎหมายแบบแบนเช่นตัวชี้ที่มีการจัดตำแหน่งที่ไม่ถูกต้องสำหรับสิ่งที่อ้างถึง

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

สิ่งที่ดีที่สุดที่คุณทำได้คือปล่อยให้เกิดข้อขัดข้องและให้ข้อมูลการวินิจฉัยที่ดี


re: "เอาข้อมูลการวินิจฉัยที่ดีออกมา" ก็มีประโยชน์ เนื่องจากคุณไม่สามารถตรวจสอบความถูกต้องของตัวชี้ได้ข้อมูลที่คุณต้องกังวลจึงมีน้อยมาก "มีข้อยกเว้นเกิดขึ้นที่นี่" อาจเป็นทั้งหมดที่คุณได้รับ call stack ทั้งหมดนั้นดี แต่ต้องการเฟรมเวิร์กที่ดีกว่าที่ไลบรารีรันไทม์ของ C ++ ส่วนใหญ่มีให้
Jesse Chisholm


1

โดยทั่วไปเป็นไปไม่ได้ที่จะทำ นี่เป็นกรณีที่น่ารังเกียจโดยเฉพาะ:

struct Point2d {
    int x;
    int y;
};

struct Point3d {
    int x;
    int y;
    int z;
};

void dump(Point3 *p)
{
    printf("[%d %d %d]\n", p->x, p->y, p->z);
}

Point2d points[2] = { {0, 1}, {2, 3} };
Point3d *p3 = reinterpret_cast<Point3d *>(&points[0]);
dump(p3);

ในหลาย ๆ แพลตฟอร์มสิ่งนี้จะพิมพ์ออกมา:

[0 1 2]

คุณกำลังบังคับให้ระบบรันไทม์ตีความบิตของหน่วยความจำไม่ถูกต้อง แต่ในกรณีนี้จะไม่เกิดปัญหาเพราะบิตทั้งหมดมีความหมาย นี้เป็นส่วนหนึ่งของการออกแบบของภาษา (ดูที่แตกต่างแบบ C กับstruct inaddr, inaddr_in, inaddr_in6) ดังนั้นคุณไม่สามารถเชื่อถือได้ป้องกันมันบนแพลตฟอร์มใด ๆ


1

ไม่น่าเชื่อว่าคุณสามารถอ่านข้อมูลที่ทำให้เข้าใจผิดได้มากแค่ไหนในบทความด้านบน ...

และแม้แต่ในเอกสาร Microsoft msdn IsBadPtr ก็อ้างว่าถูกแบน โอ้ดี - ฉันชอบแอปพลิเคชันที่ใช้งานได้มากกว่าการขัดข้อง แม้ว่าการทำงานในระยะอาจทำงานผิดพลาด (ตราบใดที่ผู้ใช้ปลายทางสามารถดำเนินการต่อกับแอปพลิเคชันได้)

โดย googling ฉันไม่พบตัวอย่างที่มีประโยชน์สำหรับ windows - พบวิธีแก้ปัญหาสำหรับแอพ 32 บิต

http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx? = 2

แต่ฉันต้องรองรับแอพ 64 บิตด้วยดังนั้นวิธีนี้จึงไม่ได้ผลสำหรับฉัน

แต่ฉันได้เก็บเกี่ยวซอร์สโค้ดของไวน์และจัดการปรุงรหัสประเภทเดียวกันซึ่งจะใช้ได้กับแอพ 64 บิตเช่นกัน - แนบรหัสที่นี่:

#include <typeinfo.h>   

typedef void (*v_table_ptr)();   

typedef struct _cpp_object   
{   
    v_table_ptr*    vtable;   
} cpp_object;   



#ifndef _WIN64
typedef struct _rtti_object_locator
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    const type_info *type_descriptor;
    //const rtti_object_hierarchy *type_hierarchy;
} rtti_object_locator;
#else

typedef struct
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    unsigned int type_descriptor;
    unsigned int type_hierarchy;
    unsigned int object_locator;
} rtti_object_locator;  

#endif

/* Get type info from an object (internal) */  
static const rtti_object_locator* RTTI_GetObjectLocator(void* inptr)  
{   
    cpp_object* cppobj = (cpp_object*) inptr;  
    const rtti_object_locator* obj_locator = 0;   

    if (!IsBadReadPtr(cppobj, sizeof(void*)) &&   
        !IsBadReadPtr(cppobj->vtable - 1, sizeof(void*)) &&   
        !IsBadReadPtr((void*)cppobj->vtable[-1], sizeof(rtti_object_locator)))  
    {  
        obj_locator = (rtti_object_locator*) cppobj->vtable[-1];  
    }  

    return obj_locator;  
}  

และรหัสต่อไปนี้สามารถตรวจจับได้ว่าตัวชี้ถูกต้องหรือไม่คุณอาจต้องเพิ่มการตรวจสอบ NULL:

    CTest* t = new CTest();
    //t = (CTest*) 0;
    //t = (CTest*) 0x12345678;

    const rtti_object_locator* ptr = RTTI_GetObjectLocator(t);  

#ifdef _WIN64
    char *base = ptr->signature == 0 ? (char*)RtlPcToFileHeader((void*)ptr, (void**)&base) : (char*)ptr - ptr->object_locator;
    const type_info *td = (const type_info*)(base + ptr->type_descriptor);
#else
    const type_info *td = ptr->type_descriptor;
#endif
    const char* n =td->name();

สิ่งนี้ได้รับชื่อคลาสจากตัวชี้ - ฉันคิดว่ามันน่าจะเพียงพอสำหรับความต้องการของคุณ

สิ่งหนึ่งที่ฉันยังคงกลัวคือประสิทธิภาพของการตรวจสอบตัวชี้ - ในโค้ดโค้ดด้านบนมีการเรียก API 3-4 ครั้งแล้ว - อาจใช้งานมากเกินไปสำหรับแอปพลิเคชันที่มีความสำคัญต่อเวลา

จะเป็นการดีถ้ามีคนสามารถวัดค่าเหนือศีรษะของการตรวจสอบตัวชี้เทียบกับการเรียก C # / c ++ ที่มีการจัดการ


1

แน่นอนว่าบางสิ่งสามารถทำได้ในบางโอกาสเช่นหากคุณต้องการตรวจสอบว่าสตริงตัวชี้สตริงถูกต้องหรือไม่โดยใช้การเขียน (fd, buf, szie) syscall สามารถช่วยคุณทำเวทมนตร์ได้: ให้ fd เป็นตัวอธิบายไฟล์ชั่วคราว ไฟล์ที่คุณสร้างเพื่อทดสอบและ buf ชี้ไปที่สตริงที่คุณกำลังทดสอบหากตัวชี้ไม่ถูกต้อง write () จะส่งคืน -1 และ errno ตั้งค่าเป็น EFAULT ซึ่งบ่งชี้ว่า buf อยู่นอกพื้นที่ที่อยู่ที่คุณสามารถเข้าถึงได้


1

ต่อไปนี้ใช้งานได้ใน Windows (มีคนแนะนำมาก่อน):

 static void copy(void * target, const void* source, int size)
 {
     __try
     {
         CopyMemory(target, source, size);
     }
     __except(EXCEPTION_EXECUTE_HANDLER)
     {
         doSomething(--whatever--);
     }
 }

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


1

ใน Windows ฉันใช้รหัสนี้:

void * G_pPointer = NULL;
const char * G_szPointerName = NULL;
void CheckPointerIternal()
{
    char cTest = *((char *)G_pPointer);
}
bool CheckPointerIternalExt()
{
    bool bRet = false;

    __try
    {
        CheckPointerIternal();
        bRet = true;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

    return  bRet;
}
void CheckPointer(void * A_pPointer, const char * A_szPointerName)
{
    G_pPointer = A_pPointer;
    G_szPointerName = A_szPointerName;
    if (!CheckPointerIternalExt())
        throw std::runtime_error("Invalid pointer " + std::string(G_szPointerName) + "!");
}

การใช้งาน:

unsigned long * pTest = (unsigned long *) 0x12345;
CheckPointer(pTest, "pTest"); //throws exception

0

IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () สำหรับ Windows
สิ่งเหล่านี้ใช้เวลาตามสัดส่วนกับความยาวของบล็อกดังนั้นเพื่อความสมบูรณ์ของการตรวจสอบฉันเพียงแค่ตรวจสอบที่อยู่เริ่มต้น


3
คุณควรหลีกเลี่ยงวิธีการเหล่านี้เพราะไม่ได้ผล blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
JaredPar

บางครั้งพวกเขาอาจจะไม่สามารถแก้ไขได้: stackoverflow.com/questions/496034/…
ChrisW

0

ฉันเคยเห็นไลบรารีต่างๆใช้วิธีการบางอย่างเพื่อตรวจสอบหน่วยความจำที่ไม่ได้อ้างอิงและอื่น ๆ ฉันเชื่อว่าพวกเขาเพียงแค่ "ลบล้าง" วิธีการจัดสรรหน่วยความจำและการจัดสรรตำแหน่ง (malloc / free) ซึ่งมีตรรกะบางอย่างที่คอยติดตามพอยน์เตอร์ ฉันคิดว่านี่เป็นการใช้งานมากเกินไปสำหรับกรณีการใช้งานของคุณ แต่มันจะเป็นวิธีหนึ่งที่จะทำ


นั่นไม่ได้ช่วยสำหรับวัตถุที่จัดสรรแบบสแต็ก แต่น่าเสียดาย
ทอม

0

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

  1. คุณยังต้องการวิธีตรวจสอบว่ามีการจัดสรรตัวชี้บน stack หรือไม่ ()

  2. คุณจะต้องกำหนดตัวชี้ที่ 'ถูกต้อง':

ก) หน่วยความจำบนที่อยู่นั้นถูกจัดสรร

b) หน่วยความจำที่อยู่นั้นคือที่อยู่เริ่มต้นของวัตถุ (เช่นที่อยู่ไม่ได้อยู่ตรงกลางของอาร์เรย์ขนาดใหญ่)

c) หน่วยความจำที่อยู่นั้นคือที่อยู่เริ่มต้นของวัตถุประเภทที่คาดหวัง

บรรทัดล่าง : แนวทางที่เป็นปัญหาไม่ใช่วิธี C ++ คุณต้องกำหนดกฎบางอย่างเพื่อให้แน่ใจว่าฟังก์ชันได้รับตัวชี้ที่ถูกต้อง



0

ภาคผนวกของคำตอบที่ได้รับการยอมรับ:

สมมติว่าตัวชี้ของคุณสามารถเก็บค่าได้เพียงสามค่า - 0, 1 และ -1 โดยที่ 1 หมายถึงตัวชี้ที่ถูกต้อง, -1 ตัวชี้ที่ไม่ถูกต้องและอีก 0 ค่าที่ไม่ถูกต้อง อะไรคือความน่าจะเป็นที่ตัวชี้ของคุณเป็นโมฆะค่าทั้งหมดมีโอกาสเท่ากัน? 1/3. ตอนนี้เอากรณีที่ถูกต้องออกดังนั้นสำหรับทุกกรณีที่ไม่ถูกต้องคุณมีอัตราส่วน 50:50 เพื่อตรวจจับข้อผิดพลาดทั้งหมด ดูดีใช่มั้ย? ปรับขนาดสำหรับตัวชี้ 4 ไบต์ มีค่าที่เป็นไปได้ 2 ^ 32 หรือ 4294967294 ในจำนวนนี้มีเพียงค่าเดียวเท่านั้นที่ถูกต้องค่าหนึ่งเป็นค่าว่างและคุณยังเหลือกรณีที่ไม่ถูกต้องอีก 4294967292 คำนวณใหม่: คุณมีการทดสอบ 1 ในกรณีที่ไม่ถูกต้อง (4294967292+ 1) ความน่าจะเป็น 2.xe-10 หรือ 0 สำหรับวัตถุประสงค์ในทางปฏิบัติส่วนใหญ่ นั่นคือความไร้ประโยชน์ของการตรวจสอบ NULL


0

คุณรู้หรือไม่ว่าไดรเวอร์ใหม่ (อย่างน้อยก็บน Linux) ที่มีความสามารถนี้อาจจะไม่ยากที่จะเขียน

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


0

คุณควรหลีกเลี่ยงวิธีการเหล่านี้เพราะไม่ได้ผล blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx - JaredPar 15 ก.พ. 52 เวลา 16:02 น.

หากไม่ได้ผล - การอัปเดต windows ครั้งต่อไปจะแก้ไขได้หรือไม่ หากไม่ทำงานในระดับแนวคิด - ฟังก์ชันอาจถูกลบออกจาก windows api อย่างสมบูรณ์

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

แต่คุณไม่ควรอ้างว่าพวกเขาไม่ทำงานเนื่องจากบางบล็อก ในแอปพลิเคชันทดสอบของฉันฉันได้ตรวจสอบแล้วว่าทำงานได้ดี


0

ลิงก์เหล่านี้อาจเป็นประโยชน์

_CrtIsValidPointer ตรวจสอบว่าช่วงหน่วยความจำที่ระบุถูกต้องสำหรับการอ่านและเขียน (เวอร์ชันดีบักเท่านั้น) http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx

_CrtCheckMemory ยืนยันความสมบูรณ์ของบล็อกหน่วยความจำที่จัดสรรในฮีปดีบัก (เวอร์ชันดีบักเท่านั้น) http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx

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