Эквивалент 0xDEADBEEF для 64-битной разработки?

Для разработки C++ для 32-разрядных систем (будь то Linux, Mac OS или Windows, PowerPC или x86) Я инициализировал указатели, которые в противном случае были бы неопределенными (например, они не могут сразу получить правильное значение) вот так:

int *pInt = reinterpret_cast<int *>(0xDEADBEEF);

(Чтобы не печатать и не быть DRY, правая часть обычно должна быть константой, например, BAD_PTR.)

Если pInt разыменовывается до того, как он получит правильное значение, то он немедленно выйдет из строя на большинстве систем (вместо сбоя намного позже, когда какая-то часть памяти будет перезаписана или войдет в очень длинный цикл).

Конечно, поведение зависит от базового оборудования (получение 4-байтового целого числа по нечетному адресу 0xDEADBEEF из пользовательского процесса может быть вполне допустимым), но сбой был на 100% надежным для всех систем, для которых я разработал до сих пор ( Mac OS 68xxx, Mac OS PowerPC, Linux Redhat Pentium, Windows GUI Pentium, консоль Windows Pentium). Например, на PowerPC запрещено (ошибка шины) извлекать 4-байтовое целое число из нечетного адреса.

Какое хорошее значение для этого в 64-битных системах?


person Peter Mortensen    schedule 11.08.2009    source источник
comment
Я видел системы, в которых первый 1 КБ памяти определялся как недопустимый. Поэтому, если указатель NULL будет разыменован, процесс быстро умрет. 0xDEADBEEF может быть допустимым местоположением.   -  person Robert    schedule 11.08.2009
comment
@Robert: я видел системы, в которых вектор прерывания начинается с 0, поэтому разыменование указателя функции NULL просто перезагружает систему (но не повторно инициализирует стеки и т. д.). Любой адрес может быть допустимым местом для чего-либо.   -  person bk1e    schedule 11.08.2009
comment
@bk1e: IVT НИКОГДА не должен быть доступен из пользовательского режима. Но вы правы в том, что нет никаких причин, по которым адрес 0 не может быть сопоставлен. В Linux легко сопоставить адрес 0, изменив параметр в ядре. В любом случае, урок, извлеченный здесь, состоит в том, чтобы не использовать глупые шаблоны для пометки указателей как недействительные, использовать null или отдельный флаг в структуре. Предполагать, что он просто выйдет из строя, совершенно безответственно и невежественно, если вам повезет, вы получите только segfault, вполне вероятно, что это может привести к удаленному выполнению кода, и уже было много, много, много раз в прошлом.   -  person L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o&#x    schedule 03.08.2010
comment
Вот почему C следует запретить. Никто не знает, как им пользоваться. С другой стороны, вы можете просто убедиться, что ваша программа сопоставляет 0xdeadbeef с защитной страницей перед запуском, например, но не во всех ОС есть эквивалент, поэтому вы все равно можете получить уязвимость в некоторых ОС. Кроме того, запуск самого segfault не обязательно останавливает удаленное выполнение кода.   -  person L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o&#x    schedule 03.08.2010
comment
@Longpoke: я согласен, но не у каждого процессора есть MMU. В некоторых встроенных системах NULL указывает на что-то важное, и вы ничего не можете с этим поделать. :)   -  person bk1e    schedule 03.08.2010
comment
@ bk1e, но стандарт C гарантирует, что NULL (и, соответственно, 0, преобразованный в указатель) будет недопустимым указателем, который вы не можете разыменовать.   -  person CMircea    schedule 27.04.2011
comment
@iconiK: Что значит не может разыменовываться? Вы наверняка можете разыменовать NULL, но результатом будет неопределенное поведение (согласно разделу 6.5.3.2 C99). Нулевой указатель гарантированно будет сравниваться с любым объектом или функцией (в соответствии с разделом 6.3.2.3 C99), поэтому, если есть объект C или функция C по адресу 0, компилятор должен преобразовать (void*)0 в точку, отличную от адреса 0. Однако таблица векторов прерываний не является объектом C или функцией C, поэтому я не думаю, что компиляторы обязаны гарантировать, что NULL не указывает на таблицу векторов прерываний.   -  person bk1e    schedule 27.04.2011
comment
@ bk1, ну, если вы явно не хотите неопределенного поведения, вам не следует разыменовывать NULL, поэтому его следует использовать для инициализированных, но неиспользуемых указателей. Я не говорил, что это адрес 0; стандарт C гарантирует, что 0, преобразованный в указатель, приводит к указателю NULL. Независимо от того, изменяет ли реализация это поведение или определяет разыменование точки NULL, выходит за рамки стандартного C (или C++, если на то пошло).   -  person CMircea    schedule 27.04.2011
comment
@iconiK: Вернемся к исходной теме: вы можете случайно разыменовать 0xDEADBEEF, что может вызвать или не вызвать сбой. Вы можете случайно разыменовать NULL, который может быть или не быть адресом 0, и который может или не может вызвать сбой. Многие платформы используют аппаратные средства защиты памяти для обнаружения разыменования недопустимого указателя, но спецификация C не требует этого. Таким образом, инициализация указателей значением NULL лучше, чем их инициализация значением 0xDEADBEEF, но не гарантируется обнаружение случайных разыменований на всех платформах.   -  person bk1e    schedule 02.05.2011


