Địa chỉ 0000000C có phải là địa chỉ đặc biệt không?


32

Khi lập trình đôi khi mọi thứ bị phá vỡ. Bạn đã mắc lỗi và chương trình của bạn cố đọc từ một địa chỉ sai.

Một điều nổi bật với tôi rằng thường những ngoại lệ đó là:

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

Bây giờ tôi thấy rất nhiều nhật ký lỗi và điều nổi bật với tôi là: 0000000C. Đây có phải là một địa chỉ "đặc biệt" không? Tôi thấy các vi phạm truy cập khác với các lần đọc xấu nhưng các địa chỉ có vẻ ngẫu nhiên, nhưng địa chỉ này tiếp tục quay lại trong các tình huống hoàn toàn khác nhau.


1
Tôi cũng nhận thấy rằng 0000000Ccách phổ biến hơn 00000008, nhưng không ai trong số các câu trả lời dường như đến địa chỉ đó ở tất cả: /
Mooing Duck

2
Có lẽ đó System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData12=0x0Cmột lý do tại sao sự bù đắp này là phổ biến hơn.
Đánh dấu Hurd

1
@MarkHurd Thật đáng sợ. Bạn có thực sự nghĩ rằng có rất nhiều ứng dụng không được quản lý với mục đích đọc / ghi các chuỗi .NET mà đây sẽ là một nguồn vi phạm truy cập chính?
Luaan

Câu trả lời:


57

00000000là một địa chỉ đặc biệt (con trỏ null). 0000000Cchỉ là những gì bạn nhận được khi bạn thêm phần bù 12 vào con trỏ null, rất có thể là do ai đó đã cố gắng lấy zthành viên của cấu trúc như cấu trúc bên dưới thông qua một con trỏ thực sự là null.

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
Hoặc có thể bởi vì một số giá trị tích phân nhỏ đã bị bỏ qua nhầm lẫn như thể nó là một con trỏ. Các giá trị nhỏ phổ biến hơn nhiều so với các giá trị khổng lồ, do đó, điều này có xu hướng tạo ra các địa chỉ bất hợp pháp như 0X0000000C thay vì, ví dụ: 0x43FCC893.
Kilian Foth

3
Lý do tôi hỏi câu hỏi này là vì 0000000C trở lại thường xuyên so với các địa chỉ khác. Tại sao bù 12 độ lớn phổ biến hơn sau đó bù 4, 8 hoặc 16?
Pieter B

5
Sau khi điều tra thêm câu trả lời này là hoàn toàn chính xác. Trong nguồn của tôi, thuộc tính "thẻ" của các lớp được sử dụng rộng rãi (dù tốt hay xấu tôi phải xử lý nó.) Thuộc tính thẻ trong trường hợp của tôi là một phần của lớp cơ sở cấp thấp và nó luôn được tạo ở mức bù đó.
Pieter B

1
Điểm tuyệt vời. Có lẽ trường hợp con trỏ null đã được bảo hiểm, nhưng con trỏ null ++ chỉ là một địa chỉ bình thường (và trong trường hợp này không hợp lệ), do đó nó chỉ thất bại khi truy cập nó.
Neil

8
@Leushenko Có, bảo vệ bộ nhớ thường hoạt động trên toàn bộ trang và ngay cả khi chỉ có thể bắt 0, thì cũng nên bảo vệ các địa chỉ sau vì chúng có thể được truy cập nếu số học con trỏ với con trỏ null xảy ra (như trong Trường hợp của OP).

11

Trong Windows nó là bất hợp pháp để dereference các toàn bộ trang đầu tiên và cuối cùng , nói cách khác là người đầu tiên hoặc cuối cùng 64 KiB của bộ nhớ quá trình (các dãy 0x00000000để 0x0000ffff0xffff0000để 0xfffffffftrong một ứng dụng 32-bit).

Điều này là để bẫy hành vi không xác định của việc hủy bỏ một con trỏ hoặc chỉ mục null thành một mảng null. Và kích thước trang là 64 KiB, vì vậy Windows chỉ cần ngăn trang đầu tiên hoặc trang cuối cùng được gán một phạm vi hợp lệ.

Điều này sẽ không bảo vệ chống lại con trỏ chưa được khởi tạo có thể có bất kỳ giá trị nào (bao gồm cả địa chỉ hợp lệ).


