공유 라이브러리 함수를 호출하려고 할 때 중간에 가로채면서(!) 내가 만든 다른 함수를 실행하게 하는 방법이다.
Interpositioning 은 Compile time / Link time / Load & run time에서 모두 가능하다.
Interpositioning을 하면 보안과 monitoring, profiling에 좋다고 한다. 보안은 왜 그런 지 모르겠지만, 함수 프로파일링에는 공감 : 함수가 몇 번 호출되는 지, malloc tracing하는 데 도움이 된다.
1) Compile time에서의 interpositioning
전처리기를 이용한 interpositioning으로 생각하면 된다.
헤더 내에 #define malloc(size) mymalloc(size) 와 같이 선언을 해버리면 c코드에서 이 헤더를 include했을 때 전처리기를 이용해서 malloc을 mymalloc으로 바꿔버림. 이걸 이용해서 libc.so에 있는 malloc 대신 내가 만든 mymalloc을 실행하는 것이 compile time에서 가능하다.
예시 파일 : 총 3개(int.c, malloc.h, mymalloc.c)가 있다.
int.c 는 평범하게 헤더를 include하고 malloc, free를 하는 main 코드이며
malloc.h는 #define malloc(size) mymalloc(size) 이런식으로 선언해두고 밑에 mymalloc(size)함수의 선언을 해둔 코드,
그리고 mymalloc.c은 컴파일 할 때 -DCOMPILETIME이라는 옵션이 있으면 mymalloc함수를 정의하는 코드이다.( #ifdef, #endif 전처리문(모두 컴파일 전에 처리됨)을 이용)
** 이때 컴파일, 링킹하는 방법은 다음과 같다.
gcc -Wall -DCOMFILETIME -c mymalloc.c
gcc -Wall -I. -o intc int.c mymalloc.o
이렇게 해라고 되어 있는데 생소한 gcc 옵션에 대해 살펴보면
(1) -Wall 은 “모든 모호한 코딩에 대해서 경고를 보내는 옵션” 이라고 한다.
(2) -DCOMFILETIME 은 “-D”라는 옵션으로 #define을 할 수 있는 방법이다. 이걸 이용하면 #ifdef, #endif를 사용할 때 그전에 #define 하는 걸 gcc에서 command로 지시할 수 있다.
(3) -I.라는 건 “-I”가 내가 “”형태로 include한 헤더파일의 위치를 구체적으로 명시하는 그런 옵션인데 -I.는 아마도 현재 폴더에서 찾으라는 의미인 것 같다.
2) Link-time에서의 interpositioning
리눅스 정적 링커는 --wrap 플래그를 통해 link interpositioning을 지원한다.
--wrap,malloc이라고 하면 기존의 심볼 malloc로의 참조는 __wrap_malloc으로 되고, 심볼 __real_malloc의 참조가 심볼 malloc으로 결정된다. 사용방법은 -Wl,option이다. option을 링커에 전달한다고 생각하면 된다.
실행 예시 :
gcc -DLINKTIME -c mymalloc.c
gcc -c int.c
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intl int.o mymalloc.o
3) Run-time에서의 interpositioning
런타임에 함수 호출을 가로채는 방법인데, 동적 링커의 LD_PRELOAD 환경변수를 활용한다. 이 환경변수에 공유라이브러리가 설정되어 있으면 동적 링커는 제일 먼저 LD_PRELOAD에 설정된 라이브러리를 먼저 검사한다고 한다. 즉 LD_PRELOAD에다가 내가 만든 다이나믹 링커를 넣어주면 libc.so에 있는 malloc 대신 내가 만든 malloc 먼저 검사하니까 내 함수를 호출 할 수 있다.
이 때, 내 malloc 내에서 libc.so의 malloc을 실행하는데 그러면서 dlsym을 통해 libc.so의 malloc을 함수포인터로 받아야한다.
** dlsym이란? 동적 라이브러리(.dll)를 열어서 symbol의 시작 번지를 return하는 함수이다.
실행 예시 :
gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
gcc -o intr int.c
LD_PRELOAD=./mymalloc.so ./intr
보통은 dlopen과 동시에 쓰는 것 같은데 dlsym(RTLD_NEXT, “malloc”)이라고 쓰면 현재 라이브러리 다음으로, 검색했을 때 나오는 함수를 찾으란 의미라고 한다. (즉, LD_PRELOAD를 쓰면 현재 library(mymalloc3.so) 다음에 나오는 libc.so에 있는 malloc을 저 함수가 찾아주는 것.)
오늘 공부하면서 낯설었던 것은 함수 포인터를 쓰는 방법이었다. void (*fp)(); 이런 식으로 써주면 맨 앞이 반환 값의 자료형, 함수포인터 이름, 매개변수 이런 식으로 되는 것. 함수포인터는 괄호를 붙여줘야하는 듯하다. 예시에서는 (void *)가 malloc의 반환형이니까 그렇게 한 것 같고 (*mallocp)라는 malloc pointer를 선언한듯하다.
** 함수 포인터를 쓰는 이유에 대해서는 좀 더 공부가 필요할 듯하다. 일단은 함수도 사실은 변수와 마찬가지로 메모리 상의 특정 지점이니까 자연스럽게 포인터로 쓸 수 있다고 받아들이자.
참고
코드 : https://koyo.kr/post/csapp-linking-2/
'Computer Systems A Programmer's Perspective (3rd Edition)'
'컴퓨터 사이언스' 카테고리의 다른 글
VS CODE 원격 접속 시 에러 해결 방법 (0) | 2020.08.06 |
---|---|
[우당탕탕 웹개발 일기] CSS & HTML & Javascript & Django의 늪 (0) | 2020.06.20 |
협업을 위한 최소한의 GIT 설정 방법 (0) | 2020.06.03 |
DFT 공부하다가 궁금했던 점 정리 (0) | 2020.04.13 |
댓글