Ответы (12)


Как правило, не имеет значения точно какой шаблон вы пишете, важно, что вы можете идентифицировать шаблон, чтобы определить, где возникают проблемы. Так уж получилось, что в ядре Linux они часто выбираются так, чтобы их можно было перехватить, если адреса разыменовываются.

Взгляните на ядро ​​Linux по адресу include/linux/poison.h. Этот файл содержит различные значения отравления для многих различных подсистем ядра. Не существует одного подходящего значения яда.

Кроме того, вы можете проверить включаемые файлы для каждой архитектуры в дереве исходных текстов ядра Linux для получения информации о том, что используется в конкретных архитектурах.

person Noah Watkins    schedule 02.08.2010

Согласно Википедии, BADC0FFEE0DDF00D используется в 64-разрядных системах IBM RS/6000 для обозначения неинициализированных Регистры процессора.

person Thomas Owens    schedule 11.08.2009

Большинство современных 64-разрядных систем позволяют использовать только самые младшие 248252 бита адресного пространства; старшие биты адреса должны быть нулевыми. Некоторые чипы (например, amd64) также позволяют использовать самый высокий 248252. Адреса за пределами этих диапазонов никогда не могут быть сопоставлены с доступной памятью; оборудование просто не позволит.

Поэтому я рекомендую вам использовать значение, близкое к 263, которое далеко не соответствует ни одному из возможных используемых пробелов. Если первые четыре шестнадцатеричных цифры равны 7ff8, значение будет NaN с плавающей запятой двойной точности, что удобно. Итак, моя рекомендуемая симпатичная шестнадцатеричная фраза — 0x7FF8BADFBADFBADF.

Кстати, вы действительно не хотите использовать значение, близкое к 0, потому что это затрудняет определение разыменования offset NULL при доступе к члену структуры, например, при разыменовании яда шаблон.

person zwol    schedule 02.08.2010
comment
Что «самая современная 64-битная система позволяет использовать только младшие 48-52 бита адресного пространства» и что «аппаратное обеспечение просто не позволит этого». - person nalply; 19.12.2010
comment
В руководстве по архитектуре для каждого интересующего ЦП указано, сколько бит адресного пространства можно использовать. Например, в Руководстве программиста по архитектуре AMD64, том 2 (системное программирование) говорится в разделе 1.1.3 говорится, что не все 64 бита адресного пространства обязательно пригодны для использования, а неиспользуемые старшие биты должны быть полностью нулевыми или все единицами, а затем в разделе 5.3.3 показано, что текущие реализации позволяют использовать только младшие биты. 48 бит. - person zwol; 19.12.2010
comment
Я не знаю ни одного обзора, в котором перечислены процессоры с 64-битной адресацией и диапазоны их используемых адресов, но единственное исключение из моего первоначального утверждения, которое я могу найти, это PowerPC в режиме таблицы хешированных страниц. В таблицах страниц с древовидной структурой каждые дополнительные шесть-восемь бит виртуального адресного пространства добавляют уровень дерева и замедляют промахи TLB, поэтому разработчики архитектуры неохотно выделяют больше полезного пространства, чем на самом деле нужно людям, которое сейчас составляет 48-52 бита. (за исключением экзотических операционных систем с единым адресным пространством). - person zwol; 19.12.2010

