Khi nào và tại sao trình biên dịch sẽ khởi tạo bộ nhớ thành 0xCD, 0xDD, v.v. trên malloc / free / new / xóa?


129

Tôi biết rằng trình biên dịch đôi khi sẽ khởi tạo bộ nhớ với các mẫu nhất định như 0xCD0xDD. Những gì tôi muốn biết là khi nàotại sao điều này xảy ra.

Khi nào

Đây có phải là cụ thể cho trình biên dịch được sử dụng?

Làm malloc/newfree/deletelàm việc theo cùng một cách liên quan đến điều này?

Là nền tảng cụ thể?

Nó sẽ xảy ra trên các hệ điều hành khác, chẳng hạn như Linuxhoặc VxWorks?

Tại sao

Sự hiểu biết của tôi là điều này chỉ xảy ra trong Win32cấu hình gỡ lỗi và nó được sử dụng để phát hiện tràn bộ nhớ và để giúp trình biên dịch bắt ngoại lệ.

Bạn có thể đưa ra bất kỳ ví dụ thực tế nào về việc khởi tạo này hữu ích như thế nào không?

Tôi nhớ đã đọc một cái gì đó (có thể trong Code Complete 2) nói rằng thật tốt khi khởi tạo bộ nhớ thành một mẫu đã biết khi phân bổ nó, và một số mẫu nhất định sẽ kích hoạt các ngắt trong Win32đó sẽ dẫn đến các ngoại lệ hiển thị trong trình gỡ lỗi.

Làm thế nào là di động này?

Câu trả lời:


191

Tóm tắt nhanh về những gì trình biên dịch của Microsoft sử dụng cho các bit khác nhau của bộ nhớ chưa được đặt tên / chưa được khởi tạo khi được biên dịch cho chế độ gỡ lỗi (hỗ trợ có thể thay đổi theo phiên bản trình biên dịch):

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(). 

Tuyên bố miễn trừ trách nhiệm: bảng là từ một số ghi chú tôi đã nói dối - chúng có thể không chính xác 100% (hoặc mạch lạc).

Nhiều giá trị trong số này được định nghĩa trong 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 */

Cũng có một vài lần thời gian chạy gỡ lỗi sẽ lấp đầy bộ đệm (hoặc một phần bộ đệm) với một giá trị đã biết, ví dụ, không gian 'chùng' trong std::stringphân bổ hoặc bộ đệm được truyền vào fread(). Những trường hợp sử dụng một giá trị được đặt tên _SECURECRT_FILL_BUFFER_PATTERN(được xác định trong crtdefs.h). Tôi không chắc chắn chính xác khi nào nó được giới thiệu, nhưng nó đã ở trong thời gian chạy gỡ lỗi ít nhất là VS 2005 (VC ++ 8).

Ban đầu, giá trị được sử dụng để lấp đầy các vùng đệm này là 0xFD- giá trị tương tự được sử dụng cho vùng đất không có người. Tuy nhiên, trong VS 2008 (VC ++ 9), giá trị đã được thay đổi thành 0xFE. Tôi giả sử rằng vì có thể có tình huống thao tác điền sẽ chạy qua phần cuối của bộ đệm, ví dụ, nếu người gọi chuyển qua kích thước bộ đệm quá lớn fread(). Trong trường hợp đó, giá trị 0xFDcó thể không kích hoạt phát hiện lỗi tràn này vì nếu kích thước bộ đệm quá lớn chỉ bằng một, giá trị điền sẽ giống như giá trị đất của người đàn ông được sử dụng để khởi tạo chim hoàng yến đó. Không có thay đổi trong vùng đất không có người đàn ông có nghĩa là sự tràn ngập sẽ không được chú ý.

Vì vậy, giá trị điền đã được thay đổi trong VS 2008 để trường hợp như vậy thay đổi canary đất của người đàn ông, dẫn đến việc phát hiện vấn đề bằng thời gian chạy.

Như những người khác đã lưu ý, một trong những thuộc tính chính của các giá trị này là nếu một biến con trỏ có một trong các giá trị này bị hủy tham chiếu, nó sẽ dẫn đến vi phạm truy cập, vì trên cấu hình Windows 32 bit tiêu chuẩn, địa chỉ chế độ người dùng sẽ không đi cao hơn 0x7fffffff.


