Является ли адрес 0000000C специальным адресом?

32

При программировании иногда что-то ломается. Вы ошиблись, и ваша программа пытается прочитать не с того адреса.

Одна вещь, которая выделяет меня, что часто эти исключения похожи на:

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

Теперь я вижу много журналов ошибок, и что мне выделяется: 0000000C. Это «специальный» адрес? Я вижу другие нарушения доступа с плохим чтением, но адреса кажутся случайными, но этот постоянно возвращается в совершенно разных ситуациях.

Питер Б
источник
1
Я также заметил , что 0000000Cесть способ более распространен , чем 00000008, но ни один из ответов не кажется, что адрес на всех: /
мычат 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
После дальнейшего расследования этот ответ является полностью правильным. В моем источнике широко используется свойство "tag" классов (хорошо или плохо, я должен иметь с ним дело). Свойство tag в моем случае является частью низкоуровневого базового класса, и оно всегда создавалось с этим смещением.
Питер Б
1
Отличный момент. Возможно, случай нулевого указателя был рассмотрен, но нулевой указатель ++ является обычным (и в данном случае недействительным) адресом, поэтому он завершается ошибкой только при обращении к нему.
Нил
8
@Leushenko Да, защита памяти обычно работает на целых страницах, и даже если можно было перехватить только 0, предпочтительно также защитить следующие адреса, потому что они могут быть доступны, если происходит арифметика указателя с нулевым указателем (как в Дело ОП).
11

В Windows запрещено разыменовывать всю первую и последнюю страницы , другими словами, первые или последние 64 КБ памяти процесса (диапазоны 0x00000000до 0x0000ffffи 0xffff0000до 0xffffffffв 32-битном приложении).

Это необходимо для ловушки неопределенного поведения разыменования нулевого указателя или индекса в нулевой массив. И размер страницы составляет 64 КБ, поэтому Windows просто нужно предотвратить допустимый диапазон для первой или последней страницы.

Это не защитит от неинициализированных указателей, которые могут иметь любое значение (включая действительные адреса).

чокнутый урод
источник
7
Windows не может этого сделать. Таблица страниц - это структура, определенная и требуемая для x86, а небольшие страницы имеют размер 4 КБ. Он установлен в камне (точнее, в кремнии). 64 КБ, вероятно, для удобства.
ElderBug
12
Я бы предпочел написать 64 КиБ вместо 65 КБ в этом случае, поскольку размер степени двойки важен.
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.
суперкат
2
@MooingDuck Да, действительно, именно поэтому я уточнил маленькие страницы. Большинство процессоров x64 также поддерживают страницы объемом 1 ГБ. Насколько я знаю, в 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указателя, вы получите нарушение доступа к его Смещение ВМТ. Распространенность этого конкретного значения может тогда иметь какое-то отношение к какому-то общему API, предоставляющему класс с аналогичным шаблоном наследования или, скорее, конкретный интерфейс (что вполне возможно для некоторых классов приложений, таких как игры DirectX). Можно было бы отследить какую-то простую общую причину, как это, но я склонен избавляться от приложений, которые довольно быстро разыменовывают нулевую разыменование, так что ...

Luaan
источник
1
Если вы просматриваете комментарии, вы можете значительно уменьшить догадки.
Дедупликатор
@Deduplicator Ну, я нахожу идею, что управляемые строки .NET используются в небезопасном коде с ручными операциями с указателями, и мысль о том, что это будет основной причиной нарушений доступа, тем более. «Да, это абсолютно безопасно для памяти, не волнуйтесь, мы использовали C #. Мы просто вручную модифицируем память из C ++, но это безопасно в C #».
Луаан