주소 0000000C는 특별한 주소입니까?


32

프로그래밍 할 때 가끔 문제가 발생합니다. 실수하여 프로그램이 잘못된 주소에서 읽으려고합니다.

저런 예외들과 같은 점이 눈에 띄는 것 중 하나는 다음과 같습니다.

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

이제 많은 오류 로그가 표시되며 눈에 띄는 것은 : 0000000C입니다. 이것이 "특별한"주소입니까? 읽기가 잘못된 다른 액세스 위반이 있지만 주소가 무작위로 보이지만 완전히 다른 상황에서 계속 나타납니다.


1
나는 또한 눈치 챘을 0000000C것입니다 방법 보다 더 일반적인 00000008, 그러나 대답 것도 주소로 보이지 않는다 모든시 : /
음매 오리

2
아마 그 System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData입니다 12=0x0C오프셋이 더 일반적인 이유입니다.
Mark Hurd

1
@MarkHurd 그거 무섭다. 실제로 관리되지 않는 응용 프로그램이 너무 많아 의도적으로 .NET 문자열을 읽거나 쓰므로 액세스 위반의 주요 원인이 될 것이라고 생각하십니까?
Luaan

답변:


57

00000000특수 주소 (널 포인터)입니다. 0000000Cnull 포인터에 오프셋 12를 추가 할 때 얻는 것입니다. 누군가 z실제로 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
또는 일부 작은 정수 값이 포인터 인 것처럼 실수로 역 참조 되었기 때문일 수 있습니다. 작은 값은 큰 값보다 훨씬 일반적이므로 0x43FCC893이 아닌 0X0000000C와 같은 불법 주소를 생성하는 경향이 있습니다.
Kilian Foth

3
내가이 질문을 한 이유는 0000000C가 다른 주소와 비교하여 자주 돌아 오기 때문입니다. 왜 오프셋 12가 오프셋 4, 8 또는 16보다 더 일반적인 크기입니까?
Pieter B

5
추가 조사 후이 답변은 완전히 정확합니다. 내 소스에서 클래스의 "태그"속성은 광범위하게 사용됩니다 (좋은 또는 나쁜 처리해야합니다). 내 경우의 태그 속성은 저수준 기본 클래스의 일부이며 항상 해당 오프셋에서 만들어졌습니다.
Pieter B

1
훌륭한 지적입니다. 아마도 null 포인터 경우가 다루어졌지만 null 포인터 ++는 단지 정상적인 (이 경우 유효하지 않은) 주소이므로 액세스 할 때만 실패합니다.
Neil

8
@Leushenko 예, 메모리 보호는 일반적으로 전체 페이지에서 작동하며 0 만 잡을 수 있었더라도 다음과 같은 주소를 보호하는 것이 좋습니다. OP의 경우).

11

윈도우에서의 역 참조 불법 전체 성과 페이지 즉, 프로세스 메모리의 첫 번째 또는 마지막 64 킬로바이트 (범위 0x000000000x0000ffff0xffff00000xffffffff32 비트 어플리케이션에서).

이것은 널 포인터 또는 인덱스를 역 참조하는 정의되지 않은 동작을 널 배열로 트랩하는 것입니다. 페이지 크기는 64 KiB이므로 Windows는 첫 번째 또는 마지막 페이지에 유효한 범위가 할당되지 않도록해야합니다.

이것은 어떤 값 (유효 주소 포함)을 가질 수있는 초기화되지 않은 포인터로부터 보호되지 않습니다.


7
Windows는 실제로 그렇게 할 수 없습니다. 페이지 테이블은 x86에 의해 정의되고 요구되는 구조이며 작은 페이지는 4KB로 고정됩니다. 그것은 돌로 설정됩니다 (보다 정확하게는 실리콘으로). 64KB는 아마도 편의를위한 것입니다.
ElderBug

12
이 경우 2의 거듭 제곱 크기가 관련되어 있기 때문에 65kB 대신 64KiB를 작성하고 싶습니다.
코드 InChaos

4
64KB 범위는 NT의 Aplha 버전과는 다릅니다. 그리고 페이지 크기가 아니라 할당 세분성입니다. blogs.msdn.com/b/oldnewthing/archive/2003/10/08/55239.aspx
shf301

3
@CodesInChaos : 대문자 "M", "G"및 "T"가 모호하지만 "^"를 10 ^ 3에 사용하고 "K"를 2 ^ 10에 사용하지 않을 이유가 없습니다.
supercat 2019 년

2
@MooingDuck 예, 그렇기 때문에 작은 페이지를 정밀하게 만들었습니다. 대부분의 x64 CPU는 1GiB 페이지도 지원합니다. 내가 아는 한, Windows는 특수 API로 할당되지 않는 한 항상 4KB 페이지가있는 페이지입니다.
장로

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액세스 위반이 발생합니다. VMT 오프셋. 이 특정 값의 유병률은 유사한 상속 패턴을 갖는 클래스를 제공하거나 특정 인터페이스 (DirectX 게임과 같은 일부 응용 프로그램 클래스에서 가능)를 제공하는 공통 API와 관련이있을 수 있습니다. 이와 같은 간단한 일반적인 원인을 추적하는 것이 가능할 수도 있지만 null 역 참조를 매우 빠르게 수행하는 응용 프로그램을 제거하는 경향이 있습니다.


1
주석을 살펴보면 추측을 상당히 줄일 수 있습니다.
중복 제거기

@Deduplicator 글쎄, 관리되는 .NET 문자열이 수동 포인터 작업이 무서운 안전하지 않은 코드로 사용된다는 생각과 이것이 액세스 위반의 주요 원인이라고 생각합니다. "예, 이것은 완전히 메모리 안전합니다. 걱정하지 마십시오. C #을 사용했습니다. C ++에서 메모리를 수동으로 수정하지만 C #에서는 안전합니다."
Luaan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.