앞서 지나갔던 내용인
"실제로 수많은 매크로를 통해 vtable
에서 함수가 호출되는 과정은 어떻게 될까?"
에 대해서 알아보도록 하겠다.
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
(*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
+ offsetof(TYPE, MEMBER)))
#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
앞서 fread함수 분석 글에서 확인하려다가 실패(?) 한 매크로들이다.
하나하나 천천히 따라가 보자.
그림으로 나타내면 다음과 같다.
확실히 그림으로 나타내니까 보기가 편한 듯.
아래에서부터 한 단계씩 위로 올라가 보자.
먼저 _IO_MEMBER_TYPE
매크로인데, 첫 번째 인자로 들어온 구조체의 두 번째 인자로 들어온 멤버변수의 자료형을 의미한다.
__typeof__
연산자가 나오는데, 이는 뒤에 인자로 들어온 객체 or 자료형의 expression의 값을 가진다._IO_jump_t
라는 expression에 해당한다.__typeof__
에 대해 자세한건 https://velog.io/@dandb3/typeof-%EC%97%B0%EC%82%B0%EC%9E%90-%ED%82%A4%EC%9B%8C%EB%93%9C)그 다음으로 offsetof매크로인데, 이름 그대로 첫 번째 인자로 들어온 구조체와 두 번째 인자로 들어온 멤버변수간의 offset을 의미한다.
정리하면 다음과 같다.
IO_validate_vtable
에 대해서 알아보자. glibc 2.27 버전으로 가져왔다.static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
자세한 분석은 나중에 하기로 하고, 일단 이 함수가 정상적으로 종료된다면 들어온 인자 그대로 리턴해주는 것을 알 수 있다.(((_IO_FILE_plus *) FP)->vtable->FUNC) (FP, DATA, N)
즉, 각 _IO_FILE
구조체에 해당하는 vtable에 저장된 FUNC라는 함수를 호출하게 되는 것이다.IO_validate_vtable
함수의 검사를 통과해야만 제대로 함수가 호출된다.