리눅스 개발자를 위한 디버깅 기법 ⑥

들어가는 말커널 디버그 과정에서 printk를 사용할 경우 원하는 조건을 출력하는 과정에서 매번 커널을 새로 컴파일해야 하는 불편함이 있다. UML(User Mode Linux)와 같은 가상 리눅스 환경을 쓰지 않는다면 커널 컴파일 후에 새로 시작해야 하므로 여기에 소모되는 시간도 결코 만만치 않다. 커널 디버거를 사용하면 이런 문제점을 해결할 수는 있지만, 조건에 맞춰 정지점이나 중단점을 설정하는 작업이 따라온다. 하지만, 조건에 맞춰 디버깅을 진행하기 어려운 경우도 있다.이런 문제점을 처리하는 해결사가 바로 동적 프로브이다. 동적 프로브는 실행 중인 프로그램의 특정 위치(예: 함수 진입 지점)에 소프트웨어로 구현한 탐침을 삽입한 다음 해당 위치를 수행하는 시점에서 필요한 디버깅 작업을 수행하도록 도와준다.이번 회에서는 커널 프로브와 동적 프로브를 설치하고 운영을 준비하는 방법을 시작으로 프로브와 컴파일러라는 양대 사용자 영역 유틸리티 사용법과 동작 원리를 살펴본다. 지면 관계상 핵심적이고 기본적인 내용만 다루도록 하겠다.설치 방법커널/동적 프로브 설치는 크게 세 단계로 나뉜다. 가장 먼저 커널 패치와 모듈 빌드가 있고, 다음으로 사용자 영역 프로그램인 dprobes 컴파일이 따라 나온다. 마지막으로 프로브 제작을 위한 전용 컴파일러인 dpcc 컴파일까지 진행하면 설치가 끝난다.일반적인 오픈 소스 소프트웨어와 마찬가지로 설치 과정이 그렇게 단순하지는 않으므로 정신을 바짝 차려서 진행해야 한다. 이번 연재에서는 페도라 코어 4(FC4)를 기준으로 설명하고 있으므로, 다른 배포판을 사용하는 독자라면 설치 과정에서 일부 절차를 각자 환경에 맞춰 변경해야 한다.i) 커널 모듈준비물로 커널 2.6.9와 kprobes/dprobes 패치 2.6.9가 필요하다. 이 글을 쓰고 있는 시점에서 최신 커널인 2.6.17 계열 패치는 아직 나오지 않았으므로, 커널/동적 프로브를 사용할 경우 당분간은 커널 2.6.9에 만족해야 한다.커널 2.6.9는 http://www.kernel.org에서, kprobes/dprobes 패치인 kprobes-dprobes-2.6.9-full.patch.bz2는 http:// dprobes.sourceforge.net/downloads/에서 내려 받기 바란다. 둘 다 /usr/src에 내려 받았다면, 다음과 같은 명령을 내려서 커널을 풀고 패치를 가하기 바란다. 커널 버전에만 주의하면 별다른 어려움 없이 설치를 끝낼 수 있다.# cd /usr/src# gtar xvpfj linux-2.6.9.tar.bz2# cd linux-2.6.9# bzcat ../kprobes-dprobes-2.6.9-full.patch.bz2 | patch -p1# cd ..설치가 끝났다면 환경 설정 항목에 들어가서 Kernel hacking 아래 Kprobes (CONFIG_KPROBES)와 Dynamic Probes-(CONFIG_DPROBES)를 선택한다. menuconfig 대신에 xconfig를 사용해도 무방하다. 편의상 여기서는 CONFIG_DPROBES를 모듈(m)로 선택한다.# mv linux-2.6.9 linux-2.6.9-kprobes# make menuconfigii) dprobes 유틸리티dprobes 유틸리티는 dpcc 컴파일러로 컴파일한 소프트웨어 탐침을 삽입하고 제거하고 확인하는 사용자 영역 응용 프로그램이다. 컴파일 과정에서 glibc 버전에 따라 문제가 발생하는 경우가 있어서 간단한 패치 방법을 적어놓았다. dprobes 유틸리티는 http://dprobes.sourceforge.net/downloads/에서 내려 받기 바란다.$ tar xvpfz dprobes-v3.6.5.tar.gz$ cd dprobes-v3.6.5/cmd이 디렉터리에서 sym.c 파일을 열어 struct sec을 struct bfd_section으로 수정해야 페도라 코어(FC4)에서 컴파일이 가능하다(라이브러리 판올림에 따라 bdf 관련 자료 구조가 변경되었으므로 여기에 맞춰 수정이 필요하다). 또한 인클루드 파일을 제대로 찾기 위해 Makefile을 열어 INCLUDE_DIRS = -I/usr/src/linux-2.6.9-kprobes/include -I$(ARCH)로 변경해야 한다. 간단한 수작업 패치 후에 마지막으로 make 명령을 내려서 dprobes 유틸리티를 컴파일하자.$ makeiii) dpcc 컴파일러dpcc 컴파일러는 C와 유사한 dprobes 문법을 사용해서 만든 소프트웨어 탐침을 RPN(Reverse Polish Notation)과 유사한 결과물로 변환한다. dprobes 유틸리티를 제대로 컴파일하려면 반드시 gdb부터 가져와서 환경 설정과 컴파일까지 마쳐야 한다. 여기서 주의할 사항은 gdb-5.2.1을 사용해야지 페도라 코어(FC4)에서 정상 동작한다는 사실이다.gdb-5.1을 사용할 경우 세그먼테이션 결함이 일어나며 dpcc 컴파일러가 오동작한다. dpcc 컴파일러는 dprobes-v3.6.5.tar.gz에 기본적으로 들어있으므로 별도 패키지를 내려 받을 필요는 없으며, gdb 5.2.1 패키지는 http://ftp.gnu. org/gnu/gdb/에서 내려 받기 바란다.$cd /home/project$ tar xvpfz gdb-5.2.1.tar.gz$ cd gdb-5.1$ ./configure$ make$ cd /home/project/dprobes-v3.6.5/dpcc/dpcc-1.0.1$ ./gen-grammar$ ./patch-dpcc --force --with-gdb-src=/home/project/gdb-5.2.1$ ./configure --with-gdb-src=/home/project/gdb-5.2.1gen-grammar 스크립트는 ANSI C 구문 해석을 위한 파일 두 개를 가져오며, patch-dpcc 스크립트는 ANSI C 구문 해석 파일을 패치하고, gdb 소스 코드 일부를 패치한다. gdb-5.2 이상을 사용할 경우 patch-dpcc에 --force 옵션을 반드시 붙여야 한다. 설치 과정이 조금 복잡하므로 차근차근 단계를 밟기 바란다.동작 테스트백문이 불여일견이라는 말이 있다. 프로브 동작원리가 감이 오지 않는 분을 위해 실제 예를 통해 설명하겠다. 커널 모듈 적재, 디바이스 파일 생성, dpcc를 사용한 컴파일, dprobes를 사용한 프로브 모듈 적재, 프로브 점화라는 단계를 밟아서 최종 결과를 확인하면 과정을 실제 예제를 통해 보여주겠다.i) kprobes/dprobes 커널 모듈 적재가장 먼저 kprobes/dprobes 커널 모듈을 적재해야 한다.# cd /usr/src/linux-2.6.9-kprobes/drivers/dprobes/# insmod dp.koii) 디바이스 파일 생성다음으로 dprobes 사용자 영역 유틸리티가 통신할 디바이스 파일을 생성한다. 우선 /proc/devices 파일을 열어서 dprobes 주 번호를 확인하자. 예에서는 254로 할당되어 있음을 알 수 있다. 주 번호 254인 문자 디바이스를 만들면 작업이 끝난다.# cat /proc/devicesCharacter devices: 1 mem …180 usb216 rfcomm254 dprobes# mknod /dev/dprobes c 254 0또는 다음과 같은 간략한 셸 스크립트를 사용하면 된다.# MAJOR=cat /proc/devices | awk '$2=="dprobes" {print $1}'# mknod /dev/dprobes c $MAJOR 0iii) dprobes 유틸리티 실행우선 dprobes 유틸리티를 실행해보자. ii)번 단계인 디바이스 파일을 만들어 놓지 않았다면 버전 번호를 출력하지 않는다.# cd /home/project/dprobes-v3.6.5/cmd# dprobes --versionIBM Dynamic Probes 5.0.0Copyright (c) 2000 - IBM Corporationiv) dpcc 컴파일동적 프로브를 위한 예제 프로그램 하나를 컴파일해 보자. 우선 update_examples 스크립트를 돌려서 타깃 예제 프로그램 디렉터리 경로를 조정한다. 그리고 tut13.dpc 예제를 dpcc 로 컴파일하면 된다. -Iinclude를 붙인 이유는 tut13.dpc에서 string.dph을 인클루드 하기 때문이다.$ cd /home/project/dprobes-v3.6.5/dpcc/dpcc-1.0.1$ ./update_examples$ ./dpcc -Iinclude examples/tut13.dpctut13.rpn이 현재 디렉터리에 생성되면 동적 프로브 삽입을 위한 모든 준비가 끝난다.v) dprobes를 사용한 동적 프로브 삽입마지막으로 동적 프로브를 삽입해보자. dprobes -r -a 명령으로 현재 존재하는 모든 dprobes를 제거한 다음에 -i 옵션으로 동적 프로브를 삽입한다. 이렇게 삽입한 tut13 동적 프로브는 예제 프로그램인 triggerprobe를 실행할 때 점화되므로 triggerprobe를 명시적으로 실행시킨다.# cd /home/project/dprobes-v3.6.5/cmd# ./dprobes -r -aremoved dprobes on all modules# ./dprobes -i /home/project/dprobes-v3.6.5/dpcc/ dpcc-1.0.1/tut13.rpn/home/project/dprobes-v3.6.5/dpcc/dpcc-1.0.1/tut13.rpn:999:8: warning: no newline at end of fileProbes in /home/project/dprobes-v3.6.5/dpcc/dpcc-1.0.1/tut13.rpn successfully prepared for insertion into /home/project/dprobes-v3.6.5/dpcc/dpcc-1.0.1/triggerprobe.# ../dpcc/dpcc-1.0.1/triggerprobeRunning code in triggerprobes.c to trigger probesList elements:elt 0: 0elt 1: 1elt 2: 2elt 3: 3···elt 9: 9alist elt 0: 0···alist elt 9: 9triggerprobe 실행이 끝난 다음 커널 kprobes/dprobes 모듈이 프로브 결과를 출력하는 기본 파일인 /var/log/messages를 확인하자.Jul 5 11:33:48 compile kernel: dprobes(1,0) cpu=0Jul 5 11:33:48 compile kernel: dprobes(1,0)Jul 5 11:33:48 compile kernel: 0x00000000 : 07080055 44332268 0000006f 0000006c : ...UD3"h...o...lJul 5 11:33:48 compile kernel: 0x00000010 : 00000061 00000020 00000022 33445507 : ...a... ..."3DU.Jul 5 11:33:48 compile kernel: 0x00000020 : 0000000706005544 33226900 00003a00 : ......UD3"i...:.Jul 5 11:33:48 compile kernel: 0x00000030 : 00002000 00002233 44550a00 00000706 : .. ..."3DU......Jul 5 11:33:48 compile kernel: 0x00000040 : 00554433 22750000 003a0000 00200000 : .UD3"u...:... ..Jul 5 11:33:48 compile kernel: 0x00000050 : 00334455 66070000 00070600 55443322 : .3DUf.......UD3"Jul 5 11:33:48 compile kernel: 0x00000060 : 78000000 3a000000 20000000 44556677 : x...:... ...DUfwJul 5 11:33:48 compile kernel: 0x00000070 : eeffaa0c 07060055 44332261 00000064 : n.*....UD3"a...dJul 5 11:33:48 compile kernel: 0x00000080 : 00000069 0000006f 00000073 000000 : ...i...o...s...이런 종류의 결과물에 익숙한 사람이라면 16진수 결과 옆에 아스키 문자로 나오는 부분을 읽어서 추측이 가능하지만, 초보자라면 이해하기가 어렵다.이런 문제점을 극복하기 위해 dpcc-1.0.1 디렉터리에 펄로 만든 tailprint를 사용한다. 단, tailprint를 사용하기 위해서는 결과를 조금 수정해서 16진으로 나오는 코드를 떨어뜨린 다음에 한 줄로 표현되어야 한다. 수작업이 귀찮다면 간단한 셸이나 펄 스크립트로 구현하기 바란다. tailprint가 인식하는 결과로 변환한 내역은 다음과 같다. 07과 같은 코드는 모두 7로 표현하고 있음에 주의하자.Jul 5 11:33:48 compile kernel: dprobes(1,0) cpu=0Jul 5 11:33:48 compile kernel: dprobes(1,0) 7 8 0 55 44 33 22 68 0 0 0 6f 0 0 0 6c 0 0 0 61 0 0 0 20 0 0 0 22 33 44 55 7 0 0 0 7 6 0 55 44 33 22 69 0 0 0 3a 0 0 0 20 0 0 0 22 33 44 55 a 0 0 0 7 6 0 55 44 33 22 75 0 0 0 3a 0 0 0 20 0 0 0 33 44 55 66 7 0 0 0 7 6 0 55 44 33 22 78 0 0 0 3a 0 0 0 20 0 0 0 44 55 66 77 ee ff aa c 7 6 0 55 44 33 22 61 0 0 0 64 0 0 0 69 0 0 0 6f 0 0 0 73 0 0 0이렇게 수정한 변환 결과를 tailprint에 입력하면(주의: tailprint 가장 첫 부분에 로그 파일 위치를 지정하는 부분을 필요에 따라 바꾸기 바란다. 기본값은 /var/log/messages이다.), 다음과 같은 문구가 화면에 나타난다.hola 7i: 10u: 7x: 0xcaaffeeadios동작 원리지금까지 숨가쁘게 dprobes 사용 예를 살펴보았다. 설명 순서가 뒤집혔다는 생각이 들지도 모르겠지만, 동적 프로브라는 개념이 일상생활에서 쉽게 찾아보기 어렵기에 구체적으로 어떻게 돌아가는지 전반적인 모습을 보여준 다음에 동작 원리를 설명하는 편이 이해하기 쉽다고 생각한다.우선 동적 프로브 코드인 dpcc-1.0.1/examples/tut13.dpc 내용을 살펴보기로 하자. #pragma에 프로브를 삽입할 코드인 triggerprobe를 지정한다. 앞서 소개한 update_examples 스크립트를 돌리면 자동으로 전체 경로를 설정한다. 다음으로 PROBEPOINT_LOCATION에 triggerprobe에 삽입할 지점을 정의하고, PROBEPOINT_HANDLER에 프로브 콜백 함수를 정의한다. 정의한 함수를 보면 알겠지만, 일반적인 C코드이다. printd(10진 출력), printu(부호 없는 숫자 출력), printx(16진 출력), print(일반 문자열 출력)와 같은 특수한 함수는 string.dph에 정의되어 있으며, 나머지 코드는 표준 ANSI C를 그대로 따른다.tut13.dpc#pragma MODNAME("/home/project/dprobes-v3.6.5-jrogue/dpcc/dpcc-1.0.1/triggerprobe")#pragma JMPMAX(65535)#pragma PROBEPOINT_LOCATION("test_fn")#pragma MODTYPE(user)#pragma PROBEPOINT_HANDLER("starthere")#include "string.dph"int i;int j = 0;unsigned int u=7;unsigned int xval=0x0caaffee;/* Demonstrate print functions. *//* View print output using tailprint. */int test_return(char * str, int a, int b, int c){ int d; int e; int f; d = a; e = b;f = c; printd(str, e); return 10;}void starthere(){ i = test_return("hola %d", 6, 7, 8); printd("i: %d", i); printu("u: %u", u); printx("x: %x", xval); print("adios");}tut13.dpctut13.dpc를 dpcc로 컴파일하면 tut13.rpn이라는 RPN 방식을 따르는 스택 기반 코드가 만들어진다. 세부적인 RPN 명세를 보려면 참고 문헌에 소개하는 매뉴얼 페이지를 확인하기 바란다.tut13.rpn/* Generated from examples/tut13.dpc by Dprobes C compiler 1.0.0 */name = "/home/project/dprobes-v3.6.5/dpcc/dpcc-1.0.1/triggerprobe"modtype = uservars = 38major = 1logmax = 1024jmpmax = 65535autostacktrace = noexcpt_log = 1024offset = test_fn + 3opcode = 0xccexcpt_mask = 0xffffminor = 0ignore = 0maxhits = 0x7ffffffflogonfault = nopush 0pop lv, 1push 0x7pop lv, 2push 0xcaaffeepop lv, 3push 0x68pop lv, 4push 0x6f…tut13.rpn실제 프로브를 삽입할 dpcc-1.0.1/triggerprobe.c를 살펴보자. 컴파일이 끝나고 dprobes 유틸리티가 삽입한 tut13.rpn 프로브는 test_fn 함수가 호출될 때 점화된다. 소스코드를 보면 알겠지만, test_fn은 테스트를 위한 함수이므로 무의미한 코드 내용을 담고 있다.triggerprobe.c// a senseless jumble of code to test things out withint test_fn(int param1, int param2){ int local1=0x111; unsigned local2=0x222; myint local3=0x333; long local4=0x444; int local5=0x555; int * localptr; localptr = (int *)local1+local2+local3+local4+local5; return local5;triggerprobe.c커널 프로브 예지면 관계상 커널 프로브에 대한 설명은 줄이고 간략한 생성 방법만 소개하겠다.kernel-global-var.dpc를 컴파일하고 프로브를 삽입하면 fork할 때마다 커널 전역 변수인 last_pid를 /var/log/messages로 출력하게 된다. 세부 내역은 kernel-global-var.dpc 코드를 살펴보기 바란다../dpcc examples/kernel-global-var.dpc./dprobes -i /home/project/dprobes-v3.6.5 /dpcc/dpcc-1.0.1/kernel-global-var.rpn -s /usr/src/linux-2.6.9-kprobes/System.mapdpcc 컴파일러에는 여러 가지 좋은 예제가 따라오므로, 시간을 들여 튜토리얼이나 매뉴얼과 더불어 각 dpc 파일을 분석하면 좋은 성과를 얻을 것이다.결론이번 연재에서 소개한 내용은 커널 프로브와 동적 프로브를 사용한 최소한의 환경 설정 방법과 운영 방법이다. 커널 프로브와 동적 프로브를 사용해서 현장에서 커널이나 응용 프로그램을 디버깅 할 경우에 커널은 물론이고 프로브에 밀접한 고급 프로그래밍 지식이 필요하므로, 실제 적용 과정에서는 참고문헌에 나온 내용을 토대로 충분한 연습을 거쳐야 한다.특히 dprobes 패키지에 들어있는 각종 예제 사례를 분석하면 도움이 될 것이다.물론 gdb와 같은 디버그와 마찬가지로 동적 프로브도 어디까지나 수단일 뿐이며, 결국 디버깅은 사람 머리로 수행하는 작업이므로 동적 프로브 사용법에 너무 집착해서 정작 중요한 디버깅 본질을 놓치면 곤란하다.우선 충분한 문제 상태 파악이 우선이고, 이 단계를 끝내고 난 다음에 어떤 도구를 사용해서 어떤 방식으로 접근할지 생각하기 바란다.지금까지 총 6회에 걸친 연재에서 리눅스 개발자를 위한 몇 가지 중요한 디버깅 기법을 살펴보았다. 많은 내용을 한정된 지면에 다루려다 보니 주로 소개 위주로 전개할 수밖에 없었는데, 다음에 기회가 닿으면 이번 연재와 연결해서 좀 더 고급 디버깅 주제를 다루도록 약속하겠다.참고문헌* 리눅스 디버깅과 성능 튜닝: 오픈 소스 도구를 사용한 문제 진단 분석과 해결(번역서, 에이콘): 12장에 동적 프로브에 대한 자세한 설명이 나온다.* http://dprobes.sourceforge.net/documentation/: 동적 프로브 공식 매뉴얼 페이지이다. 조금 딱딱하므로 초보자가 읽기에 어려울지도 모른다.
회원가입 후 이용바랍니다.
개의 댓글
0 / 400
댓글 정렬
BEST댓글
BEST 댓글 답글과 추천수를 합산하여 자동으로 노출됩니다.
댓글삭제
삭제한 댓글은 다시 복구할 수 없습니다.
그래도 삭제하시겠습니까?
댓글수정
댓글 수정은 작성 후 1분내에만 가능합니다.
/ 400
내 댓글 모음
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지