Changes between Initial Version and Version 1 of DebuggingWithQEMUAndGDB


Ignore:
Timestamp:
2016-07-09T14:22:48Z (8 years ago)
Author:
Jakub Jermář
Comment:

Add article on using QEMU and GDB for debugging purposes

Legend:

Unmodified
Added
Removed
Modified
  • DebuggingWithQEMUAndGDB

    v1 v1  
     1= Using QEMU and GDB to debug kernel and uspace tasks =
     2
     3The combination of QEMU and GDB allows HelenOS to be comfortably debugged either on the assembly or the source code level. For detailed information on low-level debugging, see for example this [http://d3s.mff.cuni.cz/teaching/crash_dump_analysis/ course] on crash dump analysis.
     4
     5== Preparing the build ==
     6
     7The default HelenOS build should produce unstripped binaries. If necessary, this can be enforced by making sure the `Strip binaries` build configuration option is not checked. Unstripped binaries come with symbols, but do not contain any fancier debugging information. In order to get maximum out of the debug build, make sure to configure HelenOS with the `Line debugging information` option. Another thing which may impede debugging is optimization, so consider changing optimization levels to 0 using the `OPTIMIZATION` variable in the respective Makefiles ([browser:mainline/kernel/Makefile] or [browser:mainline/uspace/Makefile.common]). When everything is set, make sure to rebuild with the new settings.
     8
     9== Starting QEMU ==
     10
     11QEMU provides two command line options for debugging with GDB: `-s` and `-S`. The former instructs QEMU to listen for GDB connections on localhost:1234 (but does not wait for it) and the latter stops the guest CPU at startup so that debugging is possible from the very beginning. When starting emulation using the [browser:mainline/tools/ew.py] script, one can add these options like this:
     12
     13{{{
     14$ `tools/ew.py -d` -s -S
     15}}}
     16
     17== Connecting GDB to QEMU ==
     18
     19Once QEMU is started with the `-s` (and optionally also the `-S`) option, it is possible to connect GDB to it. For our purposes, we will assume the respective cross-GDB built by the [browser:mainline/tools/toolchain.sh] is ready to be used and named as `/usr/local/cross/ia32/bin/i686-pc-linux-gnu-gdb`. Needless to say, the cross-GDB should always match the architecture of the HelenOS guest.
     20
     21{{{
     22$ /usr/local/cross/ia32/bin/i686-pc-linux-gnu-gdb
     23GNU gdb (GDB) 7.11
     24Copyright (C) 2016 Free Software Foundation, Inc.
     25License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
     26This is free software: you are free to change and redistribute it.
     27There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
     28and "show warranty" for details.
     29This GDB was configured as "--host=x86_64-pc-linux-gnu --target=i686-pc-linux-gnu".
     30Type "show configuration" for configuration details.
     31For bug reporting instructions, please see:
     32<http://www.gnu.org/software/gdb/bugs/>.
     33Find the GDB manual and other documentation resources online at:
     34<http://www.gnu.org/software/gdb/documentation/>.
     35For help, type "help".
     36Type "apropos word" to search for commands related to "word".
     37(gdb)
     38}}}
     39
     40Connect to QEMU using the following command at the `(gdb)` prompt:
     41
     42{{{
     43(gdb) target remote :1234
     44Remote debugging using :1234
     450x0000fff0 in ?? ()
     46}}}
     47
     48Note that if QEMU was not started with the `-S` option, you will have to manually break into the debugger by pressing `Ctrl-C`.
     49
     50== Loading symbols ==
     51
     52Depending on what is the subject of our debugging session, we will need some symbols. The easiest case is kernel debugging. In that case, we simply load the kernel symbols from `kernel.raw`:
     53
     54{{{
     55(gdb) symbol-file kernel/kernel.raw
     56Reading symbols from kernel/kernel.raw...done.
     57}}}
     58
     59Debugging userspace tasks is a little more complicated because unlike the kernel, there will be multiple user processes running at the same time, so setting a mere breakpoint on a user address will probably not do. We will need to get some assistance from the kernel.
     60
     61== Setting and hitting breakpoints ==
     62
     63To set a breakpoint, for example on the kernel function that handles invalid memory accesses from userspace, we type the following command:
     64
     65{{{
     66(gdb) break fault_from_uspace_core
     67Breakpoint 1 at 0x801237ff: file generic/src/interrupt/interrupt.c, line 169.
     68}}}
     69
     70{{{#!box type=warning
     71Sadly the set breakpoint is not always hit. At this point, the cause of this behavior is unknown.
     72}}}
     73
     74To continue the emulation, tell GDB to continue:
     75
     76{{{
     77(gbd) c
     78Continuing.
     79}}}
     80
     81When our breakpoint is later hit, for example as a result of executing inside HelenOS:
     82
     83{{{
     84# tester fault1
     85}}}
     86
     87we will break into the debugger prompt again:
     88
     89{{{
     90Breakpoint 1, fault_from_uspace_core (istate=0x86d47fb4, fmt=fmt@entry=0x8016f3ce "Page fault: %p.", args=args@entry=0x86d47ec4 "\004") at generic/src/interrupt/interrupt.c:169
     91169     {
     92(gdb)
     93}}}
     94
     95Note that at any time while the guest is running, you can break into the debugger also by pressing the `Ctrl-C` combo.
     96
     97Now that a breakpoint in the kernel was hit, we can inspect the state of the kernel a little bit. Typing `bt` will show us the stack trace of the current kernel thread:
     98
     99{{{
     100(gdb) bt
     101#0  fault_from_uspace_core (istate=0x86d47fb4, fmt=fmt@entry=0x8016f3ce "Page fault: %p.", args=args@entry=0x86d47ec4 "\004") at generic/src/interrupt/interrupt.c:169
     102#1  0x80123e2a in fault_if_from_uspace (istate=istate@entry=0x86d47fb4, fmt=fmt@entry=0x8016f3ce "Page fault: %p.") at generic/src/interrupt/interrupt.c:206
     103#2  0x80130bc9 in as_page_fault (address=4, access=PF_ACCESS_WRITE, istate=istate@entry=0x86d47fb4) at generic/src/mm/as.c:1497
     104#3  0x8010f978 in page_fault (n=14, istate=0x86d47fb4) at arch/ia32/src/mm/page.c:99
     105#4  0x80123bda in exc_dispatch (n=14, istate=0x86d47fb4) at generic/src/interrupt/interrupt.c:131
     106#5  0x8010ab62 in int_14 () at arch/ia32/src/asm.S:437
     107#6  0x0000000e in ?? ()
     108}}}
     109
     110To see the information about the interrupted context, in this case the user context as it existed when the page fault exception occurred, we can print the `istate` structure:
     111
     112{{{
     113(gdb) set radix 16
     114Input and output radices now set to decimal 16, hex 10, octal 20.
     115(gdb) p *istate
     116$2 = {edx = 0x80, ecx = 0x7, ebx = 0x7013cee0, esi = 0x70037f98, edi = 0x7001b784, ebp = 0x7013ce78, eax = 0x4, ebp_frame = 0x0, eip_frame = 0x3da7, gs = 0x30, fs = 0x23, es = 0x23, ds = 0x23,
     117  error_word = 0x6, eip = 0x3da7, cs = 0x1b, eflags = 0x10202, esp = 0x7013ce78, ss = 0x23}
     118}}}
     119
     120The printed `eip` member is the program counter of the instruction which caused the exception. We will remember this value along with the value of `esp` and `ebp` for later.
     121
     122== Useful macros ==
     123
     124When debugging the kernel, it is sometimes useful to find out some information about the current process. For that, we will need to mimic the computation of the `THE` structure. We will start by defining a macro:
     125
     126{{{
     127(gdb) macro define the ((the_t *) ((uintptr_t )$esp & ~0x1fff))
     128}}}
     129
     130From this time on, we can do things like:
     131
     132{{{
     133(gdb) p the->task->name
     134$3 = "tester\000it\000\000\000\000\000\000\000\000\000\000"
     135}}}
     136
     137Note that this will work only when `$esp` corresponds to the kernel stack.
     138
     139== Switching to the user context ==
     140
     141Let us assume that we have somehow found the values of the userspace registers EIP, ESP and EBP (for example by inspecting the `istate` structure as shown above) and know the name of the process (for example `tester` from the example above). Before we can add symbol information for this process, we need to find out the load address of its `.text` section (for some reason, GDB does not use the information provided in the ELF file):
     142
     143{{{
     144$ objdump -h uspace/app/tester/tester
     145
     146uspace/app/tester/tester:     file format elf32-i386
     147
     148Sections:
     149Idx Name          Size      VMA       LMA       File off  Algn
     150  0 .init         0000002e  000010b4  000010b4  000000b4  2**0
     151                  CONTENTS, ALLOC, LOAD, READONLY, CODE
     152  1 .text         0002f5a0  000010e8  000010e8  000000e8  2**3
     153                  CONTENTS, ALLOC, LOAD, READONLY, CODE
     154  2 .data         00001b08  000316a0  000316a0  0002f6a0  2**5
     155                  CONTENTS, ALLOC, LOAD, DATA
     156  3 .tbss         00000048  000331a8  000331a8  000311a8  2**2
     157                  ALLOC, THREAD_LOCAL
     158  4 .bss          00000184  000331c0  000331c0  000311a8  2**5
     159                  ALLOC
     160  5 .comment      00000011  00000000  00000000  000311a8  2**0
     161                  CONTENTS, READONLY
     162  6 .debug_abbrev 0000878f  00000000  00000000  000311b9  2**0
     163                  CONTENTS, READONLY, DEBUGGING
     164  7 .debug_aranges 00000db8  00000000  00000000  00039948  2**3
     165                  CONTENTS, READONLY, DEBUGGING
     166  8 .debug_info   000293a5  00000000  00000000  0003a700  2**0
     167                  CONTENTS, READONLY, DEBUGGING
     168  9 .debug_line   0000cfc7  00000000  00000000  00063aa5  2**0
     169                  CONTENTS, READONLY, DEBUGGING
     170 10 .debug_ranges 00000358  00000000  00000000  00070a6c  2**0
     171                  CONTENTS, READONLY, DEBUGGING
     172 11 .debug_str    000078fc  00000000  00000000  00070dc4  2**0
     173                  CONTENTS, READONLY, DEBUGGING
     174}}}
     175
     176So the `.text` section gets loaded at 0x10e8 in this case. We will use this address to load our userspace symbols:
     177
     178{{{
     179(gdb) add-symbol-file uspace/app/tester/tester 0x000010e8
     180add symbol table from file "uspace/app/tester/tester" at
     181        .text_addr = 0x10e8
     182(y or n) y
     183Reading symbols from uspace/app/tester/tester...done.
     184}}}
     185
     186The last step before we can print out our userspace stack trace is restoring registers to their userspace contents (as for example captured in the `istate` structure):
     187
     188{{{
     189(gdb) set $eip=0x3da7
     190(gdb) set $esp=0x7013ce78
     191(gdb) set $ebp=0x7013ce78
     192}}}
     193
     194We are now ready to do some userspace debugging:
     195
     196{{{
     197(gdb) bt
     198#0  0x00003da7 in test_fault1 () at fault/fault1.c:34
     199#1  0x000010f6 in run_test (test=0x31770 <tests+208>) at tester.c:84
     200#2  0x00001374 in main (argc=0x43060, argv=0x0) at tester.c:161
     201#3  0x000134e3 in __main (pcb_ptr=0x7001b784) at generic/libc.c:121
     202#4  0x000010e2 in __entry () at arch/ia32/src/entry.S:69
     203#5  0x7001b784 in ?? ()
     204}}}
     205
     206{{{
     207(gdb) disassemble
     208Dump of assembler code for function test_fault1:
     209   0x00003d9f <+0>:     push   %ebp
     210   0x00003da0 <+1>:     mov    %esp,%ebp
     211   0x00003da2 <+3>:     mov    $0x4,%eax
     212=> 0x00003da7 <+8>:     movl   $0x0,(%eax)
     213   0x00003dad <+14>:    mov    $0x2d973,%eax
     214   0x00003db2 <+19>:    pop    %ebp
     215   0x00003db3 <+20>:    ret   
     216End of assembler dump.
     217}}}