Kapan dan mengapa kompiler menginisialisasi memori ke 0xCD, 0xDD, dll di malloc / free / new / delete?

129

Saya tahu bahwa kompiler terkadang akan menginisialisasi memori dengan pola tertentu seperti 0xCDdan 0xDD. Yang ingin saya ketahui adalah kapan dan mengapa ini terjadi.

Kapan

Apakah ini khusus untuk kompiler yang digunakan?

Apakah malloc/newdan free/deletebekerja dengan cara yang sama sehubungan dengan ini?

Apakah ini spesifik platform?

Apakah akan terjadi pada sistem operasi lain, seperti Linuxatau VxWorks?

Mengapa

Pemahaman saya adalah ini hanya terjadi dalam Win32konfigurasi debug, dan digunakan untuk mendeteksi kelebihan memori dan untuk membantu pengecualian tangkapan kompiler.

Bisakah Anda memberikan contoh praktis bagaimana inisialisasi ini bermanfaat?

Saya ingat membaca sesuatu (mungkin dalam Kode Lengkap 2) mengatakan bahwa adalah baik untuk menginisialisasi memori ke pola yang diketahui saat mengalokasikannya, dan pola tertentu akan memicu interupsi Win32yang akan menghasilkan pengecualian yang ditampilkan di debugger.

Seberapa portabel ini?

LeopardSkinPillBoxHat
sumber

Jawaban:

191

Ringkasan singkat tentang apa yang digunakan kompiler Microsoft untuk berbagai bit memori yang tidak dimiliki / tidak diinisialisasi ketika dikompilasi untuk mode debug (dukungan dapat bervariasi menurut versi kompiler):

Value     Name           Description 
------   --------        -------------------------
0xCD     Clean Memory    Allocated memory via malloc or new but never 
                         written by the application. 

0xDD     Dead Memory     Memory that has been released with delete or free. 
                         It is used to detect writing through dangling pointers. 

0xED or  Aligned Fence   'No man's land' for aligned allocations. Using a 
0xBD                     different value here than 0xFD allows the runtime
                         to detect not only writing outside the allocation,
                         but to also identify mixing alignment-specific
                         allocation/deallocation routines with the regular
                         ones.

0xFD     Fence Memory    Also known as "no mans land." This is used to wrap 
                         the allocated memory (surrounding it with a fence) 
                         and is used to detect indexing arrays out of 
                         bounds or other accesses (especially writes) past
                         the end (or start) of an allocated block.

0xFD or  Buffer slack    Used to fill slack space in some memory buffers 
0xFE                     (unused parts of `std::string` or the user buffer 
                         passed to `fread()`). 0xFD is used in VS 2005 (maybe 
                         some prior versions, too), 0xFE is used in VS 2008 
                         and later.

0xCC                     When the code is compiled with the /GZ option,
                         uninitialized variables are automatically assigned 
                         to this value (at byte level). 


// the following magic values are done by the OS, not the C runtime:

0xAB  (Allocated Block?) Memory allocated by LocalAlloc(). 

0xBAADF00D Bad Food      Memory allocated by LocalAlloc() with LMEM_FIXED,but 
                         not yet written to. 

0xFEEEFEEE               OS fill heap memory, which was marked for usage, 
                         but wasn't allocated by HeapAlloc() or LocalAlloc(). 
                         Or that memory just has been freed by HeapFree(). 

Penafian: tabel ini dari beberapa catatan yang saya miliki - mereka mungkin tidak 100% benar (atau koheren).

Banyak dari nilai-nilai ini didefinisikan dalam vc / crt / src / dbgheap.c:

/*
 * The following values are non-zero, constant, odd, large, and atypical
 *      Non-zero values help find bugs assuming zero filled data.
 *      Constant values are good, so that memory filling is deterministic
 *          (to help make bugs reproducible).  Of course, it is bad if
 *          the constant filling of weird values masks a bug.
 *      Mathematically odd numbers are good for finding bugs assuming a cleared
 *          lower bit.
 *      Large numbers (byte values at least) are less typical and are good
 *          at finding bad addresses.
 *      Atypical values (i.e. not too often) are good since they typically
 *          cause early detection in code.
 *      For the case of no man's land and free blocks, if you store to any
 *          of these locations, the memory integrity checker will detect it.
 *
 *      _bAlignLandFill has been changed from 0xBD to 0xED, to ensure that
 *      4 bytes of that (0xEDEDEDED) would give an inaccessible address under 3gb.
 */

static unsigned char _bNoMansLandFill = 0xFD;   /* fill no-man's land with this */
static unsigned char _bAlignLandFill  = 0xED;   /* fill no-man's land for aligned routines */
static unsigned char _bDeadLandFill   = 0xDD;   /* fill free objects with this */
static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

