ที่อยู่ 0000000C เป็นที่อยู่พิเศษหรือไม่


32

เมื่อการเขียนโปรแกรมบางครั้งสิ่งที่แตก คุณทำผิดพลาดและโปรแกรมของคุณพยายามอ่านจากที่อยู่ผิด

สิ่งหนึ่งที่โดดเด่นสำหรับฉันที่มักจะมีข้อยกเว้นเหล่านั้น:

Access violation at address 012D37BC in module 'myprog.exe'. Read of address 0000000C.

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


1
ฉันยังสังเกตเห็นว่า0000000Cเป็นวิธีที่พบบ่อยกว่า00000008แต่ไม่มีคำตอบใดที่ดูเหมือนว่าจะอยู่ที่: /
Mooing Duck

2
บางทีนั่นอาจSystem.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringDataเป็น12=0x0Cเหตุผลว่าทำไมการชดเชยนี้เป็นเรื่องธรรมดา
Mark Hurd

1
@ MarkHurd นั่นน่ากลัว คุณคิดว่ามีแอปพลิเคชั่นที่ไม่ได้รับการจัดการจำนวนมากที่มีวัตถุประสงค์เพื่ออ่าน / เขียนสตริง. NET ซึ่งจะเป็นแหล่งสำคัญของการละเมิดการเข้าถึงหรือไม่?
Luaan

คำตอบ:


57

00000000เป็นที่อยู่พิเศษ (ตัวชี้โมฆะ) 0000000Cเป็นสิ่งที่คุณจะได้รับเมื่อคุณเพิ่มออฟเซ็ต 12 ลงในพอยน์เตอร์โมฆะเพราะคนที่พยายามจะให้zสมาชิกของโครงสร้างเหมือนกับด้านล่างผ่านพอยน์เตอร์ซึ่งจริง ๆ แล้วโมฆะ

struct Foo {
    int w, x, y; // or anything else that takes 12 bytes including padding
    // such as: uint64_t w; char x;
    // or: void *w; char padding[8];
    // all assuming an ordinary 32 bit x86 system
    int z;
}

29
หรืออาจเป็นเพราะค่าอินทิกรัลขนาดเล็กบางค่าถูกอ่านผิดโดยบังเอิญราวกับว่ามันเป็นตัวชี้ ค่าขนาดเล็กนั้นพบได้ทั่วไปมากกว่าค่าขนาดใหญ่ดังนั้นจึงมีแนวโน้มที่จะสร้างที่อยู่ที่ผิดกฎหมายเช่น 0X0000000C แทนที่จะเป็นเช่น 0x43FCC893
Kilian Foth

3
เหตุผลที่ฉันถามคำถามนี้คือเพราะ 0000000C กลับมาบ่อยครั้งเมื่อเทียบกับที่อยู่อื่น ทำไมการชดเชย 12 ขนาดทั่วไปจึงชดเชย 4, 8 หรือ 16
Pieter B

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

1
จุดที่ดีเยี่ยม บางทีกรณีตัวชี้โมฆะได้รับการคุ้มครอง แต่ตัวชี้โมฆะ ++ เป็นเพียงที่อยู่ปกติ (และในกรณีนี้ไม่ถูกต้อง) ที่อยู่ดังนั้นจึงล้มเหลวเฉพาะเมื่อเข้าถึง
Neil

8
@ Leushenko ใช่การป้องกันหน่วยความจำมักจะทำงานได้ทั้งหน้าและแม้ว่าจะเป็นไปได้ที่จะจับได้เพียง 0 แต่ก็ยังดีกว่าที่จะปกป้องที่อยู่ต่อไปนี้เพราะพวกมันมีแนวโน้มที่จะเข้าถึงได้ กรณีของ OP)

11

