Linux Tutorial #14 디버깅 & 성능 분석 (Kprobes)

문연수·2021년 5월 28일
0

Linux Tutorial

목록 보기
15/25
post-thumbnail

Kprobes 는 리눅스 커널의 디버깅과 성능 측정에 사용할 수 있는 좋은 인터페이스이다. Kprobes 는 커널 루틴에 동적으로 중단점(breakpoint)을 삽입해 디버그와 성능 정보를 수집한다. 분석하고 싶은 커널 코드에 트랩(trap)을 설치하면 중단점 도달 시, 설정해둔 처리 함수(handler) 를 실행시킬 수 있다.kprobes 는 어떤 코드에도 삽입 가능하고, kretporbs 는 특정 함수가 반환될 때 동작한다. 백날 설명해봐야 한번 보는 것만 못하다. 이번 장에서는 예제 프로그램을 빌드하여 모듈을 삽입하고, 그 결과를 확인 해보겠다.

1. Kprobes 인터페이스 지원 여부

Kprobes 를 사용하기 위해서는 만족해야 하는 몇 가지 조건이 있다. 대부분의 독자는 아래의 사항을 만족할 것이므로 큰 걱정할 필요는 없다.

CPU 아키텍쳐

Kprobes 인터페이스는 아래의 CPU 아키텍쳐에서 동작한다.

  • i386
  • x86_64
  • ppc64
  • ia64
  • sparc64
  • arm
  • ppc
  • mips
  • s390
  • parisc

필자는 x86_64 를 사용하므로 지원 대상이다. 독자의 아키텍쳐 역시 대부분 위 목록 중 하나에 해당할 것이므로 걱정할 필요 없다.

커널 설정 상수

빌드할 때 사용했던 .config 파일은 아래의 상수가 전부 활성화되어야 한다. 일부러 설정을 건드린 것이 아니라면 대체로 활성화 되어 있을테니 걱정할 필요 없다. 만일 비활성화 되어 있다면 활성화( = y)로 변경하여 다시 빌드하면 그만이다. 상수는 아래와 같다:

CONFIG_KPROBES
CONFIG_MODULES
CONFIG_MODULE_UNLOAD
CONFIG_KALLSYMS
CONFIG_KALLSYMS_ALL
CONFIG_DEBUG_INFO

2. Kprobes 함수 목록

1번 의 설정을 모두 만족한다면 Kprobes 를 쓸 준비가 된 것이다. 우리가 사용할 Kprobes 함수들은 include/linux/kprobes.hkernel/kprobes.c 에 선언 및 정의되어 있다. 공식 문서 또한 상세하게 잘 설명해놨으므로 이를 참조해도 좋을 것이다. 필자는 공식 문서와 같이 아주 디테일하게 설명하진 않고 간단하게 스케치만 해볼 것이다.

kprobe 관련 함수

#include <linux/kprobes.h>

// kprobe 를 등록/해제 하는 함수
int register_kprobe(struct kprobe *p);
void unregister_kprobe(struct kprobe *p);

// 중단점 앞에서 실행되는 처리 함수
typedef int (*kprobe_pre_handler_t) (struct kprobe *, struct pt_regs *);

// 중단점 끝에서 실행되는 처리 함수
typedef int (*kprobe_post_handler_t) (struct kprobe *, struct pt_regs *,
                                      int trapnr);

// 실행 중 예외를 처리하는 함수
typedef int (*kprobe_fault_handler_t) (struct kprobe *, struct pt_regs *,
				       int trapnr);

// kprobe 동작 활성/비활성화 함수
int disable_kprobe(struct kprobe *kp);
int enable_kprobe(struct kprobe *kp);

kretprobe 관련 함수

#include <linux/kprobes.h>

// kretprobe 를 등록/해제 하는 함수
int register_kretprobe(struct kretprobe *rp);
void unregister_kretprobe(struct kretprobe *rp);

// kretprobe 동작 활성/비활성화 함수
int enable_kretprobe(struct kretprobe *rp);
int disable_kretprobe(struct kretprobe *rp);

kprobe 는 위에서 본 것처럼 두 가지(kprobe, kretprobe) 종류가 있다. kprobe 는 어떠한 커널 코드에도 설치할 수 있는 반면,kretprobe (혹은 return probe 라고 불리는) 는 특정 함수가 반환할 때 동작한다.

