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

디버깅만큼 학제간 연구를 많이 요구하는 분야는 없다. 자신이 속한 전문 분야에 대한 지식은 물론이고 컴퓨터 아키텍처와 운영체제를 시작으로 컴파일러 하부 구조, 라이브러리 특성, 응용 프로그램 요구사항에 깔린 여러 가지 다양한 가정, 프로그램 작성자의 의도를 꿰뚫는 심리학까지 개인이 알고 있는 모든 기술을 총동원해야 하기 때문이다. 따라서 디버깅에는 왕도가 없다. 아무리 좋은 디버깅 도구나 디버깅 방법론을 가져오더라도 실제 상황에 적용을 하지 못하면 아무 소용이 없기 때문이다. 하지만 사냥에 나선 사냥꾼이 엽총과 총알을 준비해야 하듯이 디버깅을 효율적으로 진행하기 위해 기본적인 도구는 제대로 챙겨놓아야 한다.이번 회에서는 앞으로 연재할 여러 가지 다양한 디버깅 도구 탐험에 앞서 디버깅과 관련하여 기본적인 용어와 개념을 소개하도록 하겠다. 가장 먼저 건축이나 기계공학에서 사용하는 용어인 비파괴 검사를 응용한 비파괴식 디버깅과 파괴식 디버깅에 대해 살펴보기로 하자.비파괴식 디버깅과 파괴식 디버깅네이버 백과사전에서 비파괴식검사를 검색해 보면 다음과 같은 정의를 볼 수 있다(http://100.naver.com/100.php?id=82048).「 공업제품 내부의 기공(氣孔)이나 균열 등의 결함, 용접부의 내부 결함 등을 제품을 파괴하지 않고 외부에서 검사하는 방법 」비파괴식검사는 쉽게 말해서 검사 대상에 손대지 않고 방사성 동위원소나 초음파 등을 사용해서 내부를 관찰하는 기법이다. 이와 유사하게 전자공학에서도 PCB를 변경하지 않은 상태에서 오실로스코프에서 빠져 나온 프로브를 각종 부품에 대고 여러 가지 다양한 검사를 수행한다. 컴퓨터 공학 부문에서 디버깅을 이야기할 때도 코드에 손을 대거나 특수한 라이브러리로 링크해서 실행 파일을 변경하는 파괴식 디버깅 기법과 목적 코드와 실행 파일에 손을 대지 않고 있는 그대로 외부에서 관찰하는 비파괴식 디버깅 기법이 존재한다.언뜻 생각하면 비파괴식 디버깅이 효용성이 있을지 의심부터 앞설 수 있다. 하지만 서비스를 중단할 수 없는 상용 시스템이나 소스 코드가 없는 목적 파일 또는 라이브러리를 링크한 프로그램을 디버깅할 때는 비파괴식 디버깅 이외에 다른 방법이 없을 가능성이 높다. 따라서 적재적소에 필요한 디버깅 기법을 사용해야 한다. 가장 대표적인 파괴식 디버깅 예인 printk/printf와 strace를 통해 양쪽 차이점에 대해 좀더 세부적으로 알아보겠다.예 1: 가장 대표적인 파괴식 디버깅: printk와 printf가장 널리 알려져 있으며 역사가 오래된 디버깅 기법이 바로 printk/printf를 사용한 디버깅이다. 특정 코드에서 변수, 포인터, 구조체 값을 출력하며, 코드 경로를 제대로 따라가는지 확인하기 위해 중간 중간에 표시하는 printk/printf 코드를 삽입한다. 특별한 도구가 필요하지 않으며, UART나 직렬 통신이 뚫린 다음부터 커널이나 응용 프로그램 양쪽 모두에서 사용할 수 있으므로 특히 상호대화식 디버거를 사용하기 어렵거나 제약이 있는 임베디드 분야에서 널리 이용한다.libc를 사용하는 일반 응용 프로그램인 경우에는 printf 계열 함수를 사용하며, 리눅스 커널이나 커널 모듈 형식을 따르는 커널 영역 프로그램인 경우에는 printk 계열 함수를 사용한다. 특히 printk를 사용할 경우에는 중요도에 따라 필터링이 가능하며 syslog 기능과 연동시켜서 콘솔 출력이 아니라 파일이나 네트워크 출력으로 방향 재지정까지 가능하다.언제 어디서나 손쉽게 사용할 수 있는 반면에 부작용도 생각해야 하는데, 타이밍에 민감한 응용 프로그램을 디버깅하는 과정에서 printk나 printf를 사용할 경우 하이젠버그에 말려들 가능성이 있기 때문에 조심해야 한다.예 2: 가장 대표적인 비파괴식 디버깅: strace프로그램이 error=-12라는 암호 같은 오류를 내면서 죽어버릴 경우 도대체 어떤 조건에 문제가 생겨서 죽는지 알고 싶어질 것이다. 소스코드가 있다면 grep 명령으로 “error=” 문자열을 추적해 들어가면 아주 쉽게 원인을 찾을 수 있지만, 실행 파일만 존재한다면 정말 답답한 심정을 느낄 것이다. 이럴 때는 응용 프로그램이 부르는 시스템 호출(system call)을 모두 추적해주는 강력한 유틸리티인 strace를 사용할 경우 문제 근본 원인에 한 걸음 다가설 수 있다.흔히 리눅스 환경에서 공유 라이브러리나 환경 설정 파일 위치, 접근허가(permission) 문제로 인해 프로그램이 제대로 동작하지 않는 경우가 있는데, strace로 추적하면 멈춘 지점의 조금 앞부분(시스템을 끝내기 위한 각종 처리와 오류 메시지 출력 부분 전)에서 수행한 작업을 바로 파악할 수 있다. 임베디드 시스템 디버깅 과정에서도 유용하게 사용할 수 있으므로, strace를 반드시 교차 컴파일해서 루트 디렉토리에 넣어두면 두고두고 써먹을 수 있다.다음은 유명한 "hello, world" 프로그램과 이를 strace로 추적한 결과이다. 응용 프로그램이 수행하면서 부르는 시스템 호출을 시간 순으로 차례로 표시하고 반환값과 오류 번호를 알려준다. 어떤 라이브러리를 링크하고 어떤 파일을 읽고 쓰는지 따라갈 수 있다.줈 hello.c줈 strace를 사용한 추적 결과정적 검사와 동적 수행 검사디버깅 과정에서는 프로그램을 실제로 수행시키면서 문제점을 추적하는 경우와 실제로 수행시키지 않은 상태에서 문제점을 추적하는 경우로 나눠서 생각할 수 있다. 우리가 흔히 디버깅 작업을 수행할 때는 동적 검사에 의존하지만 경우에 따라서 정적 검사를 통해 버그를 더 효율적으로 찾아낼 수도 있다.정적 검사는 종이에 인쇄한 코드 검토로 거슬러 올라간다. 코드 리뷰와 같은 정형적인 코드 검토 기법은 프로그램 실행이나 컴파일에 앞서 사람이 코드 분석을 수행한다. 사람 생명에 밀접한 제어 코드나 돈에 치명적인 영향을 미칠 수 있는 중요한 코드는 여러 차례에 걸친 코드 리뷰를 사용해서 무결성을 점검한다. 단순히 어떤 프로그램이 바르게 동작함은 물론이고 올바른 프로그램인지를 검토하는 데는 컴퓨터와 달리 가치 판단을 할 수 있는 사람이 필연적으로 개입해야 하기 때문이다.정적 분석 기법 중에서 컴파일러를 활용하는 기법은 무척 기본적이면서도 간과하기 쉽다. 예를 들어, gcc 옵션인 ?Wall을 켜 놓을 경우 컴파일러가 컴파일하는 과정에서 문법적인 오류뿐만 아니라 실행 과정에서 문제가 될 수 있는 의심스러운 부분을 모두 찾아내므로, 실제 프로그램을 수행하지 않고서도 많은 문제점을 찾아서 미리 싹을 잘라버릴 수 있다. 옛날 C(pre ANSI C)에서는 컴파일러와 별개로 C 점검 프로그램인 lint를 사용해서 이런 작업을 수행하도록 유도했으나, 최근 컴파일러 기술 발전으로 인해 컴파일러 하나에서 모든 작업을 수행할 수 있게 됐다.예 3: 정적 메모리 배열 색인 검증 도구인 아이락http://ropas.snu.ac.kr/2004/airac/서울대학교 프로그래밍 연구실에서 만든 아이락은 C 프로그램에 존재하는 배열 색인 오류를 정적으로 분석하는 도구이다. 아이락은 C 프로그램 실행 상황을 정적으로 분석해서 배열 외부 메모리에 접근하는 경우를 모두 잡아낸다.물론 정적 분석은 오류를 족집게처럼 100% 올바르게 찾아내기는 어렵다. 예를 들어, 아이락에서 사용자가 입력한 숫자를 색인으로 사용한다면 사용자 마음을 읽어야 하는 소프트웨어가 필요한데 현실적으로 가능하지 않다. 따라서 분석 결과에 잡음이 많이 섞이므로 사람 이 알아서 이를 필터링 해야 하는 문제점이 생긴다. 하지만 정적 분석은 수학적인 모델을 사용해서 색인 배열이 넘어갈 수 있는 경우를 모두 추적하므로 올바른 테스트 집합에 절대적으로 의존하는 동적 분석에 비해 신뢰도를 높일 수 있다는 장점이 있다.예 4: 동적 메모리 검증 도구인 Electric Fencehttp://perens.com/FreeSoftware/Electric Fence는 오픈소스 대가인 브루스 페런스가 작성한 malloc() 디버깅 라이브러리이다. Electric Fence는 프로그램이 할당한 메모리 바로 뒤에 보호 메모리를 할당하는 방식으로, 배열 끝을 넘어설 경우, 보호 오류로 인해 segmentation fault와 더불어 프로그램 실행을 즉시 중단한다.하지만 정적 분석기인 아이락과 달리 Electric Fence를 사용하려면, 프로그램 makefile에 -lefence 옵션과 -g 옵션을 추가해서 컴파일한 다음에 실제 응용 프로그램을 실행해야만 한다. Electric Fence를 사용하면 프로그램 수행 속력이 느려지고 메모리를 많이 사용한다는 단점이 있다. 또한 out-of-bound와 같은 메모리 참조 오류가 있을 경우에는 문제 지점에서 segm- entation fault를 일으키도록 간접적인 행동을 취하므로, 메모리 문제를 찾아내려면 소스코드 단에서 디버깅을 지원하고 있는 gdb나 ddd에 의존해야 한다.메모리 문제가 발생하는 공간: 스택과 힙디버깅 과정에서 초보 개발자가 가장 어려워하는 부분이 바로 스택과 힙에서 발생하는 문제이다. 물론 램을 1GByte씩 장착한 PC 환경에서 자바나 C#과 같이 가베지 컬렉션을 제공하는 프로그래밍 언어로 개발할 경우 메모리 관리 문제가 논의의 대상에서 벗어난다. 하지만 제한적인 메모리에서 동작하는 임베디드 시스템 환경에서 C와 같은 상대적으로 저수준 프로그래밍 언어를 사용해서 개발할 경우 1Byte라도 아껴 쓰기 위해 여러 가지 기교를 총 동원해야 하므로 상대적으로 메모리 문제가 발생할 가능성이 높다.초보자 개발자가 자주 실수하는 메모리 관련 오류를 두 개 정도 살펴보자.예 5: 전형적인 스택 관련 오류우선 스택 관련 버그이다. 첫눈에 봐서는 프로그램이 제대로 돌아갈 듯이 보이며, 플랫폼에 따라 제대로 돌아갈 수 있을지도 모르겠지만, 명백히 잘못된 프로그램이다. 어디가 잘못되었는지 알겠는가?우선 “hello, world!\n”를 담기에는 buf 크기가 너무 작아서 스택 영역을 침범하고 도저히 이해 못할 여러 가지 버그를 양산해낸다. 다음으로 자동 변수를 사용해서 스택에 buf를 위치시키므로 foobar() 함수가 반환되는 순간 buf의 생명주기가 끝나므로 무효한 메모리 위치에 접근하기에 잘못하면 엉뚱한 메모리 위치에 접근하게 된다.스택과 관련한 버그는 대화식 디버거를 사용해서 역추적(backtrace)을 하면서 함수별 스택 프레임을 관찰하는 방법을 찾아낼 수 있다(세부 설명은 다음 회에서 다룬다). 물론 코어 파일이 생성될 경우에는 좀더 쉽게 바로 문제가 일어나는 지점으로 갈 수 있으므로 디버깅 시간을 단축시킬 수도 있는데, 경우에 따라서는 스택 프레임 자체를 깨버려서 역추적으로 위치를 파악하지 못할 수도 있다. 스택 버그를 얕보고 덤볐다가 결국 코드 리뷰를 통해 잡아내야만 하는 대단히 난처한 상황에 직면할 수도 있다.예 6: 전형적인 힙 관련 오류다음으로 힙과 관련한 오류이다. 물론 여러 가지 힙 관련 오류가 있지만, 여기서는 가장 흔히 저지르는 실수를 일부러 저질러 보았다. 어디가 잘못되었는지 알겠는가?MAX_CHAR 매크로에 문제가 있으므로 힙에 할당받은 메모리 범위를 벗어나며, 할당 받은 메모리를 해제하는 부분이 없기 때문에 이 시스템은 메모리 상황에 따라 다르겠지만 조만간 메모리 누수에 따른 스레딩 현상으로 시스템 전체가 뻗어버릴 운명에 놓여있다. MAX_CHAR 값을 늘여서 메모리 범위 문제를 해결한 다음에 이 프로그램을 컴파일해서 돌려놓고 쉘에서 free를 쳐보면 지속적으로 가용 메모리가 줄어드는 상황을 확인할 수 있다.특히 동적 메모리 문제는 프로그램 수행 중에 지속적으로 상태가 변하므로 추적하기가 어렵기 때문에 여러 가지 도구를 사용해서 문제점을 파악해야 한다(세부 설명은 5회 연재에서 다룬다). 코어 덤프가 발생했더라도 실제 코어가 발생한 지점 훨씬 앞쪽부터 메모리가 깨졌을 확률이 높아 단순히 대화식 디버그를 사용한 역 추적 정보만으로는 정확한 위치를 파악할 수 없으므로 전체 호출 트리를 쫓아다니면서 근본적인 문제 발생 지점을 추적해나갈 필요도 있다.막무가내로 대화식 디버거를 열어서 단계별 추적에 나서지 말고 정확하게 현상을 파악하라는 조언이 나오는 이유가 바로 여기에 있다.앞서 소개한 예제는 상당히 간단하므로 일반적인 개발자가 이런 평이한 실수를 저지를 확률은 거의 없다는 생각이 들지도 모르겠다.하지만 코드 크기가 수만 행을 넘어가고 여러 개발자가 얽힌 상태에서 개발을 진행할 경우에 부작용(side effect)으로 인한 스택/힙 메모리 문제가 여기저기서 터져 나오기 마련이며, 심지어 나중에 양산에 들어간 제품을 현장에서 사용할 때 수면위로 떠오르는 경우도 있다. 따라서 메모리 문제는 아무리 조심해도 지나치지 않다.시스템 로그 파일 활용하기디버깅을 잘하는 개발자와 그렇지 못한 개발자를 유심히 관찰하다 보면 한 가지 공통적인 특징을 발견할 수 있다. 디버깅을 잘하는 개발자는 로그 파일을 100% 활용해서 문제가 발생했을 경우에 문제를 최대한 빠른 시간 내에 격리해서 원인 분석에 들어간다. 디버깅에 서툰 개발자는 문제가 발생한 지점을 파악하느라 우왕좌왕하다가 황금 같은 개발 시간을 낭비해버린다.프로그램을 작성하는 과정에서 중요한 가정이 어긋날 경우에 반드시 로그 파일에 문제점을 기록하는 습관을 길러야 한다. 나중에 문제가 생겨서 디버깅 메시지를 넣는 대신 처음부터 계획적으로 디버깅 메시지를 출력하도록 만드는 전략은 투자 대비 이익을 극대화한다. 자신이 직접 로그 파일 관련 모듈을 만들어 사용할 수도 있지만, 리눅스 디바이스 드라이버 3판 4장에 커널 모듈과 일반 응용 프로그램에 모두 적용할 수 있는 DEBUG 관련 매크로 정의가 있어서 여기서 잠깐 소개한다. 이 매크로를 사용하면 일관성 있게 프로그램 내부에서 로그 파일을 기록할 수 있게 된다.예 7: PDEBUG 매크로Makefile에서는 다음과 같이 간단하게 한 줄로 디버깅을 끄고 켤 수 있으므로 개발자용 디버깅 버전과 상용 버전을 함께 유지할 수 있게 된다.리눅스 디버깅 도구와 별 상관이 없어 보이는 이런 로그 관련 내용을 굳이 아까운 지면을 소비하면서까지 설명하는 이유는 간단하다. 바로 디버깅을 잘하려면 디버깅이 쉽도록 미리 프로그램을 짜놓아야 한다는 사실을 강조하기 위한 목적이다. 앞으로 연재에서 설명할 여러 가지 기법은 이렇게 디버깅 하부구조가 잘 갖춰져 있는 프로그램에 적용해야만 효과가 제대로 난다는 사실을 다시 한 번 강조하고 넘어가겠다.결 론첫 회부터 여러 가지 어려운 개념에 대해 소개하는 바람에 리눅스 디버깅에 대해 두려움을 느낄지도 모르겠다. 하지만 디버깅 자체가 원래 어렵기 때문에 문제를 쉽게 풀 수는 없는 노릇이므로 앞으로 연재 과정에서도 계속해서 복잡한 내용이 나오기에 어느 정도 마음을 단단히 먹고 있어야 할 것이다. 첫 회에서는 개괄적인 내용을 다뤘는데, 다음 회부터 사례연구를 통한 구체적인 디버깅 방법을 살펴보기로 하겠다.참고로 본 연재에서는 지면 관계상 몇 가지 유용한 도구만 다루기 때문에 세부적인 디버깅 도구와 디버깅 테크닉을 익히려면 다음에 소개하는 참고 문헌을 읽어서 기본이 되는 각종 지식을 습득한 다음에 실제 현장 상황에 적용해봐야 한다. 다시 한 번 언급하지만 디버깅에는 왕도가 없으므로 꾸준한 노력과 고민만이 디버깅 실력을 높일 수 있다.쪹 연재순서리눅스 개발자를 위한 디버깅 기법1회 리눅스 디버깅 개괄2회 core 파일, gdb와 gdbserver3회 커널 웁스, kdb와 kgdb4회 내부를 들어다 보는 창인 /proc 파일 시스템 분석5회 메모리 관리 디버깅 기법 소개6회 소프트웨어 탐침: 동적 프로브 소개필 / 자 / 소 / 개박재호(jhrogue@yahoo.co.kr)컴퓨터와 책에 얽힌 재미있는 이야기를 다루는 ‘컴퓨터 vs 책’이라는 블로그 (http://blog.ya hoo.co.kr/jhrogue)를 운영하고 있으며, 회사에서는 임베디드 리눅스를 사용한 제품 개발에 몰두하고 있다. 최근 임베디드 리눅스 개발자를 위한 필독서인 리눅스 디바이스 드라이버 3판(한빛미디어)과 리눅스 디버깅과 성능 튜닝: 오픈 소스 도구를 사용한 문제 진단 분석과 해결(에이콘)을 번역했다.하이젠버그하이젠버그는 버그를 추적하기 위해 팔을 걷어붙이고 나서는 순간 사라져버리거나 행동양식을 바꿔버리는 컴퓨터 버그를 의미한다. 물리학에서 유명한 하이젠베르그의 불확정성 원리를 본 따 만든 이 버그는 디버그 모드에서 멀쩡하게 잘 돌아가던 프로그램이 릴리즈 모드에서는 죽어버리고, 경쟁 조건에 걸리는 순간 프로그램이 이상 동작을 하는 경우에 의심해 볼만하다. 스레드 프로그래밍을 디버깅 할 때 printf를 넣어서 문제 원인을 찾으려고 하면 이상 없이 돌다가 printf를 빼버리는 순간 죽어버린 경험을 해본 개발자라면 하이젠버그가 얼마나 무시무시한 버그인지 몸소 실감했을 것이다. 기타 여러 가지 희한한 버그는 http://en.wikipedia.org/wiki/Heisenbug를 참조하기 바란다.#include int main(){ printf(“hello, world!\n”);return 0;}$ strace ./helloexecve("./hello", ["./hello"], [/줈 26 vars 줈/]) = 0uname({sys="Linux", node="linux.charops.com", ...}) = 0brk(0) = 0x80495f4open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat64(3, 0xbfffecbc) = -1 ENOSYS (Function not implemented)fstat(3, {st_mode=S_IFREG|0644, st_size=62150, ...}) = 0old_mmap(NULL, 62150, PROT_READ, MAP_PRIVATE, 3, 0) = 0x126000close(3) = 0open("/lib/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0H\300\1"..., 1024) = 1024fstat(3, {st_mode=S_IFREG|0755, st_size=5564618, ...}) = 0old_mmap(NULL, 1205832, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x136000mprotect(0x253000, 38472, PROT_NONE) = 0old_mmap(0x253000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ FIXED, 3, 0x11c000) = 0x253000old_mmap(0x259000, 13896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ FIXED|MAP_ANONYMOUS, -1, 0) = 0x259000close(3) = 0old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x25d000munmap(0x126000, 62150) = 0getpid() = 27695fstat64(1, 0xbffff210) = -1 ENOSYS (Function not implemented)fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x126000ioctl(1, TCGETS, {B9600 opost isig icanon echo ...}) = 0write(1, "hello, world!\n", 14hello, world!) = 14munmap(0x126000, 4096) = 0_exit(0) = ?MMU 없는 시스템을 위한 uClinuxuClinux는 MMU 없는 시스템을 위한 리눅스 커널로 2.4까지는 별도 패치 형식으로 주 커널(main kernel, www.kernel.org)에 붙여 사용했지만, 2.6부터는 주 커널에 통합된 형태로 제공한다. MMU가 없는 시스템에서 동작하다 보니 uClinux는 커널 메모리와 응용 프로그램 메모리가 선형 메모리 주소에 함께 올려놓고 관리한다. 따라서 사용자 영역 프로그램에서 문제를 일으킬 경우 커널 영역을 손상시킬 수도 있으며, 디바이스 드라이버가 불법적인 방법으로 하드웨어 레지스터를 직접 제어하는 바람에 시스템을 불안정하게 만들 수도 있다.따라서 uClinux를 사용할 경우에는 커널은 물론이고 응용 프로그램에서 사용하는 메모리 관리에도 신경을 많이 써야 한다. 잘못하면 커널 웁스가 발생하거나 시스템이 소리소문 없이 죽어버릴 수도 있기 때문이다.#include #define MAX_CHAR 10static unsigned char줈 foobar(){ unsigned char buf[MAX_CHAR]; strcpy(buf, “hello, world!\n”); return buf;}int main(){ printf(“[%s]”, foobar());}#include #define MAX_CHAR 10static unsigned char줈 foobar(){ unsigned char 줈buf; buf = malloc(sizeof(char) 줈 MAX_CHAR); strcpy(buf, “hello, world!\n”); return buf;}int main(){ while (1) { printf(“[%s]”, foobar()); }}#undef PDEBUG /줈 노파심에서 PDEBUG 정의를 해제한다. 줈/#ifdef MY_MODULE_DEBUG# ifdef _ _KERNEL_ _ /줈 디버깅을 켜고, 커널 영역일 경우에만 다음 매크로를 사용한다. 줈/# define PDEBUG(fmt, args...) printk( KERN_DEBUG "my module: " fmt, ## args)# else /줈 사용자 영역일 경우에 다음 매크로를 사용한다. 줈/# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)# endif#else# define PDEBUG(fmt, args...) /줈 디버깅 용도가 아니다: 아무런 작용도 하지 않는다. 줈/#endif#undef PDEBUGG#define PDEBUGG(fmt, args...) /줈 아무런 작용도 하지 않는다. 그냥 틀만 잡아놓는다. 줈/# 디버깅을 켜고 끄려면 다음 행을 주석 처리하거나 주석 처리 하지 않는다.DEBUG = y# CFLAGS에 디버깅 플래그를 더하거나 뺀다.ifeq ($(DEBUG),y) DEBFLAGS = -O -g -DMY_MODULE_DEBUG # 최적화 중 인라인 함수 확장을 위해 "-O"가 필요하다.else DEBFLAGS = -O2endifCFLAGS += $(DEBFLAGS)참고문헌* 리눅스 디바이스 드라이버 3판(번역서, 한빛미디어): 디바이스 드라이버 디버깅을 위해서는 4장 디버깅 기술을 참조하기 바란다.* 리눅스 디버깅과 성능 튜닝: 오픈 소스 도구를 사용한 문제 진단 분석과 해결(번역서, 에이콘): 디버깅과 관련한 무척 흥미로운 주제를 단계별로 차근차근 다루고 있다. 리눅스 초급 개발자라면 제대로 된 디버깅을 위해 반드시 한번 읽어봐야 한다.* Self-Service Linux: Mastering the Art of Problem Determination(원서, Prentice Hall PTR): 리눅스 디버깅과 성능 튜닝보다 좀 더 고급 주제를 다룬다. 본문 중 gdb와 스택 동작 원리 설명은 이 책의 백미이다.
이 기사를 공유합니다
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지