0%

Debug threads using GDB

Instructions

gdb ./program_name . Start the debugger.

disass Function . Show the disassemble code of Function.

b x . Set a breakpoint at line x.

b Function . Set a breakpoint at the beginning of function.

info b . List the information of breakpoints.

info r . List all values of registers.

list Function . Show the source code of function.

p currentThread . Show the address of the main thread.

run . Run from the beginning.

c . Continue running.

ni/si . Run a step.

x *address . Show the function in address.

Get started

In threads directory:

gdb ./nachos

Then, show the disassemble code of main function:

disass main

We can see a few addresses of functions:

0x00001404 <+23>:    call   0x12f0 <__x86.get_pc_thunk.bx>
0x0000142d <+64>: call 0x2727 <Initialize(int, char**)>
0x00001435 <+72>: call 0x32ce <ThreadTest()>
0x0000143a <+77>: call 0x3730 <SynchTest()>
0x00001449 <+92>: jle 0x1490 <main(int, char**)+163>
0x00001462 <+117>: call 0x10e0 <strcmp@plt>
0x0000146c <+127>: jne 0x1480 <main(int, char**)+147>
0x00001478 <+139>: call 0x11c0 <printf@plt>
0x0000148e <+161>: jmp 0x1446 <main(int, char**)+89>
0x0000149c <+175>: call 0x2cfc <Thread::Finish()>

Start from initialize . disass Initialize but nothing we are looking for.

In ThreadTest() :

0x00003343 <+117>:   call   0x3273 <SimpleThread(int)>

The address of SimpleThread(int) is 0x3273 .

In SynchTest() , nothing.

Oh, just get started… Now dig into the questions.

Find the address of Funtion

Use b function to set breakpoint, then it returns the address.

(gdb) b InterruptEnable
Breakpoint 4 at 0x56558025: file thread.cc, line 242.
(gdb) b SimpleThread
Breakpoint 5 at 0x56558273: file threadtest.cc, line 26.
(gdb) b ThreadFinish
Breakpoint 6 at 0x56557ffa: file thread.cc, line 241.
(gdb) b ThreadRoot
Breakpoint 7 at 0x56559e7a

Find the address of threads (both main and forked)

Let’s look into the main function, in the first few lines, we can see there is a Initialize function call. Look into it, there seems to be nothing major. Then look down, we see ThreadTest(), then look into it. It calls SimpleThread() in threadtest.cc :

void SimpleThread(_int which) {
int num;
for (num = 0; num < 5; num++) {
printf("*** thread %d looped %d times\n", (int) which, num);
currentThread->Yield();
}
}

We can see a Yield() function, which makes the current thread to give up CPU to another thread, so we make a breakpoint here. When it yields, it’s obviously another thread running, so we can see the address of thread.

(gdb) list SimpleThread
(gdb) b 31
Breakpoint 1 at 0x32ae: file threadtest.cc, line 31.
run
Starting program: /root/OS/threads/nachos
*** thread 0 looped 0 times

Breakpoint 1, SimpleThread (which=0) at threadtest.cc:31
31 currentThread->Yield();
(gdb) p currentThread
$1 = (Thread *) 0x56563ca0
(gdb) c
Continuing.
*** thread 1 looped 0 times

Breakpoint 1, SimpleThread (which=1) at threadtest.cc:31
31 currentThread->Yield();
(gdb) p currentThread
$2 = (Thread *) 0x56563d00

We can see the thread switch and also the address of threads using p currentThread

So the main thread of the Nachos is 0x56563ca0 , and the forked thread created by the main thread is 0x56563d00 .

Digging into SWITCH()

When the main thread executes SWITCH() function for the first time, to what address the CPU returns when it executes the last instruction ret of SWITCH()? What location in the program that address is referred to?

When the forked thread executes SWITCH() function for the first time, to what address the CPU returns when it executes the last instruction ret of SWITCH()? What location in the program that address is referred to?

Looking through all files in the directory, one thing is sure that the SWITCH() function is written in disassemble language.

(gdb) disass SWITCH
Dump of assembler code for function SWITCH:
0x56559e84 <+0>: mov %eax,0x5655e054
0x56559e89 <+5>: mov 0x4(%esp),%eax
0x56559e8d <+9>: mov %ebx,0x8(%eax)
0x56559e90 <+12>: mov %ecx,0xc(%eax)
0x56559e93 <+15>: mov %edx,0x10(%eax)
0x56559e96 <+18>: mov %esi,0x18(%eax)
0x56559e99 <+21>: mov %edi,0x1c(%eax)
0x56559e9c <+24>: mov %ebp,0x14(%eax)
0x56559e9f <+27>: mov %esp,(%eax)
0x56559ea1 <+29>: mov 0x5655e054,%ebx
0x56559ea7 <+35>: mov %ebx,0x4(%eax)
0x56559eaa <+38>: mov (%esp),%ebx
0x56559ead <+41>: mov %ebx,0x20(%eax)
0x56559eb0 <+44>: mov 0x8(%esp),%eax
0x56559eb4 <+48>: mov 0x4(%eax),%ebx
0x56559eb7 <+51>: mov %ebx,0x5655e054
0x56559ebd <+57>: mov 0x8(%eax),%ebx
0x56559ec0 <+60>: mov 0xc(%eax),%ecx
0x56559ec3 <+63>: mov 0x10(%eax),%edx
0x56559ec6 <+66>: mov 0x18(%eax),%esi
0x56559ec9 <+69>: mov 0x1c(%eax),%edi
0x56559ecc <+72>: mov 0x14(%eax),%ebp
0x56559ecf <+75>: mov (%eax),%esp
0x56559ed1 <+77>: mov 0x20(%eax),%eax
0x56559ed4 <+80>: mov %eax,(%esp)
0x56559ed7 <+83>: mov 0x5655e054,%eax
0x56559edc <+88>: ret
0x56559edd <+89>: xchg %ax,%ax
0x56559edf <+91>: nop

In line <+80>, make a breakpoint, run, and look into registers using info r , then show the location by x address .

(gdb) b *0x56559ed4
Breakpoint 2 at 0x56559ed4
(gdb) run
Starting program: /root/OS/threads/nachos
*** thread 0 looped 0 times

Breakpoint 2, 0x56559ed4 in SWITCH ()

(gdb) info r
eax 0x56559e76 1448451702
.............................................
(gdb) x 0x56559e76
0x56559e76 <ThreadRoot>: 0x52e58955
(gdb) c
Continuing.
*** thread 1 looped 0 times

Breakpoint 2, 0x56559ed4 in SWITCH ()
(gdb) info r
eax 0x56556a24 1448438308
.............................................
(gdb) x 0x56556a24
0x56556a24 <Scheduler::Run(Thread*)+144>: 0x8d10c483

The CPU returns to address 0x56559e76 , which located to <ThreadRoot> when it executes the last instruction ret of SWITCH() for the first time when the main thread executes SWITCH() function, and the second time it returns to 0x56556a24 locating to <Scheduler::Run(Thread*)+144> .