3. 샘플 코드 빌드

리눅스 커널은 kprobe 에 대한 샘플 소스 코드를 제공하므로 이를 빌드해서 그 내용을 테스트 해볼 것이다. samples/kprobes/ 경로에서 그 내용을 확인할 수 있다. kprobe 샘플 코드는 모듈의 형태로 되어 있다. 따라서 모듈을 빌드하고 삽입하는 과정을 통해 그 결과를 확인할 수 있다. 이는 9 - 10 장 커널 모듈 프로그래밍 에서 설명했으므로 자세히 설명하진 않을 것이다. Makefile 은 아래와 같이 작성하면 된다:

4. 샘플 코드 분석: kprobe_example.c

가장 먼저 kprobe_example.c 파일에 대해 분석해보겠다. kprobe_initkprobe_exit 은 모듈 삽입과 삭제 시 실행되는 함수이다. 앞서 설명했듯이 kprobe 를 동작시키기 위해서는 register_kprobe 함수를 호출해서 트랩을 설치해야 한다. register_kprobekp 구조체의 주소를 매개변수로 받아서 트랩을 설치한다. 위의 3 행은 각각 트랩 발동 시(실행 전), 발동 후(끝난 후), 발동 중(예외)에 대한 처리를 하는 함수들이다.

그런데 뭔가 이상한 생각이 들지 않는가? 분명 트랩 시 호출되는 처리 함수는 kp 구조체에 담았지만, 정작 가장 중요한 트랩이 보이질 않는다. 이는 상단의 kp 구조체를 선언함과 동시에 초기화 한다.

보는 것처럼 struct kprobe 구조체는 .symbol_name 멤버 변수에 커널 코드의 심볼(함수) 를 저장하고, 상황에 맞춰 구조체에 등록된 처리 함수(handler) 를 호출하게 된다.

전처리(handler_pre), 후처리(handler_post) 함수는 심볼의 이름(p->symbol_name) 과, 프로브 혹은 함수 주소(p->addr), 명령어 주소(ip), 플래그(flags) 등을 출력한다.
실제 코드에는 아키텍쳐별 출력 구문 나열되어 있었는데 필자는 x86_64 를 사용하기 때문에 해당 출력 구문만 남기고 모두 제거했다.

필자가 실행한 결과는 아래와 같았다:

(중략...)

5. 샘플 코드 분석: kretprobe_example.c

kretprobe_example.c 역시 이전 예제와 구조적으로 다르진 않지만 전달하고 반환받는 구조체에서 약간의 차이를 보인다.

kretprobe_init 함수는 모듈 등록 시 호출되는 함수로, 중단점을 설치한다. kretprobe_exit 함수는 모듈 삭제 시 호출되는 함수로 등록했던 중단점을 해제한다.

entry_handler 는 중단점 진입 전에 실행되는 함수이고 ret_handler 는 중단점 반환 후 실행되는 함수이다. entry_handler 는 인자로 전달된 *ri 에 현재 시간을 저장한다.

kprobe 와는 달리 kretporbe 는 동시적으로 호출되는 경우, 각각의 probe 는 독자적인 데이터 공간을 가진다. *ri 는 다른 probe 들과는 공유하지 않는 독자적인 데이터 공간을 가르키는 변수이다. 따라서 해당 공간에 현재 시간을 저장할 수 있다. 위 사진에서 나온 struct kretproberegister_kretprobe 에 전달하는 구조체로 각 probestruct my_data 크기의 독자적인 데이터 공간을 가지며, 최대 20 개의 probe 를 병렬적으로 처리할 수 있음을 의미한다.

마지막으로 ret_handler 는 중단점의 반환 값을 읽어 들이고 현재 시간과 entry_handler 에서 받아왔던 시간의 차 (심볼 수행에 걸린 시간) 를 출력한다. 출력 결과는 아래와 같다:

(중략...)

출처

[사이트] https://www.kernel.org/doc/Documentation/kprobes.txt
[사이트] https://elixir.bootlin.com/linux/v3.4/source/arch/x86/include/asm/ptrace.h
[사이트] https://en.wikipedia.org/wiki/FLAGS_register
[책] 리눅스 커널 소스 해설: 기초 입문 (정재준 저)

profile
2000.11.30

0개의 댓글