7
Windows thực sự không thể làm điều đó. Bảng trang là cấu trúc được xác định và yêu cầu bởi x86 và các trang nhỏ được cố định ở 4KB. Nó được đặt trong đá (chính xác hơn là bằng silicon). 64KB có lẽ là để thuận tiện.
ElderBug

12
Tôi muốn viết 64 KiB thay vì 65 kB trong trường hợp này, vì kích thước sức mạnh của hai có liên quan.
CodeInChaos

4
Phạm vi 64KB là một phần còn lại từ phiên bản Aplha của NT. Và đó không phải là kích thước trang, mà là độ chi tiết phân bổ. blogs.msdn.com/b/oldnewthing/archive/2003/10/08/55239.aspx
shf301

3
@CodesInChaos: Mặc dù chữ hoa "M", "G" và "T" không rõ ràng, tôi thấy không có lý do gì để từ chối sử dụng "k" trong 10 ^ 3 và "K" cho 2 ^ 10.
supercat

2
@MooingDuck Đúng vậy, đó là lý do tại sao tôi có các trang nhỏ. Hầu hết CPU x64 cũng hỗ trợ các trang 1GiB. Theo tôi biết, Windows luôn có các trang có trang 4KB, trừ khi được phân bổ các API đặc biệt.
ElderBug

2

Về lý do tại sao 0x0Ccó vẻ phổ biến hơn 0x08(thực sự là vậy? Tôi không biết; và trong các loại ứng dụng nào?), Điều này có thể phải làm với các con trỏ bảng phương thức ảo. Đây thực sự là một nhận xét nhiều hơn (đoán đại chúng hoang dã :), nhưng nó có phần lớn hơn, vì vậy hãy đến đây ... Nếu bạn có một lớp học với các phương thức ảo, các trường riêng của nó sẽ bị thay đổi 0x04. Ví dụ, một lớp kế thừa từ một lớp ảo khác có thể có bố cục bộ nhớ như thế này:

0x00 - VMT pointer for parent
0x04 - Field 1 in parent
0x08 - VMT pointer for child
0x0C - Field 1 in child

Đây có phải là một kịch bản phổ biến, hoặc thậm chí đóng? Tôi không chắc. Tuy nhiên, lưu ý rằng trong ứng dụng 64 bit, điều này thậm chí có thể chuyển sang 0x0Cgiá trị thú vị hơn :

0x00 - VMT parent
0x08 - Field 1 parent
0x0C - VMT child
0x14 - Field 2 child

Vì vậy, thực sự có rất nhiều trường hợp các ứng dụng có thể có sự chồng chéo đáng kể trong các con trỏ null. Nó có thể là trường đầu tiên trong lớp con hoặc con trỏ bảng phương thức ảo của nó - cần thiết bất cứ khi nào bạn gọi bất kỳ phương thức ảo nào trên một cá thể, vì vậy nếu bạn gọi một phương thức ảo trên một nullcon trỏ, bạn sẽ bị vi phạm quyền truy cập trên nó VMT bù. Sự phổ biến của giá trị cụ thể này sau đó có thể có liên quan đến một số API phổ biến cung cấp một lớp có kiểu kế thừa tương tự, hoặc nhiều khả năng là một giao diện cụ thể (hoàn toàn có thể đối với một số lớp ứng dụng, như trò chơi DirectX). Có thể theo dõi một số nguyên nhân phổ biến đơn giản như thế này, nhưng tôi có xu hướng loại bỏ các ứng dụng làm hội thảo vô hiệu hóa khá nhanh, vì vậy ...


1
Nếu bạn xem qua các bình luận, bạn có thể giảm bớt việc đoán.
Ded repeatator

@Ded repeatator Vâng, tôi thấy ý tưởng rằng các chuỗi .NET được quản lý được sử dụng trong mã không an toàn với các thao tác con trỏ thủ công đáng sợ và nghĩ rằng đây sẽ là nguyên nhân chính cho vi phạm truy cập thậm chí còn nhiều hơn thế. "Vâng, đây hoàn toàn là bộ nhớ an toàn, đừng lo lắng, chúng tôi đã sử dụng C #. Chúng tôi chỉ sửa đổi thủ công bộ nhớ từ C ++, nhưng nó an toàn trong C #."
Luaan
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.