Ada juga beberapa kali di mana runtime debug akan mengisi buffer (atau bagian dari buffer) dengan nilai yang diketahui, misalnya, ruang 'kendur' dalam std::stringalokasi atau buffer yang diteruskan fread(). Kasing tersebut menggunakan nilai yang diberikan nama _SECURECRT_FILL_BUFFER_PATTERN(didefinisikan dalam crtdefs.h). Saya tidak yakin kapan tepatnya diperkenalkan, tapi itu di runtime debug oleh setidaknya VS 2005 (VC ++ 8).

Awalnya, nilai yang digunakan untuk mengisi buffer ini adalah 0xFD- nilai yang sama digunakan untuk tanah tak bertuan. Namun, dalam VS 2008 (VC ++ 9) nilainya diubah menjadi 0xFE. Saya berasumsi itu karena mungkin ada situasi di mana operasi pengisian akan berjalan melewati akhir buffer, misalnya, jika penelepon meneruskan dalam ukuran buffer yang terlalu besar untuk fread(). Dalam hal ini, nilainya 0xFDmungkin tidak memicu deteksi overrun ini karena jika ukuran buffer terlalu besar hanya dengan satu, nilai isian akan sama dengan nilai tanah tak bertuan yang digunakan untuk menginisialisasi kenari itu. Tidak ada perubahan di tanah tak bertuan yang berarti penganiayaan tidak akan diperhatikan.

Jadi nilai isian diubah pada VS 2008 sehingga kasus seperti itu akan mengubah kenari tanah tak bertuan, yang menghasilkan deteksi masalah oleh runtime.

Seperti yang telah dicatat oleh orang lain, salah satu properti kunci dari nilai-nilai ini adalah bahwa jika variabel pointer dengan salah satu dari nilai-nilai ini dide-referensikan, itu akan mengakibatkan pelanggaran akses, karena pada konfigurasi Windows 32-bit standar, alamat mode pengguna tidak akan lebih tinggi dari 0x7fffffff.

Michael Burr
sumber
1
Saya tidak tahu apakah itu ada di MSDN - saya menyatukannya dari sana-sini atau mungkin saya mendapatkannya dari beberapa situs web lain.
Michael Burr
2
Oh yeah - beberapa di antaranya dari sumber CRT di DbgHeap.c.
Michael Burr
Beberapa di antaranya ada di MSDN ( msdn.microsoft.com/en-us/library/bebs9zyz.aspx ), tetapi tidak semua. Daftar bagus
sean e
3
@seane - FYI tautan Anda sepertinya mati. Yang baru (teks telah ditingkatkan) tersedia di sini: msdn.microsoft.com/en-us/library/974tc9t1.aspx
Simon Mourier
Apa nama blok ini? Apakah itu penghalang memori, membar, pagar memori atau instruksi pagar ( en.wikipedia.org/wiki/Memory_barrier )?
kr85
36

Satu properti bagus tentang nilai isian 0xCCCCCCCC adalah dalam x86 assembly, opcode 0xCC adalah int3 opcode, yang merupakan interupsi breakpoint perangkat lunak. Jadi, jika Anda pernah mencoba mengeksekusi kode dalam memori yang tidak diinisialisasi yang telah diisi dengan nilai isian itu, Anda akan segera mencapai breakpoint, dan sistem operasi akan memungkinkan Anda melampirkan debugger (atau mematikan proses).

Adam Rosenfield
sumber
6
Dan 0xCD adalah intinstruksi, jadi mengeksekusi 0xCD 0xCD akan menghasilkan int CD, yang juga akan menjebak.
Tad Marshall
2
Di dunia saat ini, Pencegahan Eksekusi Data bahkan tidak mengizinkan CPU untuk mengambil instruksi dari heap. Jawaban ini sudah usang sejak XP SP2.
MSalters
2
@MSalters: Ya, memang benar bahwa secara default, memori yang baru dialokasikan tidak dapat dieksekusi, tetapi seseorang dapat dengan mudah menggunakan VirtualProtect()atau mprotect()membuat memori dapat dieksekusi.
Adam Rosenfield
Anda tidak dapat mengeksekusi kode dari blok data. PERNAH. Tebak lagi.
Dan
9

Ini adalah kompiler dan khusus OS, Visual studio menetapkan berbagai jenis memori ke nilai yang berbeda sehingga dalam debugger Anda dapat dengan mudah melihat apakah Anda telah menyatu lagi ke dalam memori malloced, array tetap atau objek tidak diinisialisasi. Seseorang akan memposting detail sementara saya mencari di Google ...

http://msdn.microsoft.com/en-us/library/974tc9t1.aspx

