xv6 디버깅

xv6 GDB로 디버깅하기

xv6은 QEMU 아래서 돌아간다. 그래서 기존 유저레벨 프로그램들과는 다르게 QEMU머신에 원격으로 접속해서 디버깅을 진행한다. 교수님이 공부하시던 옛날에는 VM 이런 거 없이 베어메탈 머신에 OS 깔아서 랜선 꼽아 디버깅을 진행했다고 한다. 얼마나 좋아진 세상인가 ㅋㅋ

기본 설정

디버깅 옵션 설정해 xv6 컴파일

이 옵션 사용해 xv6 컴파일

$ make qemu-nox-gdb

(X로 화면 출력하고 싶으면 qemu-gdb로 해도 됩니다.)

make하고 나면 다음과 같이 출력됩니다.

$ make qemu-nox-gdb
sed "s/localhost:1234/localhost:26000/" < .gdbinit.tmpl > .gdbinit
*** Now run 'gdb'.
qemu-system-i386 -nographic -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp 2 -m 512  -S -gdb tcp::26000

저 포트번호(26000)을 기억해 두세요. 사람마다 약간씩 다를 수 있어요.

이렇게 띄워 놓으면 xv6는 디버깅 준비가 다 되었습니다.

xv6 gdb로 연결

터미널 창 하나 더 띄워서 여기엔 gdb를 켜 주세요.

킬 때는 kernel파일로 켜 주시면 됩니다.

$ gdb kernel

그 다음 qemu의 xv6에 연결합니다.

(gdb) target remote localhost:26000

아까 기억해 둔 포트 번호를 사용하면 됩니다.

잘 연결되었다면

Remote debugging using localhost:26000
0x0000fff0 in ?? ()

와 같이 뜰 겁니다.

이제 gdb를 사용해 디버깅할 수 있어요.

xv6 안의 유저프로그램을 디버깅하고 싶으면

(gdb) add-symbol-file _cat

와 같이 심볼파일 넣어주면 됩니다.

Example 1: Tracing syscall

In this example, we will trace how xv6 handles system calls.

We will go through the system call fork, which will be used in the first process _init to launch 'sh'.

First breakpoint - init.c:24

24th line of init.c calls fork().

To set a breakpoint in init.c, you should add symbols from _init first by add-symbol-file _init.

Then set a breakpoint using:

(gdb) break init.c:24

After setting up this breakpoint, continue until your xv6 hits this breakpoint.

After hitting this breakpoint, step few times and see how xv6 makes a trap frame.
You can always check the values of registers by typing info registers.

For your information, you will be at the kernel mode after sending int 0x40, a syscall interrupt.

Second breakpoint - syscall.c:syscall()

syscall() function is called by trap() function.

Set a breakpoint using:

(gdb) break syscall

You can check the number of system call by print myproc()->tf->eax, which would be 1(SYS_fork).

Using this syscall number, it will find out a corresponding function to handle this syscall.

Third breakpoint - sysproc.c:sys_fork

sys_fork is the corresponding function for SYS_fork. It will call fork() from proc.c,
which really forks the process.

Set a breakpoint using:

(gdb) break sys_fork

After hitting this breakpoint, step forward to see how forking actually happens in the kernel.

Example 2: Tracing the first process, init

In this example, we will trace how xv6 runs its first process.

xv6 loads its first process in userinit(), which is called in main() at main.c. Then it is launched by the scheduler.

Let's see how scheduler is called and how it runs the process.

First breakpoint - mpmain()

The function mpmain will be called with your first cpu. It will launch scheduler and fetch jobs to do next.

Set a breakpoint at mpmain by

(gdb) break mpmain

(If you want a shorter one, you can also set it with b mpmain)

Then type continue in gdb. It will hit this breakpoint right after booting up.

In mpmain, you can step some steps to examine what is going on.

Second breakpoint - scheduler()

The function scheduler will be called by mpmain. This function iterates through available processes and find
the next runnable process.

Set a breakpoint at scheduler by

(gdb) break scheduler

If you continue your execution after hitting the first breakpoint, you will hit this breakpoint.
In this context, you may see some global variables, like ptable and see how it looks like.