Я предполагаю, что вы уже обесценили NULL (т.е. 0 без приведения типов). Это определенно самый безопасный выбор, так как теоретически допустимый указатель может указывать на адрес памяти 0xDEADBEEF (или любой другой ненулевой адрес памяти).

person Michael Koval    schedule 11.08.2009
comment
Я думаю, что один из моментов использования 0xDEADBEEF или какого-либо другого известного плохого значения заключается в том, что при отладке результирующего файла ядра (эквивалент руды) совершенно очевидно, что вы имеете дело с неинициализированным указателем, а не с мусором/недействительными/неправильными данными. Я бы предположил, что шансы на то, что 0xDEADBEEF на самом деле указывает на действительное место в памяти, достаточно малы, чтобы их преимущества значительно перевешивались. - person Rodyland; 11.08.2009
comment
Для указателей всегда следует использовать NULL/0. 0xDEADBEEF и другие шестнадцатеричные значения используются для заполнения недавно выделенных, но не инициализированных, недавно освобожденных, к которым больше не следует обращаться, областей памяти, обрабатываемых функциями диспетчера кучи (это также может быть правдой). для стека)... - person Malkocoglu; 11.08.2009
comment
Что вы никогда не видели отображение памяти в NULL? - person Joshua; 18.08.2009
comment
Никто никогда не видел отображения памяти в NULL, поскольку компилятор гарантирует, что NULL-ячейка памяти никогда не используется: ... нулевой указатель определенно никуда не указывает; это не адрес какого-либо объекта или функции. См.: c-faq.com/null/null1.html. - person Michael Koval; 20.08.2009
comment
@ Майк Коваль, ну это просто неправда. Я сопоставил память с NULL. На самом деле это не так уж и редкость, быстрый поиск в Google эксплойта разыменования нулевого указателя должен дать вам множество примеров. Похоже, что в нем, вероятно, есть некоторая информация высокого уровня: блог. cr0.org/2009/06/bypassing-linux-null-pointer.html - person mrduclaw; 14.10.2009
comment
@Mike: Это правда, однако спецификация C не определяет поведение попытки разыменования указателя NULL. Таким образом, платформа может (хотя и нетипично) принимать чтение и запись в *NULL. - person ephemient; 18.01.2010
comment
Указатель NULL/0 не обязательно представлен в памяти как 0-байт. Хотя я не знаю ни одной системы, в которой было бы что-то другое. - person Thomas; 03.08.2010
comment
@mrduclaw Так что уточните это немного - пока вы остаетесь в пространстве пользователя, вам гарантируется, что NULL абсолютно недействителен. - person Jakob Borg; 03.08.2010
comment
Просто чтобы вы знали, mmap() будет отображать NULL, если это явно указано (передается флаг для точного адреса с NULL в качестве адреса). а в gcc компиляция с -fno-delete-null-pointer-checks говорит компилятору не предполагать, что разыменование NULL недопустимо. - person Joshua; 03.08.2010

0xDEADBEEFBAADF00D может сработать.

person Joshua    schedule 11.08.2009

У меня нет для вас хорошего выбора, но вот список шестнадцатеричных слов, которые вы можете использовать, чтобы сделать вашу фразу.

person Ned Batchelder    schedule 11.08.2009

Я думаю, двух 0xDEADBEEF должно быть достаточно.

person LostMohican    schedule 02.08.2010

Я вижу несколько ответов, утверждающих, что NULL — хороший выбор, но я не согласен.