Martin Beckett
sumber
Dugaan saya adalah bahwa ini digunakan untuk memeriksa apakah Anda lupa untuk mengakhiri string Anda dengan benar (karena 0xCD atau 0xDD dicetak).
strager
0xCC = variabel lokal (tumpukan) tidak diinisialisasi 0xCD = variabel kelas tidak diinisialisasi (heap?) 0xDD = variabel yang dihapus
FryGuy
@ FryGuy Ada alasan praktis yang menentukan (sebagian) nilai-nilai ini, seperti yang saya jelaskan di sini .
Glenn Slayden
4

Ini bukan OS - melainkan kompiler. Anda dapat memodifikasi perilaku juga - lihat bagian bawah posting ini.

Microsoft Visual Studio menghasilkan (dalam mode Debug) biner yang mengisi memori stack dengan 0xCC. Ini juga menyisipkan ruang antara setiap frame stack untuk mendeteksi buffer overflows. Contoh yang sangat sederhana tentang di mana ini berguna ada di sini (dalam praktiknya Visual Studio akan menemukan masalah ini dan mengeluarkan peringatan):

...
   bool error; // uninitialised value
   if(something)
   {
      error = true;
   }
   return error;

Jika Visual Studio tidak memprioritaskan variabel ke nilai yang diketahui, maka bug ini berpotensi sulit ditemukan. Dengan variabel yang sudah diinisialisasi (atau lebih tepatnya, memori stack yang sudah diinisialisasi), masalah dapat direproduksi pada setiap proses.

Namun, ada sedikit masalah. Nilai yang digunakan Visual Studio BENAR - apa pun kecuali 0 adalah. Sebenarnya sangat mungkin bahwa ketika Anda menjalankan kode Anda dalam mode Rilis bahwa variabel unitisasi dapat dialokasikan ke sepotong memori stack yang kebetulan mengandung 0, yang berarti Anda dapat memiliki bug variabel unitial yang hanya memanifestasikan dirinya dalam mode Rilis.

Itu mengganggu saya, jadi saya menulis sebuah skrip untuk memodifikasi nilai pra-isi dengan langsung mengedit biner, memungkinkan saya untuk menemukan masalah variabel tidak terinstalasi yang hanya muncul ketika tumpukan berisi nol. Script ini hanya memodifikasi stack pre-fill; Saya tidak pernah bereksperimen dengan heap pre-fill, meskipun itu harus mungkin. Mungkin melibatkan mengedit DLL run-time, mungkin tidak.

Airsource Ltd
sumber
1
Bukankah VS mengeluarkan peringatan saat menggunakan nilai sebelum diinisialisasi, seperti GCC?
strager
3
Ya, tetapi tidak selalu, karena itu tergantung pada analisis statis. Akibatnya cukup mudah untuk membingungkannya dengan pointer aritmatika.
Airsource Ltd
3
"Bukan OS - melainkan kompiler." Sebenarnya, ini bukan kompiler - itu adalah perpustakaan runtime.
Adrian McCarthy
Saat debugging, debugger Visual Studio akan menunjukkan nilai bool jika tidak 0 atau 1 dengan sesuatu seperti true (204) . Jadi relatif mudah untuk melihat bug semacam itu jika Anda melacak kode.
Phil1970
4

Apakah ini khusus untuk kompiler yang digunakan?

Sebenarnya, ini hampir selalu merupakan fitur dari perpustakaan runtime (seperti perpustakaan C runtime). Runtime biasanya sangat berkorelasi dengan kompiler, tetapi ada beberapa kombinasi yang bisa Anda tukarkan.

Saya percaya pada Windows, tumpukan debug (HeapAlloc, dll.) Juga menggunakan pola isian khusus yang berbeda dari yang berasal dari malloc dan implementasi gratis di perpustakaan runtime debug C. Jadi mungkin juga fitur OS, tetapi sebagian besar waktu, itu hanya perpustakaan runtime bahasa.

Apakah malloc / baru dan gratis / hapus berfungsi dengan cara yang sama sehubungan dengan ini?

Bagian manajemen memori baru dan hapus biasanya diimplementasikan dengan malloc dan gratis, sehingga memori yang dialokasikan dengan baru dan hapus biasanya memiliki fitur yang sama.

Apakah ini spesifik platform?

Detailnya khusus untuk runtime. Nilai aktual yang digunakan sering dipilih untuk tidak hanya terlihat tidak biasa dan jelas ketika melihat dump hex, tetapi dirancang untuk memiliki properti tertentu yang dapat mengambil keuntungan dari fitur prosesor. Misalnya, nilai ganjil sering digunakan, karena mereka dapat menyebabkan kesalahan pelurusan. Nilai besar digunakan (sebagai lawan 0), karena menyebabkan keterlambatan mengejutkan jika Anda beralih ke penghitung yang tidak diinisialisasi. Pada x86, 0xCC adalah int 3instruksi, jadi jika Anda menjalankan memori yang tidak diinisialisasi, itu akan menjebak.