(gdb) p ptable
$1 = {lock = {locked = 0, name = 0x80107620 "ptable", cpu = 0x0, pcs = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, proc = {{sz = 0, 
      pgdir = 0x0, kstack = 0x0, state = UNUSED, pid = 0, parent = 0x0, tf = 0x0, context = 0x0, chan = 0x0, killed = 0, ofile = {
        0x0 <repeats 16 times>}, cwd = 0x0, name = '\000' <repeats 15 times>} <repeats 64 times>}}

Since we have two CPUs, this breakpoint may be hit by the second CPU, before setting up ptable.
You can check it using info threads. In my case, it was hit before loading the first process.
You can also move to a certain thread with a command thread #.

Third breakpoint - proc.c:342

Set a breakpoint with this command:

break proc.c:342

It will set a breakpoint at the line number 342 in proc.c file.

Based on the vanila xv6 from xv6-public repo, line 342 of proc.c will be:

      c->proc = p; // line 342
      switchuvm(p);
      p->state = RUNNING;

Change the number 342 to the corresponding line number at your xv6.

This line will be run directly after finding a RUNNABLE process.
Continue until your gdb hits this breakpoint.

When this breakpoint is hit, you can find out which process is ready to be launched.

(gdb) p *p
$7 = {sz = 4096, pgdir = 0x8dffe000, kstack = 0x8dfff000 "", state = RUNNABLE, pid = 1, parent = 0x0, tf = 0x8dffffb4, 
  context = 0x8dffff9c, chan = 0x0, killed = 0, ofile = {0x0 <repeats 16 times>}, cwd = 0x80110a14 <icache+52>, 
  name = "initcode\000\000\000\000\000\000\000"}

You can find out the name of the process is "initcode", which xv6 loaded in userinit() at main().

Fourth breakpoint - main() at init.c

To debug an user-mode program running on xv6, we need to load symbols from the binary.

You can do it with a following command:

(gdb) add-symbol-file _init

If you are debugging mostly at the user level and don't want to be confused with the kernel symbols,
you can use symbol-file _init command that changes the current symbol file.

Then you will be able to set the breakpoint in your user program.

(gdb) break init.c:main

After some more continue, your xv6 will hit this fourth breakpoint.
(Recommend deleting the third breakpoint with delete 3)

you can find out variables from _init, such as:

(gdb) p argv
$10 = {0x846 "sh", 0x0 <main>}

which will be used at executing 'sh'.

Acknowledgments

이 가이드는 제가 작성했고, UWisc-Madison 운영체제 과목에서도 사용되었습니다. 예제의 경우 시간나면 번역할게요.

Read more

푸어 오버 커피 이야기

드립커피에 대해 좀 잘 알아보고 싶어서 Gemini 2.5 Pro Deep Research를 통해 조사해 보았습니다. 그리고 틈나는 대로 번역 + 의역 + 필요한 내용 가감 중입니다. 그냥 한국어로 담부터 조사를 시켜봐야겠어요... 틀린 내용이 있을수도 있으니 유의해 주시고 혹시 시간이 되신다면 댓글로 지적 부탁드립니다. 참고로 푸어오버와 드립이 혼용되고 있는데요, 제가 알기로는 일본식을 드립이라

By MaxLevSnail

Cruz Loma Gesha

로스터리: DuckRabbit Coffee, OH Producer: Galo Morales Origin : Pichincha, Equador Process-Type : Washed Varietal : Gesha (Geisha) Cup : Strawberry, Blackberry, Pineapple, Sage, Honeysuckle, Jasmine, Lemon Roasted on : 03.31.2025 덕래빗 로스터리 이름을 듣고 궁금해서 하나 시켜봤어요. exotic subscription을 끊었는데 원두값 $25 + 배송비 $5의 구독입니다. 이번엔 100g 원두 받았어요. 15g씩 먹어도 많아야

By MaxLevSnail
현대 프랑스 디저트의 역사 (1945년~)

현대 프랑스 디저트의 역사 (1945년~)

내가 공부해 보고 싶어서 GPT의 deep research 기능을 활용해 조사해 보았다. 시간날 때마다 번역 예정. 결국 GPT가 찾은 내용이기에 공신력은 없습니다. 틀린 내용이나 덧댈 내용 있으면 말씀해주세요. 식량 부족의 시기였던 제2차 세계대전 이후, 프랑스의 제과 예술은 다시 활기를 되찾았다. 버터, 설탕, 크림과 같은 주요 재료들이 풍부해지면서 제과 장인들은 오랫동안 사랑받아온

By MaxLevSnail