- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
커널 모드 프로그래밍을 하다 보면 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 공유는 허용합니다.