Адреса 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це є причиною, чому це компенсація є більш поширеною.
Марк Херд

1
@MarkHurd Це страшно. Ви справді думаєте, що існує стільки некерованих додатків, які спеціально читають / пишуть .NET-рядки, що це було б головним джерелом порушень доступу?
Луаан

Відповіді:


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.
Кіліан Фот

3
Я задав це питання тому, що 0000000C повертається так часто порівняно з іншими адресами. Чому зсув 12 на величину частіше, ніж зміщення 4, 8 або 16?
Пітер Б

5
Після подальшого розслідування ця відповідь є абсолютно правильною. У моєму джерелі властивість класів "тег" використовується широко (або добре, або погано, з цим я маю справу.) Властивість тегу в моєму випадку є частиною базового класу низького рівня, і він завжди створювався при цьому зміщенні.
Пітер Б

1
Відмінний момент. Можливо, було зафіксовано нульовий випадок вказівника, але нульовий вказівник ++ - це лише звичайна (і в цьому випадку недійсна) адреса, тому він не працює лише при доступі до нього.
Ніл

8
@ Левшенко Так, захист пам’яті зазвичай працює на цілих сторінках, і навіть якщо можна було ввести лише 0, бажано також захистити наступні адреси, оскільки вони, ймовірно, можуть отримати доступ, якщо трапляється арифметика вказівника з нульовим покажчиком (як у Справа ОП).

11

У Windows забороняється відміняти всю першу та останню сторінку , іншими словами, першу або останню 64 Кбайт оперативної пам’яті (діапазони 0x00000000до 0x0000ffffта 0xffff0000до 0xffffffff32-бітної програми).

Це полягає в тому, щоб зафіксувати невизначене поведінку щодо перенаправлення нульового вказівника або індексу в нульовий масив. А розмір сторінки - 64 KiB, тому Windows просто повинен запобігти призначенню першої чи останньої сторінки допустимим діапазоном.

Це не захистить від неініціалізованих покажчиків, які можуть мати будь-яке значення (включаючи дійсні адреси).


7
Windows насправді цього не може зробити. Таблиця сторінок - це структура, визначена і вимагається x86, а невеликі сторінки закріплені на 4 КБ. Він встановлений у камені (точніше, у кремнію). 64 КБ, ймовірно, для зручності.
ElderBug

12
Я вважаю за краще написати в цьому випадку 64 KiB, а не 65 kB, оскільки розмір потужності два.
CodesInChaos

4
Діапазон 64 Кб - ліворуч від версії 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 також підтримують сторінки 1GiB. Наскільки я знаю, Windows завжди містить сторінки в 4 КБ, якщо вони не виділені спеціальними 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. Поширеність цього конкретного значення може потім мати щось спільне з деяким загальним API, який надає класу, який має схожу схему успадковування, або, швидше за все, певний інтерфейс (цілком можливо для деяких класів додатків, таких як ігри DirectX). Можливо, можна відстежити якусь просту поширену причину, як це, але я схильний позбуватися програм, які роблять нульову перенаправлення досить швидко, тому ...


1
Якщо переглядати коментарі, ви зможете значно зменшити здогадки.
Дедуплікатор

@Deduplicator Добре, я вважаю, що керовані рядки .NET використовуються в небезпечному коді з операціями вказівника вручну страшно, і думка, що це буде основною причиною порушень доступу, тим більше. "Так, це повністю безпечно для пам'яті, не хвилюйтесь, ми використовували C #. Ми просто вручну змінюємо пам'ять з C ++, але це безпечно в C #."
Луань
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.