-
[Pintos] 디버깅 도구 (Debugging Tools) - printf, assert, __attributes__, backtraces프로젝트/Pintos 2021. 3. 16. 10:56
디버깅이란 프로그램에서 발생할 수 있는 버그(에러) 를 찾아서 없애는 과정을 말한다. 이 장에서는 Pintos 프로젝트를 진행하면서 활용할 수 있는 여러가지 디버깅 도구들에 대해 설명한다. 여기서 설명된 방법들은 Pintos 프로젝트 뿐 아니라 다른 프로젝트들에서도 널리 사용될 수 있는 디버깅 방법들이므로 익혀두도록 하자.
1. printf()
printf() 를 c 언어의 가장 처음에 배우기 때문에 printf() 를 사용한 디버깅은 없어보인다고 생각하기 쉽다. 하지만 printf() 만큼 간단하고 유용하게 사용할 수 있는 Debugging Tools 도 없다.
프로그램이 비정상적으로 작동할 때 printf() 를 코드의 중간중간 섞어 놓으면 문제가 발생한 코드의 범위를 특정할 수 있다.
ex) code A - printf("after A") - code B - printf("after B") - code C
실행결과 : after A (after B 는 출력되지 않음)
-> code B 에 문제가 있음.
2. ASSERT
assert 함수는 표준 c 라이브러리 중 하나인 assert.h 에 포함되어 있는 매크로이다.
pintos 에서는 <lib/debug.h> 에 간략하게 구현되어 있다.
/* lib/debug.h */ #ifndef NDEBUG #define ASSERT(CONDITION) if (CONDITION) { } else { PANIC ("assertion `%s' failed.", #CONDITION); } #define NOT_REACHED() PANIC ("executed an unreachable statement"); #else #define ASSERT(CONDITION) ((void) 0) #define NOT_REACHED() for (;;) #endif
NDEBUG 라는 것은 디버그 모드가 아닌 경우(릴리즈 모드)를 말한다. 디버그 모드에서는 ASSERT 등의 디버깅 도구들이 작동하지만 릴리즈 모드에서는 디버깅 도구들은 무시하기 때문에 더 최적화된 실행파일이 만들어진다.
(#ifndef 는 뒤 매크로가 정의되어 있지 않을 때 실행된다. #ifdef 와 헷갈리지 말도록 하자.)
ASSERT 매크로는 정해진 조건에 맞지 않을 때 프로그램을 중단한다.
위 코드에서 보면 조건(CONDITION) 이 참(true)이면 그냥 지나가지만, 거짓(false)라면 커널을 PANIC 에 빠뜨리고 Panic message(문제가 된 부분의 expression, file and line number, backtrace 등)을 출력한다. 즉, 프로그램이 올바로 작동하기 위해서는 assert 뒤의 조건이 항상 참(true)이 되도록 만들어야 한다.
printf() 와 마찬가지로 버그가 의심되는 곳에 여기저기 섞어두면 유용하다.
3. Function and Parameter Attributes
pintos 의 <lib/debug.h> 에는 함수 혹은 함수 파라미터에 사용할 수 있는 특별한 속성(attributes) 들이 정의되어 있다.
/* lib/debug.h */ #define UNUSED __attribute__ ((unused)) #define NO_RETURN __attribute__ ((noreturn)) #define NO_INLINE __attribute__ ((noinline)) #define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST)))
우선 __attribute__ 라는 표현이 생소할 수도 있다. __attribute__ 표현은 gcc 에서 사용할 수 있는 확장 기능정도로 볼 수 있다. 이는 함수, 구조체, 변수 등의 컴파일 시에 특정 속성을 적용시키는 역할을 한다. "__attribute__ ((속성인자))" 의 형태로 사용하면 이 표현이 붙은 함수, 구조체 혹은 변수에 지정한 속성을 부여할 수 있다.
예를 들어, __attribute__ ((unused)) 는 특정 변수에 unused 라는 속성을 부여하는데 사용될 수 있다.
/* www.keil.com/support/man/docs/armcc/armcc_chr1359124982981.htm */ void Variable_Attributes_unused_0() { static int aStatic =0; int aUnused __attribute__((unused)); int bUnused; aStatic++; }
이를 실행시키면 원래라면 사용하지 않은 aUnused, bUnused 라는 변수에 대해 컴파일러가 경고를 하지만, unused 속성을 부여받은 aUnused 변수에 대하여는 경고를 하지 않는다.
이 내용을 가지고 위의 <lib/debug.h> 를 해석해보자.
- UNUSED : 함수의 parameter 에 붙어서 해당 parameter 가 사용되지 않을 수 있음을 명시한다.
- NO_RETURN : 함수 prototype 에 붙어서 해당 함수가 return 되지 않음을 명시한다. 이는 단순히 return 값이 없다는 것 이상으로 이 함수를 call 한 caller 에게로 control 이 돌아가지 않음을 의미한다. 따라서 NO_RETURN 속성을 가지는 함수 이후의 코드는 컴파일러에 의해 도달할 수 없는 코드로 최적화되어 제거된다.
__attribute__ ((noreturn)) void noReturnf(){ ... } int main(){ print("a"); noReturnf(); print("b"); // unreachable print("c"); // unreachable }
- NO_INLINE : 함수 prototype 에 붙어서 해당 함수가 inline 처리되지 않음을 명시한다. inline 으로 정의된 함수는 컴파일 할 때 함수가 사용되는 모든 곳에 함수의 코드를 복사하여 넣어준다. 즉, 프로그램이 실행되면서 만나는 function call 에서 함수를 호출하지 않고 함수의 코드를 그대로 실행한다.
- PRINT_FORMAT (format, first) : 함수 prototype 에 붙어서 해당 함수가 printf 함수의 형식으로 사용됨을 명시한다. format 은 format string 의 위치이고, first 는 value parameter 의 시작 위치를 나타낸다. 이렇게 말하면 이해가 잘 되지 않으니 예시를 들어보겠다.
/* lib/debug.h */ void debug_panic (const char *file, int line, const char *function, const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN;
위 함수는 pintos 의 panic message 를 출력해주는 디버깅함수이다. 이 함수에 PRINT_FORMAT (4, 5) 가 붙었으므로 이 함수의 parameter 는 printf 함수의 형식을 따라야 하고, 그렇지 않을 시 오류를 출력한다. 즉 format string 의 위치로 지정된 4번째 parameter 인 const char *message 에는 format string (ex. "%d + %d = %d" 등) 의 형식 문자열이 argument 로 들어와야 하고, value parameter 의 시작 위치로 지정된 5번째 parameter 부터는 format string 의 %d, %s, %c 등에 들어갈 값들이 알맞은 type 으로 입력되어야 한다.
debug_panic ([blah], [blah], [blah], "error message here, line %d", 4)
예시처럼 앞에 3개의 parameter 를 생략하고 보면 printf 의 형식과 같다. 만약 5번째 argument 로 4 대신 "c" 등의 다른 type 의 argument 가 들어오면 컴파일러가 경고 문구로 알려주게 된다.
4. Backtraces
kernel panic 이 발생했을 때 프로그램이 오류가 발생한 지점의 address 를 역추적하여 알려준다. <lib/debug.h> 에 debug_backtrace(), debug_backtrace_all() 로 구현되어 있으며 각각 현재 지점의 backtrace, 모든 threads 의 backtrace 를 출력한다. 이 함수는 역추적한 address 들을 hexadecimal 값으로 출력하여 주기 때문에 해석에 곤란함을 겪을 수 있다. 이를 위해, pintos 는 터미널에서 backtrace 라는 명령어를 제공하여 이 hexadecimal 값들을 function name, source file line number 로 해석하여 준다.
example) debug_backtrace() 의 결과가 아래와 같이 출력되었다고 하자.
Call stack : 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0xc804812c 0x8048196 0x8048ac8
결과의 해석은 backtrace [kernel.o(backtrace 를 제공한 kernel)] [hexadecimal numbers] 형식으로 사용한다.
$ backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0xc804812c 0x08048196 0x08048ac8
# result 0xc0106eff: debug_panic (lib/debug.c:86) 0xc01102fb: file_seek (filesys/file.c:405) 0xc010dc22: seek (userprog/syscall.c:744) 0xc010cf67: syscall_handler (userprog/syscall.c:444) 0xc0102319: intr_handler (threads/interrupt.c:334) 0xc010325a: intr_entry (threads/intr-stubs.S:38) 0xc804812c: (unknown) 0x08048196: (unknown) 0x08048ac8: (unknown)
아래 3줄이 (unknown) 으로 표시된 것은 이들이 kernel 함수가 아니라 user program 의 함수이기 때문이다. 만약 어떤 user program 에서 kernel panic 이 발생했는지 알고있다면 해당 user program 에서 다시 backtrace 를 실행하면 (unknown) 의 정체를 알 수 있다.
$ backtrace tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0xc804812c 0x08048196 0x08048ac8
# result 0xc0106eff: (unknown) 0xc01102fb: (unknown) 0xc010dc22: (unknown) 0xc010cf67: (unknown) 0xc0102319: (unknown) 0xc010325a: (unknown) 0xc804812c: test_main (...xtended/grow-too-big.c:20) 0x08048196: main (tests/main.c:10) 0x08048ac8: _start (lib/user/entry.c:9)
이번엔 kernel 함수들이 (unknown) 으로 표시되고 user program 함수들만 제대로 나온 것을 볼 수 있다. 당연한 얘기지만, 이 출력값은 Call stack 의 출력이므로 아래서부터 위로 올라가며 함수를 호출한 것이다.
$ backtrace kernel.o tests/filesys/extended/grow-too-big ...
이런 식으로 kernel 과 user program 을 동시에 넣으면 (unknown) 없이 한 번에 모든 값이 출력된다.
5. GDB
GDB 의 내용은 워낙 방대한 관계로 따로 포스팅하도록 한다.
Reference)
web.stanford.edu/class/cs140/projects/pintos/pintos.pdf
www.keil.com/support/man/docs/armcc/armcc_chr1359124982981.htm
dojang.io/mod/page/view.php?id=748
'프로젝트 > Pintos' 카테고리의 다른 글