1
Tôi không biết nếu nó có trên MSDN - tôi đã ghép nó từ đây và ở đó hoặc có thể tôi đã nhận được nó từ một số trang web khác.
Michael Burr

2
Ồ vâng - một số trong số đó là từ nguồn CRT trong DbgHeap.c.
Michael Burr

Một số trong số đó là trên MSDN ( msdn.microsoft.com/en-us/l Library / bbs9zyz.aspx ), nhưng không phải tất cả. Danh sách tốt.
sean e

3
@seane - FYI liên kết của bạn dường như đã chết. Một cái mới (văn bản đã được nâng cao) có sẵn ở đây: msdn.microsoft.com/en-us/l Library / 974tc9t1.aspx
Simon Mourier

Tên của các khối này là gì? Đây có phải là hàng rào bộ nhớ, thanh ghi nhớ, hàng rào bộ nhớ hoặc hướng dẫn hàng rào ( en.wikipedia.org/wiki/Memory_barrier )?
kr85

36

Một đặc tính tốt về giá trị điền 0xCCCCCCCC là trong tập hợp x86, opcode 0xCC là opcode int3 , là điểm ngắt của phần mềm. Vì vậy, nếu bạn từng cố gắng thực thi mã trong bộ nhớ chưa được khởi tạo với giá trị điền đó, bạn sẽ ngay lập tức đạt được điểm dừng và hệ điều hành sẽ cho phép bạn đính kèm trình gỡ lỗi (hoặc giết tiến trình).


6
Và 0xCD là inthướng dẫn, vì vậy thực thi 0xCD 0xCD sẽ tạo ra một cái int CD, nó cũng sẽ bẫy.
Tad Marshall

2
Trong thế giới ngày nay, Ngăn chặn thực thi dữ liệu thậm chí không cho phép CPU tìm nạp một lệnh từ heap. Câu trả lời này đã lỗi thời kể từ XP SP2.
MSalters

2
@MSalters: Vâng, đúng là theo mặc định, bộ nhớ mới được phân bổ sẽ không thể thực thi được, nhưng ai đó có thể dễ dàng sử dụng VirtualProtect()hoặc mprotect()để thực hiện bộ nhớ.
Adam Rosenfield

Bạn không thể thực thi mã từ một khối dữ liệu. KHÔNG BAO GIỜ. Đoán lại.
Dan

9

Đó là trình biên dịch và hệ điều hành cụ thể, Visual studio đặt các loại bộ nhớ khác nhau thành các giá trị khác nhau để trong trình gỡ lỗi, bạn có thể dễ dàng nhìn thấy nếu bạn đã nạp vào bộ nhớ malloced, một mảng cố định hoặc một đối tượng chưa được khởi tạo. Ai đó sẽ đăng thông tin chi tiết trong khi tôi đang googling họ ...

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


Tôi đoán là nó được sử dụng để kiểm tra xem bạn có quên chấm dứt chuỗi của mình đúng không (vì các 0xCD hoặc 0xDD đã được in).
strager

0xCC = uninitialized địa phương (stack) biến 0xCD = lớp chưa được khởi tạo (? Đống) biến 0xDD = xóa biến
FryGuy

@FryGuy Có một lý do thực tế chỉ ra (một số) những giá trị này, như tôi giải thích ở đây .
Glenn Slayden

4

Đó không phải là HĐH - đó là trình biên dịch. Bạn cũng có thể sửa đổi hành vi - xem phần dưới của bài đăng này.

Microsoft Visual Studio tạo (trong chế độ Gỡ lỗi) một nhị phân lấp đầy bộ nhớ ngăn xếp với 0xCC. Nó cũng chèn một khoảng trắng giữa mỗi khung ngăn xếp để phát hiện lỗi tràn bộ đệm. Một ví dụ rất đơn giản về nơi điều này hữu ích ở đây (trong thực tế Visual Studio sẽ phát hiện ra vấn đề này và đưa ra cảnh báo):

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

Nếu Visual Studio không biến các biến thành preinitialise thành một giá trị đã biết, thì lỗi này có thể khó tìm thấy. Với các biến preinitialised (hay đúng hơn là bộ nhớ stack preinitialized), vấn đề có thể tái tạo trên mỗi lần chạy.

