리눅스 커널의 매크로 함수 offsetof 와 container_of
리눅스 커널 코드에서 자주 보이는 매크로 함수 offsetof와 container_of에 대해 분석한다
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
