/////
Search

Appendix E : Debugging Tools

Debugging Tools

printf()

과소평가하지마라!
pintos 내에서 printf가 만들어지기 때문에, 커널 어디서든 사용할 수 있고, kernel thread 안에서든 interrupt handler에서든 lock이 걸려있는 어디서든 사용 가능함.
printf()는 단순히 examining data하는 것보다는 usefull할거다. 이건 그리고 어디서, 언제 뭐가 잘못됬는지 찾을 수 있게 해주고, 뿐만 아니라 useful error message를 내놓지 않은 panic이나 crash일 때도 사용가능하다. fail이 예상되는 지점에 printf를 하는데, 이 때 다른 번호가 출력되게 해라.

Assert

Assert는 문제를 일찍 잡을 수 있기 때문에 좋다. 이상적으로, 각 함수는 몇 가지 set의 assertion으로 시작하고, 주로 argument의 validity를 체크한다. 주로 loop invariants를 체크하는데 사용한다.
핀토스는 ASSERT 매크로를 제공한다.
만약 평가했을 때, false라면 kernel panic이다. panic 메시지는 파일과 line number를 포함한채로 뜬다. 또한 문제 해결을 위한 backtrace 또한 제공한다.

Function and Parameter Attributese

이 매크로는 debug.h에 정의되어있고, 컴파일로 하여금 function이나 function parameter에 대해 특별한 속성을 알게 한다. 이건 gcc specific하다.
1.
UNUSED
a.
function parameter에 붙이는 거로, 함수 내에서 이 파라미터가 안 쓰일 것을 얘기한다. 만약 다른 곳에서 나타난다면 warning을 준다.
2.
NO_RETURN
a.
function prototype 에 추가되는 것으로, 컴파일로하여금 함수의 return값이 없다고 말해준다.
3.
NO_INLINE
a.
function prototype에 붙는 것으로, 이 function을 절대 in-line으로 생략할 수 없도록 한다. 가끔씩 backtrace의 quality를 높이는데 사용된다.
4.
PRINT_FORMAT(format,first)
a.
function prototype에 붙는 것으로, 컴파일러에게 이 함수는 printf()와 같은 format string을 argument로 받고, 그리고 이에 대한 값을 first로 받는 다는 것을 알려준다. 이것은 컴파일러가 잘못된 argument type을 패스하는 것을 말해주도록 한다.

Backtraces

만약에 kernel이 패닉을 하면, 이건 “backtrace”를 프린트하는데, 이건 지금 실행중인 함수 안 어디서 프로그램이 에러를 받았는지에 대한 요약이다. 또한 debug_backtrace()라는 함수 콜을 insert할 수 있는데, 이건 backtrace를 코드 안 어디서든 출력할 수 있게 해준다. debug_backtrace_all()은 모든 쓰레드의 backtrace를 프린트하게 해준다.
출력되는 backtrace의 주소는 hexadecimal number이고, 이건 해석하기가 힘들다. 그래서 backtrace라는 명령어를 제공해서 이것을 function name과 source file line number를 알 수 있게 해준다. backtrace kernel.o 라하고 backtrace를 구성하는 hexadecimal number를 던져주면, 각 주소에 맞는 함수와 file line number를 제공해줄꺼다.
만약 backtrace 결과가 왜곡됬거나, 이해가 안된다면( 예를 들어 B위에 A가 list되있는데, B가 A를 호출한적이 없다면), 이건 너가 kernel thread의 스택을 훼손시켰을 가능성이 있다, 왜냐하면 backtrace는 stack에서 추출되기 때문이다. 다른 이유로, 너가 backtrace 명령에 친 kernel.o가 backtrace를 생성한 kernel이랑 같지 않을 수 있다.
때때로, corruption 없이도, backtrace는 헷갈릴 수 있다. 컴파일러 최적화는 가끔 surprising behavior을 할 때가 있다. 예를 들어 어떤 함수가 마지막 행동으로써 다른 함수를 호출한다면, 불려진 함수가 backtrace에 안 남을 수 있다. 예를 들어 A가 B를 호출하는데, B가 아무것도 return하지 않는다면, 컴파일러는 이거를 최적화를 시켜버리고, A 대신에 전혀 다른 C가 backtrace에 남을 수 있다. C는 A 이후에 등장할 함수일 수 있다. thread project에서 ,test fail 케이스에서 이런 현상을 주로 볼 수 있다.
backtrace kernel.o Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8.
JavaScript
복사
이렇게 하면 콜스택에 쌓인거를 다 확인할 수 있다.

GDB

