커널 모드 프로그래밍을 하다 보면 CONTAINING_RECORD 매크로를 자주 보게 됩니다. 이 매크로는 자기 자신을 포함하고 있는 상위 구조체의 주소를 얻기 위해 사용합니다.

간단한 예제를 살펴보겠습니다.

typedef struct _HEADER {
    ULONG Hello;
    ULONG World;
    ULONG A;
    ULONG B;
    ULONG C;
    PVOID Body;
} HEADER, *PHEADER;

이렇게 HEADER라는 구조체가 있습니다. Hello, World, A, B, C는 HEADER 구조체의 속성이고, 마지막 Body는 가변 크기의 멤버를 담고 있습니다.

typedef struct _HELLO_BODY {
    ULONG Hello;
} HELLO_BODY, *PHELLO_BODY;

방금 전 Body는 이런 모양의 구조체를 다시 포함하고 있습니다.

그럼 실제로 사용해보겠습니다.

VOID
SetHeader(
    PVOID BodyPointer)
{
    CONTAINING_RECORD(BodyPointer, HEADER, Body)->A = 0xAAAAAAA;
}

VOID
ContainingRecord()
{
    PHEADER header = ExAllocatePool(NonPagedPool, sizeof(HEADER) + sizeof(HELLO_BODY));
    PHELLO_BODY hellobody;

    hellobody = (PHELLO_BODY)&header->Body;

    SetHeader(hellobody)

    ExFreePool(header);
}

여기서 ExAllocatePool로 header에 메모리를 할당 할 때 HEADER 크기만 하지 않고 HELLO_BODY의 크기도 더해줬습니다. 그래서 HEADER, HELLO_BODY는 따로 떨어져 있지 않고 바로 이어져있습니다.

HEADER의 Body가 PVOID라 주소를 담고 있을것 같지만 HELLO_BODY가 이 공간을 차지하고 있기 때문에 HELLO_BODY의 첫번째 멤버의 값이 표시될 것입니다.

hellobody에는 header의 Body의 주소를 넣어주었기 때문에, header의 Hello, World, A, B, C는 액세스 할 수 없습니다.

SetValue 함수 안에서는 header의 존재를 알 수 없기 때문에 이럴때 CONTAINING_RECORD 매크로를 사용합니다. hellobody가 HEADER 구조체의 Body에 연결되어 있다고 한다면 CONTAINING_RECORD(BodyPointer, HEADER, Body)로 하면 현재 HELLO_BODY를 포함하고 있는 HEADER 구조체의 주소를 알아 올 수 있습니다.

HEADER 구조체의 주소를 알아왔기 때문에 HEADER의 멤버 A에도 접근이 가능합니다.

위의 예보다 더욱 많이 사용되는 곳이 연결 리스트(Linked list)를 순회하면서 참조할 때입니다. 간단한 사용예를 들면 아래와 같습니다.

for (links = queue.Flink; links != &queue; links = links->Flink)
{
    tempdata = CONTAINING_RECORD(links, DATA, DataLinks);
    DbgPrint("data, Hello : %d World : %d\n", tempdata->Hello, tempdata->World);
}

CONTAINING_RECORD 매크로는 ntdef.h에 정의되어 있습니다.

#define CONTAINING_RECORD(address, type, field) \
                         ((type *)((PCHAR)(address) - \
                         (ULONG_PTR)(&((type *)0)->field)))

매크로가 캐스팅을 많이 해서 복잡해 보이지만 실제로는 매우 간단합니다.

예제와 연결해서 설명해보면 address에는 hellobody(BodyPointer), type에는 HEADER, field에는 Body가 들어갑니다. 먼저 hellobody의 주소에서 Body의 위치를 빼주는 것입니다.

(ULONG_PTR)(&((type *)0)->field)))에서 0을 HEADER * 형으로 캐스팅 하였기 때문에 HEADER 구조체상에서 Body의 위치가 나오게 됩니다.

이렇게 해서 Body의 위치만큼 앞으로 간다면 HEADER 구조체의 선두, 즉 HEADER 구조체의 주소를 알 수 있게 되는 것입니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

29 October 2007