Tuy nhiên, có một vấn đề nhỏ. Giá trị mà Visual Studio sử dụng là TRUE - mọi thứ trừ 0 sẽ là. Thực tế rất có thể là khi bạn chạy mã của mình trong chế độ Phát hành, các biến được đơn vị hóa có thể được phân bổ cho một phần bộ nhớ ngăn xếp có chứa 0, điều đó có nghĩa là bạn có thể có một lỗi biến đơn vị chỉ xuất hiện trong chế độ Phát hành.

Điều đó làm tôi khó chịu, vì vậy tôi đã viết một kịch bản để sửa đổi giá trị điền trước bằng cách chỉnh sửa trực tiếp nhị phân, cho phép tôi tìm các vấn đề biến không được kích thích chỉ hiển thị khi ngăn xếp chứa số không. Kịch bản này chỉ sửa đổi trước khi điền vào ngăn xếp; Tôi chưa bao giờ thử nghiệm với việc điền trước đống, mặc dù điều đó là có thể. Có thể liên quan đến việc chỉnh sửa DLL thời gian chạy, có thể không.


1
Không VS đưa ra cảnh báo khi sử dụng một giá trị trước khi nó được khởi tạo, như GCC?
strager

3
Có, nhưng không phải lúc nào cũng vậy, vì nó phụ thuộc vào phân tích tĩnh. Do đó, khá dễ nhầm lẫn nó với số học con trỏ.
Airsource Ltd

3
"Đây không phải là HĐH - đó là trình biên dịch." Trên thực tế, nó không phải là trình biên dịch - đó là thư viện thời gian chạy.
Adrian McCarthy

Khi gỡ lỗi, trình gỡ lỗi Visual Studio sẽ hiển thị giá trị của bool nếu không phải là 0 hoặc 1 với giá trị như true (204) . Vì vậy, tương đối dễ dàng để thấy loại lỗi đó nếu bạn theo dõi mã.
Phil1970

4

Đây có phải là cụ thể cho trình biên dịch được sử dụng?

Trên thực tế, nó hầu như luôn là một tính năng của thư viện thời gian chạy (như thư viện thời gian chạy C). Thời gian chạy thường tương quan mạnh với trình biên dịch, nhưng có một số kết hợp bạn có thể trao đổi.

Tôi tin rằng trên Windows, heap debug (HeapAlloc, v.v.) cũng sử dụng các mẫu điền đặc biệt khác với các mẫu xuất phát từ malloc và triển khai miễn phí trong thư viện thời gian chạy gỡ lỗi C. Vì vậy, nó cũng có thể là một tính năng của hệ điều hành, nhưng hầu hết thời gian, nó chỉ là thư viện thời gian chạy ngôn ngữ.

Do malloc / mới và miễn phí / xóa hoạt động theo cùng một cách liên quan đến điều này?

Phần quản lý bộ nhớ của mới và xóa thường được triển khai với malloc và miễn phí, do đó bộ nhớ được cấp phát mới và xóa thường có các tính năng giống nhau.

Là nền tảng cụ thể?

Các chi tiết là thời gian chạy cụ thể. Các giá trị thực tế được sử dụng thường được chọn để không chỉ trông khác thường và rõ ràng khi nhìn vào kết xuất hex, mà còn được thiết kế để có các thuộc tính nhất định có thể tận dụng các tính năng của bộ xử lý. Ví dụ: các giá trị lẻ thường được sử dụng, vì chúng có thể gây ra lỗi căn chỉnh. Các giá trị lớn được sử dụng (trái ngược với 0), vì chúng gây ra sự chậm trễ đáng ngạc nhiên nếu bạn lặp đến một bộ đếm chưa được khởi tạo. Trên x86, 0xCC là một int 3hướng dẫn, vì vậy nếu bạn thực thi bộ nhớ chưa được khởi tạo, nó sẽ bị kẹt.

Nó sẽ xảy ra trên các hệ điều hành khác, chẳng hạn như Linux hoặc VxWorks?

Nó chủ yếu phụ thuộc vào thư viện thời gian chạy bạn sử dụng.

Bạn có thể đưa ra bất kỳ ví dụ thực tế nào về việc khởi tạo này hữu ích như thế nào không?