pintos를 —gdb 옵션으로 켜야한다. (pintos —gdb — run mytest)
그리고 다른 터미널을 켜서, pintos-gdb를 사용해서 kernel.o에 대해 GDB를 킨다.
pintos-gdb kernel.o target remote loaclhost:1234
JavaScript
복사

gdb command

c
실행을 계속한다. breakpoint를 만나거나, ctrl + C를 누를 때까지
break function, break file:line, break *address
breakpoint를 함수나, 파일안의 line이나 주소에 설정한다.
gdb main을 하면 pintos가 시작될 때 멈출 수 있다.
p expression
주어진 expression을 평가하고, 그 값을 출력할 수 있다. 만약 expression이 함수 call을 포함한다면, 그 함수는 실행된다.
l *address
그 주소 주변으로 몇 라인을 출력한다.
bt
backtrace랑 비슷한거다.
p/a address
해당 주소를 점유하는 variable이나 함수의 이름을 출력한다.
diassemble function
Disassembles function(??)

GDB Macro

pintos 디버깅을 위한 macro를 지원한다.
debugpintos
같은 머신 상에서 대기하고 있는 pintos process에 debugger를 붙인다.
shorthand for target remote localhost:1234
dumplist list type element
Prints the elements of list, which should be a struct list that contains elements of the given type (without the word struct) in which element is the struct list_elem member that links the elements
dumplist all_list thread allelem
struct list_elem allelem을 사용해서 struct thread의 일부인 struct list all_list에 연결되어있는 struct thread의 모든 요소를 프린트한다.
document dumplist Dump the content of a Pintos list, invoke as dumplist name_of_list name_of_struct name_of_elem_in_list_struct end ex) dumplist all_list thread allelem
JavaScript
복사
btthread thread
thread의 backtrace를 보여준다. backtrace를 보여줘야하는 쓰레드의 struct thread를 가리키는 pointer여야함. current thread라면 bt 랑 같다.
그냥 단지 그것의 커널 스택 페이지가 어디에 위치해있는지를 제공해줌으로써 suspended한 쓰레드도 다 보여줄 수 있다.
btthreadlist list element
모든 list에 있는 모든 쓰레들의 backtrace를 보여준다.
btthreadlist all_list allelem
bthreadall
shorthand for btthreadlist all_list allelem
btpagefault
page fault exception이 난 직후에 current thread에 대한 backtrace를 프린트한다. 주로, 페이지 fault가 발생했을 대, GDB는 이를 멈추고 메시지를 띄운다.
Program received signal 0, Signal 0. 0xc0102320 in intr0e_stub ()
JavaScript
복사
이러면 bt는 딱히 유용하지 않다. btpagefault를 대신 사용해라. user process 상에서도 pagefault가 났을 때 btpagefault를 사용할 수 있다.
이 케이스에서는, user program의 심볼 테이블을 loadusersymbols macro를 사용해야할거다.
hook-stop
GDB invokes this macro every time the simulation stops, which Bochs will do for every processor exception, among other reasons. If the simulation stops due to a page fault, hook-stop will print a message that says and explains further whether the page fault occurred in the kernel or in user code. If the exception occurred from user code, hook-stop will say: pintos-debug: a page fault exception occurred in user mode pintos-debug: hit ’c’ to continue, or ’s’ to step to intr_handler In Project 2, a page fault in a user process leads to the termination of the process. You should expect those page faults to occur in the robustness tests where we test that your kernel properly terminates processes that try to access invalid addresses. To debug those, set a break point in page_fault() in ‘exception.c’, which you will need to modify accordingly. In Project 3, a page fault in a user process no longer automatically leads to the termination of a process. Instead, it may require reading in data for the page the process was trying to access, either because it was swapped out or because this is the first time it’s accessed. In either case, you will reach page_fault() and need to take the appropriate action there. If the page fault did not occur in user mode while executing a user process, then it occurred in kernel mode while executing kernel code. In this case, hook-stop will print this message:
pintos-debug: a page fault occurred in kernel mode followed by the output of the btpagefault command. Before Project 3, a page fault exception in kernel code is always a bug in your kernel, because your kernel should never crash. Starting with Project 3, the situation will change if you use the get_user() and put_user() strategy to verify user memory accesses (see Section 3.1.5 [Accessing User Memory], page 27).

Example

쓰레드가 timer_slleep한 쓰레드가 깨지 않는 문제에 대한 솔루션을 예시로 든거임.
mlfqs_load_1에서 딱 막히는거다.
그냥 나중에 좀 보는게 빠르겠다.