Apakah ini akan terjadi pada sistem operasi lain, seperti Linux atau VxWorks?

Ini sebagian besar tergantung pada pustaka runtime yang Anda gunakan.

Bisakah Anda memberikan contoh praktis bagaimana inisialisasi ini bermanfaat?

Saya mendaftarkan beberapa di atas. Nilai-nilai umumnya dipilih untuk meningkatkan peluang bahwa sesuatu yang tidak biasa terjadi jika Anda melakukan sesuatu dengan bagian memori yang tidak valid: penundaan lama, jebakan, kesalahan penyelarasan, dll. Manajer tumpukan juga terkadang menggunakan nilai pengisian khusus untuk kesenjangan antara alokasi. Jika pola-pola itu pernah berubah, ia tahu ada tulisan buruk (seperti buffer overrun) di suatu tempat.

Saya ingat membaca sesuatu (mungkin dalam Code Complete 2) yang baik untuk menginisialisasi memori ke pola yang diketahui saat mengalokasikannya, dan pola tertentu akan memicu interupsi di Win32 yang akan menghasilkan pengecualian yang ditampilkan di debugger.

Seberapa portabel ini?

Menulis Kode Padat (dan mungkin Kode Lengkap ) berbicara tentang hal-hal yang perlu dipertimbangkan ketika memilih pola pengisian. Saya telah menyebutkan beberapa dari mereka di sini, dan artikel Wikipedia tentang Magic Number (pemrograman) juga merangkumnya. Beberapa trik tergantung pada spesifikasi prosesor yang Anda gunakan (seperti apakah itu memerlukan baca dan tulis yang selaras dan nilai apa yang dipetakan ke instruksi yang akan menjebak). Trik lain, seperti menggunakan nilai besar dan nilai tidak biasa yang menonjol di memori dump lebih portabel.

Adrian McCarthy
sumber
2

Artikel ini menjelaskan pola bit memori yang tidak biasa dan berbagai teknik yang dapat Anda gunakan jika Anda menemukan nilai-nilai ini.

Stephen Kellett
sumber
2

Alasan yang jelas untuk "mengapa" adalah anggaplah Anda memiliki kelas seperti ini:

class Foo
{
public:
    void SomeFunction()
    {
        cout << _obj->value << endl;
    }

private:
    SomeObject *_obj;
}

Dan kemudian Anda instantiate satu Foodan menelepon SomeFunction, itu akan memberikan pelanggaran akses mencoba membaca 0xCDCDCDCD. Ini berarti Anda lupa menginisialisasi sesuatu. Itulah "bagian mengapa". Jika tidak, maka penunjuk mungkin telah berbaris dengan memori lain, dan akan lebih sulit untuk di-debug. Ini hanya memberi tahu Anda alasan Anda mendapatkan pelanggaran akses. Perhatikan bahwa kasus ini cukup sederhana, tetapi di kelas yang lebih besar mudah untuk melakukan kesalahan itu.

AFAIK, ini hanya bekerja pada kompiler Visual Studio ketika dalam mode debug (sebagai lawan melepaskan)

FryGuy
sumber
Penjelasan Anda tidak mengikuti, karena Anda juga akan mendapatkan pelanggaran akses yang mencoba membaca 0x00000000, yang akan sama bermanfaatnya (atau lebih, sebagai alamat yang buruk). Seperti yang saya tunjukkan dalam komentar lain di halaman ini, alasan sebenarnya untuk 0xCD(dan 0xCC) adalah mereka adalah x86 opcode yang dapat ditafsirkan yang memicu interupsi perangkat lunak, dan ini memungkinkan pemulihan yang baik ke debugger hanya dalam satu jenis kesalahan khusus dan langka. , yaitu, ketika CPU secara keliru mencoba mengeksekusi byte di wilayah non-kode. Selain penggunaan fungsional ini, nilai isian hanyalah petunjuk penasehat, seperti yang Anda perhatikan.
Glenn Slayden
2

Ini untuk dengan mudah melihat bahwa memori telah berubah dari nilai awal awal, umumnya selama debugging tetapi kadang-kadang untuk kode rilis juga, karena Anda dapat melampirkan debugger ke proses saat sedang berjalan.

Ini bukan hanya memori juga, banyak debugger akan mengatur konten register ke nilai sentinel ketika proses dimulai (beberapa versi AIX akan mengatur beberapa register 0xdeadbeefyang agak lucu).

paxdiablo
sumber
1

Kompiler IBM XLC memiliki opsi "initauto" yang akan menetapkan variabel otomatis nilai yang Anda tentukan. Saya menggunakan yang berikut ini untuk build debug saya:

-Wc,'initauto(deadbeef,word)'

Jika saya melihat penyimpanan variabel yang tidak diinisialisasi, itu akan diatur ke 0xdeadbeef

Anthony Giorgio
sumber