Generally speaking, use rumprun -D [port]
and target remote:[port]
in gdb.
disas
ordisas startaddr endaddr
- disassembles around current PC or specific addressinfo locals/args/variables
- prints out local variables, function arguments, all variables...info registers
- prints out all registers and their valuesbreak file:linenr
- sets a breakpoint on specific file/linec
- continue executionn
- step to next command in current stack framesi
- step by single instructionni
- step by single instruction but if it is a function call, step until it returnsl
- prints out few lines of code around current PCp $reg/variable
- prints values of registers or variablesx address
- print value at some address. Look at docs for more details on formatting
We'll walk through debugging hello_world Go application. Process is the same whether you debug C, Go, Erlang or any other language/setup.
Before running the kernel, start gdb in one terminal, from directory where the binary is located:
cd $HOME/gorump/examples/hello_world
gdb -ex 'target remote:1234' hello.bin
In another terminal, run the kernel:
rumprun xen -D 1234 -i hello.bin
gdb prompt should stop on 0x0000
and wait for input:
info "(gdb)Auto-loading safe path"
Remote debugging using :1234
0x0000000000000000 in _text ()
(gdb)
We can set a breakpoint on runtime1.go:77
, which is goenvs_unix
function in Go runtime. After that, we use c
to run until the breakpoint.
(gdb) break runtime1.go:77
Breakpoint 2 at 0x4ac89: file /usr/local/go/src/runtime/runtime1.go, line 77.
(gdb) c
Continuing.
Breakpoint 1, runtime.goenvs_unix () at /usr/local/go/src/runtime/runtime1.go:76
76 n := int32(0)
(gdb)
Execution of the kernel stops at this point and gdb waits for our input. We can investigate the surrounding code:
(gdb) l
71
72 func goenvs_unix() {
73 // TODO(austin): ppc64 in dynamic linking mode doesn't
74 // guarantee env[] will immediately follow argv. Might cause
75 // problems.
76 n := int32(0)
77 for argv_index(argv, argc+1+n) != nil {
78 n++
79 }
80
(gdb)
argv
and argc
are global variables in runtime
at this point, so we can't get their value with p argc
but need to specify exactly:
(gdb) p 'runtime.argc'
$1 = 1
(gdb) p 'runtime.argv'
$2 = (uint8 **) 0x3d5250 <kludge_argv>
(gdb)
We see that argv
is a double pointer, so we can unpack that and look at the value:
(gdb) x /1a 'runtime.argv' # /1a means "one as pointer" so it automatically reads what's on that address
0x3d5250 <kludge_argv>: 0x26d520
(gdb) x /1s 0x26d520 # /1s means "one string on that location"
0x26d520: "argument"
(gdb)
If we were to debug the stack of main
(which is where argc
and argv
reside) we can use x
to get multiple values:
(gdb) x /16w 0x3d5240
0x3d5240 <kludge_argc>: 0x00000001 0x00000000 0x00000000 0x00000000
0x3d5250 <kludge_argv>: 0x0026d520 0x00000000 0x0026d525 0x00000000
0x3d5260 <envp>: 0x0026d52b 0x00000000 0x00000000 0x00000000
0x3d5270: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
We can also debug our application of course. HelloWorld has a single .go file, hello.go, so we set a breakpoint:
(gdb) break hello.go:11
Breakpoint 4 at 0x176b7: file /home/ubuntu/gorump/examples/hello_world/hello.go, line 11.
(gdb)
Then, we just c
to continue running until the next breakpoint, which should be the one in hello.go
(gdb) c
Continuing.
Breakpoint 4, main.damain () at /home/ubuntu/gorump/examples/hello_world/hello.go:11
11 fmt.Println("Hello, Rumprun. This is Go.")
(gdb) l
6 func main() {
7 }
8
9 //export damain
10 func damain() {
11 fmt.Println("Hello, Rumprun. This is Go.")
12 }
(gdb)
Again, we can investigate variables, stack, code or machine instructions...