Tôi liệt kê một số ở trên. Các giá trị thường được chọn để tăng khả năng xảy ra điều gì đó bất thường nếu bạn làm điều gì đó với các phần bộ nhớ không hợp lệ: độ trễ dài, bẫy, lỗi căn chỉnh, v.v. Đôi khi, người quản lý Heap cũng sử dụng các giá trị điền đặc biệt cho các khoảng trống giữa các lần phân bổ. Nếu những mô hình đó thay đổi, nó biết rằng có một chữ viết xấu (như lỗi tràn bộ đệm) ở đâu đó.

Tôi nhớ đã đọc một cái gì đó (có thể trong Code Complete 2) rằng thật tốt khi khởi tạo bộ nhớ cho một mẫu đã biết khi cấp phát nó và một số mẫu nhất định sẽ kích hoạt các ngắt trong Win32, điều này sẽ dẫn đến các ngoại lệ hiển thị trong trình gỡ lỗi.

Làm thế nào là di động này?

Viết Solid Code (và có thể Code Complete ) nói về những điều cần xem xét khi chọn mẫu điền. Tôi đã đề cập đến một số trong số họ ở đây và bài viết Wikipedia về Magic Number (lập trình) cũng tóm tắt chúng. Một số thủ thuật phụ thuộc vào chi tiết cụ thể của bộ xử lý bạn đang sử dụng (như liệu nó có yêu cầu đọc và ghi được căn chỉnh hay không và giá trị nào ánh xạ tới các hướng dẫn sẽ bẫy). Các thủ thuật khác, như sử dụng các giá trị lớn và các giá trị bất thường nổi bật trong kết xuất bộ nhớ sẽ dễ mang theo hơn.



2

Lý do rõ ràng cho "tại sao" là giả sử bạn có một lớp học như thế này:

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

private:
    SomeObject *_obj;
}

Và sau đó bạn khởi tạo một Foovà gọi SomeFunction, nó sẽ vi phạm quyền truy cập khi cố đọc 0xCDCDCDCD. Điều này có nghĩa là bạn quên khởi tạo một cái gì đó. Đó là "lý do tại sao". Nếu không, thì con trỏ có thể đã xếp hàng với một số bộ nhớ khác và việc gỡ lỗi sẽ khó khăn hơn. Nó chỉ cho bạn biết lý do bạn vi phạm quyền truy cập. Lưu ý rằng trường hợp này khá đơn giản, nhưng trong một lớp lớn hơn, rất dễ mắc sai lầm đó.

AFAIK, điều này chỉ hoạt động trên trình biên dịch Visual Studio khi ở chế độ gỡ lỗi (trái ngược với phát hành)


Lời giải thích của bạn không tuân theo, vì bạn cũng bị vi phạm quyền truy cập khi cố đọc 0x00000000, điều này sẽ hữu ích (hoặc hơn thế nữa, là một địa chỉ xấu). Như tôi đã chỉ ra trong một bình luận khác trên trang này, lý do thực sự của 0xCD(và 0xCC) là chúng có thể hiểu được các mã op86 x86 gây ra sự gián đoạn phần mềm và điều này cho phép phục hồi một cách duyên dáng vào trình gỡ lỗi chỉ trong một loại lỗi cụ thể và hiếm gặp , cụ thể là khi CPU cố gắng thực thi các byte trong một vùng không có mã. Khác với việc sử dụng chức năng này, các giá trị điền chỉ là gợi ý tư vấn, như bạn lưu ý.
Glenn Slayden

2

Dễ dàng nhận thấy rằng bộ nhớ đã thay đổi so với giá trị ban đầu, nói chung là trong quá trình gỡ lỗi nhưng đôi khi cũng là mã phát hành, vì bạn có thể đính kèm trình gỡ lỗi vào quy trình trong khi nó đang chạy.

Không chỉ là bộ nhớ, nhiều trình gỡ lỗi sẽ đặt nội dung đăng ký thành giá trị sentinel khi quá trình bắt đầu (một số phiên bản của AIX sẽ đặt một số thanh ghi 0xdeadbeefcó tính hài hước nhẹ).


1

Trình biên dịch XLC của IBM có tùy chọn "initauto" sẽ gán các biến tự động một giá trị mà bạn chỉ định. Tôi đã sử dụng như sau cho các bản dựng gỡ lỗi của mình:

-Wc,'initauto(deadbeef,word)'

Nếu tôi nhìn vào việc lưu trữ một biến chưa được khởi tạo, nó sẽ được đặt thành 0xdeadbeef

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.