포스트

리눅스 커널의 매크로 함수 offsetof 와 container_of

리눅스 커널 코드에서 자주 보이는 매크로 함수 offsetof와 container_of에 대해 분석한다

리눅스 커널의 매크로 함수 offsetof 와 container_of

커널 매크로 함수 스크린샷 1

1. “offset_of”

위치 : /include/linux/kernel.h

정의 : #define offsetof(type, element) ((size_t)&(((type *)0)->element))

주어진, type 구조체 내에서 주어진 element의 이름을 갖는 멤버 변수의 offset 값을 반환

2. “container_of”

위치 : /include/linux/kernel.h

정의 : #define container_of(ptr, type, member) ({

1
2
3
    const typeof( ((type *)0)->member ) *__mptr = (ptr);   

    (type *)( (char *)__mptr - offsetof(type,member) );})

주어진 ptr을 멤버로 갖는 type 타입 구조체 인스턴스의 주소를 반환

3. 매크로 함수 정의부 부연 설명

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct _object {

    int x;

    int y;

    int z;

} object_t;



object_t obj;

size_t offset = offsetof(object_t, y);

printf("%ld\n", offset); // 4

object_t* ptr_obj = container_of(&obj.y, object_t, y);



// true

if (&obj == ptr_obj) {

puts("equal!"); 

}

(type *)0)->element

매크로 함수들의 정의부를 보면 위와 같이 주소 값 0을 type 데이터 타입에 대한 주소로 변환한 뒤에 그 멤버 변수인 element에 접근 하는 부분이 있다.

얼핏 보면, NULL 포인터를 이용하여 element에 대한 접근을 시도하기 때문에, segmentation fault를 일으킬 것 같지만 실제로 해보면 그렇지 않음을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
object_t* obj = NULL;

size_t offset = (size_t)&(obj->y);



mov    QWORD PTR [rbp-0x28],0x0

mov    rax,QWORD PTR [rbp-0x28] // obj = 0x00

add    rax,0x4 

mov    QWORD PTR [rbp-0x20],rax 

왜냐하면, 위의 disassembly 코드에서 볼 수 있듯이 단순히 멤버 변수 까지의 논리적 주소 상에서의 offset 만 계산하고 연산을 끝내기 때문이다.

결과적으로, 주소 0x00에 접근(read/write) 하지는 않기 때문에 segmentation fault는 발생하지 않는다.

반면,

1
2
3
object_t* obj = NULL;

int data = obj->y;

와 같은 코드의 경우에는 주소 0x00에 접근하여 이를 base로 offset 0x04 위치로 접근 하는 명령문이기 때문에 엑세스 위반으로 segmentation fault가 발생한다.

참고로,

container_of(&obj.y, object_t, y) 명령문은 컴파일러에 의해 다음과 같이 단 한 줄의 명령어로 변환 된다.

1
mov    QWORD PTR [rbp-0x20],0x4
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.