NULL часто используется как допустимое значение, возвращаемое функциями. Это указывает на возврат ошибки или неизвестное значение. Это другое значение, чем «неинициализированный указатель».

Использование отладчика в коде и наблюдение NULL оставляет две возможности: указатель никогда не был инициализирован или ему не удалось выделить память.

Установка неинициализированного указателя в 0xDEADBEEF или 64-битный эквивалент означает, что указатель NULL указывает на преднамеренное значение.

person Zan Lynx    schedule 11.08.2009
comment
Это верно только в том случае, если вам нужен неинициализированный указатель. И вот здесь я не согласен: если вы не делаете встроенные вещи или другие вещи, которые настолько чувствительны к производительности, что инициализация указателя на NULL невозможна в очень редких случаях, когда вам нужен указатель без в состоянии инициализировать его чем-то разумным, инициализация его NULL избавит вас от проблемы. Я не думаю, что мне нужен неинициализированный указатель в годах. - person sbi; 11.08.2009
comment
Значения, которые никогда не были инициализированы, очень полезны при отладке — таким образом не явно инициализируется какая-либо заданная ячейка памяти, а заполняют ею целые блоки памяти (в malloc() и снова с другим шаблоном). , на free()), чтобы обнаружить места, где программа должна инициализировать переменную, но не сделала этого. - person zwol; 03.08.2010

Конечно, это зависит от ОС и среды. Я не думаю, что 0xDEADBEEF обязательно является плохим указателем в произвольной 32-битной системе.

На самом деле, любая современная ОС должна защищать доступ к первым нескольким страницам памяти процесса, поэтому значение NULL должно быть хорошим недопустимым значением указателя. Достаточно удобно, что это уже предопределено для вас.

person Mark Bessey    schedule 11.08.2009
comment
Согласитесь, что NULL — очень полезный выбор. 0xDEADBEEF не является недопустимым указателем, но это не только крайне маловероятно на практике, но и не выровнено по 4- или 2-байтовой границе. За исключением char* или, возможно, bool*, это не произойдет естественным образом, если у вас серьезно не испорчена семантика памяти. - person ChrisV; 11.08.2009
comment
В любом случае, он почти всегда находится в области отображения памяти ядра. - person Joshua; 18.08.2009

0x42 может работать как на 32-битной, так и на 64-битной версии? (Это все равно должно вызвать сбой, так как он достаточно близок к указателю NULL, и, учитывая, что он довольно большой, есть вероятность, что у вас не будет этого в обычном разыменовании поля структуры с указателем структуры, равным NULL).

person Andrew Y    schedule 11.08.2009
comment
На самом деле он не такой уж большой... существует множество структур длиной более 66 байт. - person Jeremy Friesner; 03.08.2010

Поскольку система, над которой я работал, в основном работает на платформе x86_64, я использую следующее значение:

0xDEADBEEFDEADBEEF

Причины:

  • На платформе x86_64 только младшие 48 бит используются для адреса виртуальной памяти в текущей реализации, что означает, что любое значение > 2^48 должно работать: https://en.wikipedia.org/wiki/X86-64
  • Поскольку 0xDEADBEEF уже очень хорошо известен для этой цели в 32-битной версии, 0xDEADBEEFDEADBEEF в 64-битной версии просто более «обратно совместим».
person baye    schedule 28.05.2019

person    schedule
comment
Этот конкретный имеет то преимущество, что вам нужны йоттабайты памяти, чтобы фактически сделать это значение разумным, поскольку куча ядра растет, а куча пользователя растет. - person Joshua; 02.08.2010
comment
@Joshua: 1. Я могу отображать свои страницы где угодно. 2. Это очень опасное предположение (возможно, это верно для вашей текущей ОС на установке x86, но это зависит от реализации), что, если стек начинается с 0xBADCOFFEE0DFOOD-0x1000? Стек уже начинается в случайных местах, он делает это, чтобы попытаться усложнить эксплуатацию и помимо этого по техническим причинам. - person L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o&#x; 03.08.2010
comment
Как вы пришли к этому числу? - person 0x499602D2; 30.04.2013