-
Notifications
You must be signed in to change notification settings - Fork 55
libevent 헤더와 라이브러리 버전 차이로 인한 memcached segfault
아래와 같이 빌드한 memcached 사용할 경우, segmentation fault 발생
- libevent 2.1.12 헤더 파일을 이용하여 컴파일하고
memcached.o
파일 생성 - libevent 1.4.13 라이브러리와 링크하여
memcached
바이너리 파일 생성
memcached 바이너리에 링크되는 libevent 라이브러리 파일(libevent-1.4.13.so)은 아래와 같은 event
구조체를 바탕으로 구현되어 있습니다.
struct event {
TAILQ_ENTRY (event) ev_next;
TAILQ_ENTRY (event) ev_active_next;
TAILQ_ENTRY (event) ev_signal_next;
unsigned int min_heap_idx; /* for managing timeouts */
struct event_base *ev_base;
int ev_fd;
short ev_events;
short ev_ncalls;
short *ev_pncalls; /* Allows deletes in callback */
struct timeval ev_timeout;
int ev_pri; /* smaller numbers are higher priority */
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
int ev_res; /* result passed to event callback */
int ev_flags;
};
memcached 바이너리의 컴파일 시 사용된 libevent 헤더 파일(event.h)에는 아래와 같은 event 구조체가 정의되어 있습니다.
struct event {
struct event_callback ev_evcallback;
/* for managing timeouts */
union {
TAILQ_ENTRY(event) ev_next_with_common_timeout;
int min_heap_idx;
} ev_timeout_pos;
evutil_socket_t ev_fd;
struct event_base *ev_base;
union {
/* used for io events */
struct {
LIST_ENTRY (event) ev_io_next;
struct timeval ev_timeout;
} ev_io;
/* used by signal events */
struct {
LIST_ENTRY (event) ev_signal_next;
short ev_ncalls;
/* Allows deletes in callback */
short *ev_pncalls;
} ev_signal;
} ev_;
short ev_events;
short ev_res; /* result passed to event callback */
struct timeval ev_timeout;
};
-
event 구조체 초기화
// struct event_base *main_base = event_base_new(); /* ... */ /* new connection */ event_set(&c->event, sfd, event_flags, event_handler, (void *)c); event_base_set(main_base, &c->event);
캐시 서버 구동 후 새로운 connection이 생성되면, memcached는 위와 같이 libevent의 함수를 호출하여 event 구조체를 초기화하게 됩니다.
이때, 함수의 구현은 링크된 shared object(v1.4.13) 내부의 구현을 사용하게 되므로
c->event
는 1.4.13 version의 구조에 맞는 값을 가지게 되고, memcached가 이해하고 있는 event 구조체(v2.1.12)와 mismatch 발생합니다. -
update_event()
함수 로직struct event_base *base = c->event.ev_base; // invalid value event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c); event_base_set(base, &c->event); /* SEGMENTATION FAULT */
위와 같이 event 구조체가 잘못 초기화된 상황에서 일정 개수(
-R
option에 의해 결정, default=20)의 요청을 처리하는 시점에 memcached는 event를 새로 설정하는 과정에서event_base_set()
의 event_base 인자 값으로c->event.ev_base
값을 넘기게 됩니다.memcached는 2.1.12 version의 헤더를 바탕으로 event 구조체를 해석하므로,
c->event.ev_base
위치에는 event_base 주소가 아닌 다른 값이 위치한 상태이고, 이를 event_base 주소로 넘기면서 segfault 문제가 발생하게 됩니다.
설치 과정 중 ./configure
수행 시 인자에 따라서 컴파일에 사용할 헤더와 라이브러리를 탐색하게 됩니다.
이 때 탐색 우선순위는 다음과 같습니다.
-
--with-libevent
또는--with-zookeeper
에 의해 지정된 경로 -
(system path)
: gcc 기본 참조 경로 -
--prefix
에 의해 지정된 경로
이를 바탕으로 문제가 발생하는 시나리오는 다음과 같습니다.
-
사전 조건
-
libevent
와zookeeper
가 동일 경로에 설치된 상태<target_path> ├── include │ ├── event.h (libevent 1.14.3) │ └── zookeeper │ └── zookeeper.h └── lib ├── libevent-1.14.3.so └── libzookeeper_mt-3.5.9-p3.so.2
-
libevent
가 system path에 이미 설치된 상태<system_path> ├── include │ └── event.h (libevent 2.1.12) └── lib └── libevent-2.1.12.so
-
-
./configure --enable-zk-integration --prefix=<target_path>
명령 수행- libevent 설치 위치 탐색
-
--with-libevent
option 설정하지 않았으므로 pass - gcc 기본 탐색 경로에서 libevent 발견
- gcc 컴파일 옵션 추가하지 않음
-
- zookeeper 설치 위치 탐색
-
--with-zookeeper
option 설정하지 않았으므로 pass - gcc 기본 탐색 경로에서 zookeeper 발견 실패
-
--prefix
위치에서 zookeeper 발견 - gcc 컴파일 옵션 추가
gcc -I <target_path>/include/zookeeper -L <target_path>/lib
-
- libevent 설치 위치 탐색
-
make
수행
gcc는-I
option 또는-L
option이 지정된 경우 해당 경로를 우선 탐색하고, 발견하지 못한 경우에 한해 기본 경로 탐색- 헤더
-
-I
위치에서 zookeeper 헤더 발견 / libevent 헤더 발견 실패 - system path에서 libevent 2.1.12 버전의 헤더 발견
-
- 라이브러리
-
-L
위치에서 zookeeper.so 발견 / libevent.1.4.13.so 발견
-
- 헤더
-
결과적으로 libevent의 헤더는
./configure
수행 결과와 동일하게 system path를 사용하지만,
라이브러리는 zookeeper에 의해 추가된-L
option의 영향을 받아 prefix 경로의 라이브러리를 사용하게 됩니다.
-
./configure
수행 시--with-libevent=<path>
option 사용-
--with-libevent
설정하는 경우 system path보다 지정된 경로를 먼저 탐색하고-I
와-L
옵션을 추가하기 때문에 문제 발생하지 않게 됩니다.
-
- 다음 릴리즈 버전(>1.13.4) 사용
- 이번 문제 확인 이후로 라이브러리 탐색 순서를 변경하였습니다.
- 기존:
--with-libevent=<path>
=>(system path)
=>--prefix=<path>
- 변경:
--with-libevent=<path>
=>--prefix=<path>
=>(system path)
- 기존:
- 따라서, prefix에 libevent가 설치되어 있는 경우 system path를 탐색하지 않습니다.
- 이번 문제 확인 이후로 라이브러리 탐색 순서를 변경하였습니다.