ใน Windows มันเป็นกฎหมายที่จะ dereference หน้าแรกและครั้งสุดท้ายทั้งในคำอื่น ๆ ครั้งแรกหรือครั้งสุดท้าย 64 กิโลไบท์ของหน่วยความจำกระบวนการ (ช่วง0x00000000ไป0x0000ffffและ0xffff0000ไป0xffffffffในโปรแกรมประยุกต์แบบ 32 บิต)

นี่คือการดักจับพฤติกรรมที่ไม่ได้กำหนดของ dereferencing ตัวชี้โมฆะหรือดัชนีลงในอาร์เรย์โมฆะ และขนาดหน้าคือ 64 KiB ดังนั้น Windows จะต้องป้องกันไม่ให้หน้าแรกหรือหน้าสุดท้ายถูกกำหนดช่วงที่ถูกต้อง

วิธีนี้จะไม่ป้องกันตัวชี้เริ่มต้นที่อาจมีค่าใด ๆ (รวมถึงที่อยู่ที่ถูกต้อง)


7
Windows ทำเช่นนั้นไม่ได้จริงๆ ตารางหน้าเป็นโครงสร้างที่กำหนดและต้องการโดย x86 และหน้าขนาดเล็กได้รับการแก้ไขที่ 4KB มันตั้งอยู่ในหิน (แม่นยำยิ่งขึ้นในซิลิคอน) 64KB น่าจะเป็นเพื่อความสะดวก
ElderBug

12
ฉันควรเขียน 64 KiB แทน 65 kB ในกรณีนี้เนื่องจากขนาด power-of-two มีความเกี่ยวข้อง
CodesInChaos

4
ช่วง 64KB เป็นส่วนที่เหลือจาก Aplha รุ่น NT และไม่ใช่ขนาดหน้ากระดาษ แต่เป็นการจัดสรรแบบละเอียด blogs.msdn.com/b/oldnewthing/archive/2003/10/08/55239.aspx
shf301

3
@CodesInChaos: ในขณะที่ตัวพิมพ์ใหญ่ "M", "G" และ "T" นั้นคลุมเครือฉันไม่เห็นเหตุผลที่จะเลิกใช้ "k" สำหรับ 10 ^ 3 และ "K" เป็นเวลา 2 ^ 10
supercat

2
@MooingDuck ใช่แน่นอนนั่นเป็นเหตุผลว่าทำไมฉันจึงกำหนดหน้าเล็ก ๆ x64 CPU ส่วนใหญ่รองรับหน้า 1GiB เท่าที่ฉันรู้ Windows จะใช้หน้าเว็บที่มีหน้า 4KB ยกเว้นว่ามีการจัดสรรด้วย API พิเศษ
ElderBug

2

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

0x00 - VMT pointer for parent
0x04 - Field 1 in parent
0x08 - VMT pointer for child
0x0C - Field 1 in child

นี่เป็นสถานการณ์ทั่วไปหรือแม้กระทั่งปิด ฉันไม่แน่ใจ. อย่างไรก็ตามโปรดทราบว่าในแอปพลิเคชัน 64 บิตสิ่งนี้สามารถเปลี่ยนไปเป็น0x0Cค่าที่น่าสนใจยิ่งขึ้น:

0x00 - VMT parent
0x08 - Field 1 parent
0x0C - VMT child
0x14 - Field 2 child

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


1
หากคุณดูความคิดเห็นต่างๆคุณสามารถลดการคาดเดาได้มาก
Deduplicator

@Dupuplicator ดีฉันคิดว่ามีการใช้สตริง. NET ที่มีการจัดการในโค้ดที่ไม่ปลอดภัยพร้อมกับตัวชี้การดำเนินการด้วยตนเองที่น่ากลัวและความคิดที่ว่านี่จะเป็นสาเหตุสำคัญของการละเมิดการเข้าถึงมากยิ่งขึ้น "ใช่นี่ปลอดภัยโดยสิ้นเชิงไม่ต้องกังวลเราใช้ C # เราเพิ่งแก้ไขหน่วยความจำด้วยตนเองจาก C ++ แต่มันปลอดภัยใน C #"
Luaan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.