An example of debugging
Context
The following example will try to reuse a maximum of the most used commands when debugging a DPU program. The content of the program is not important here: the program has no specific goal, and we are not debugging it to find some hidden bug (which would be a standard use for a debugger).
The source files are debug_example_main.c and debug_example_misc.c:
#include <defs.h>
#include <stdint.h>
#include "debug_example_misc.h"
int bar(int var) {
int my_var = global_variable;
return misc_func(var, my_var);
}
int foo(int var) {
if (var > 10)
return bar(var + 1);
return bar(var);
}
int func(int my_var) {
int acc = 0;
for (int i = 0; i < my_var; ++i) {
acc = foo(acc + i);
}
return acc;
}
int main() {
int my_var = 12 + me();
return func(my_var + 1);
}
#include "debug_example_misc.h"
int32_t global_variable = 42;
uint32_t misc_func(uint32_t param1, uint32_t param2) {
global_variable++;
uint32_t add = param1 + param2;
uint32_t mult = param1 * param2;
return add | mult;
}
One header file is also needed, debug_example_misc.h:
#ifndef __DEBUG_EXAMPLE_MISC_H__
#define __DEBUG_EXAMPLE_MISC_H__
#include <stdint.h>
extern int32_t global_variable;
uint32_t misc_func(uint32_t param1, uint32_t param2);
#endif /* __DEBUG_EXAMPLE_MISC_H__ */
We are building the program in debug mode with dpu-upmem-dpurte-clang:
dpu-upmem-dpurte-clang debug_example_main.c debug_example_misc.c -o debug_example
This creates the binary debug_example.
Initialization
First we load the program and check that we have debug information:
file debug_example
target modules dump symfile
We can now see that dpu-lldb manage to get debug information from the ELF DPU binary:
Index UserID DSX Type File Address/Value Load Address Size Flags Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
[ 0] 1 SourceFile 0x0000000000000000 0x0000000000000000 0x00000004 debug_example_main.c
[ 1] 2 Code 0x0000000080000248 0x0000000000000010 0x00000002 me
[ 2] 3 SourceFile 0x0000000000000000 0x0000000000000000 0x00000004 debug_example_misc.c
[ 3] 4 SourceFile 0x0000000000000000 0x0000000000000000 0x00000004 mul32.c
[ 4] 5 Code 0x0000000080000428 0x0000000000000010 0x00000000 __mulsi3_exit
[ 5] 6 Code 0x0000000080000320 0x0000000000000108 0x00000000 __mulsi3_start
[ 6] 7 Code 0x0000000080000310 0x0000000000000010 0x00000000 __mulsi3_swap
[ 7] 8 SourceFile 0x0000000000000000 0x0000000000000000 0x00000004 crt0.c
[ 8] 9 Code 0x0000000080000018 0x0000000000000018 0x00000000 __sys_atomic_bit_clear
[ 9] 10 Code 0x0000000080000030 0x0000000000000020 0x00000000 __sys_start_thread
[ 10] 11 SourceFile 0x0000000000000000 0x0000000000000000 0x00000004 stdio.c
[ 11] 12 SourceFile 0x0000000000000000 0x0000000000000000 0x00000004 memset.c
[ 12] 13 Data 0x0000000000000018 0x00000000000000c0 0x00000200 __imm_mem_end
[ 13] 14 X Code 0x0000000080000058 0x0000000000000050 0x00000012 bar
[ 14] 15 X Code 0x00000000800000a8 0x0000000000000090 0x00000012 foo
[ 15] 16 X Code 0x0000000080000138 0x00000000000000b8 0x00000012 func
[ 16] 17 X Data 0x00000000000000d8 0x0000000000000004 0x00000011 global_variable
[ 17] 18 X Code 0x00000000800001f0 0x0000000000000058 0x00000012 main
[ 18] 19 X Code 0x0000000080000258 0x00000000000000a0 0x00000012 misc_func
[ 19] 20 X Code 0x00000000800002f8 0x0000000000000140 0x00000012 __mulsi3
Objectives
Before starting the real debugging, let’s define some objectives. We want to:
track the value of
global_variable(defined indebug_example_misc.h)track the value of
my_var(defined at multiple places indebug_example_main.c)check the parameters of
misc_func(defined indebug_example_misc.c) when the function is calledmake some precise step by step debugging in the
funcfunction (defined indebug_example_main.c)
At different points, we may add some auxiliary objectives to make use of other commands.
Start of the debugging
Let’s add the breakpoints and hooks to achieve our objectives:
(lldb) breakpoint set -n misc_func
Breakpoint 1: where = debug_example`misc_func + 32 at debug_example_misc.c:6:18, address = 0x80000278
(lldb) breakpoint set -n func
Breakpoint 2: where = debug_example`func + 24 at debug_example_main.c:20:7, address = 0x80000150
(lldb) breakpoint set -n main
Breakpoint 3: where = debug_example`main + 24 at debug_example_main.c:30:21, address = 0x80000208
(lldb) target stop-hook add -o "target variable global_variable"
Stop hook #1 added.
(lldb) target stop-hook add -o "frame variable my_var"
Stop hook #2 added.
(lldb) process launch
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 42
- Hook 2 (frame variable my_var)
(int) my_var = 0
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = breakpoint 3.1
frame #0: 0x80000208 debug_example`main at debug_example_main.c:30:21
27 }
28
29 int main() {
-> 30 int my_var = 12 + me();
^
31 return func(my_var + 1);
32 }
When stopping at the start of a function, the local variables may not have been initialized yet.
This explains the value of my_var as 0x00000000 being the default value for non-initialized stack (but it could be another value if the stack has already been used or after a reset).
Step by step debugging
Now, let’s continue until we hit the breakpoint on the func function, then step over until we hit the breakpoint on the misc_func function:
(lldb) process continue
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 42
- Hook 2 (frame variable my_var)
(int) my_var = 13
Process 21312 resuming
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = breakpoint 2.1
frame #0: 0x80000150 debug_example`func(my_var=13) at debug_example_main.c:20:7
17 }
18
19 int func(int my_var) {
-> 20 int acc = 0;
^
21
22 for (int i = 0; i < my_var; ++i) {
23 acc = foo(acc + i);
(lldb) thread step-over
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 42
- Hook 2 (frame variable my_var)
(int) my_var = 13
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step over
frame #0: 0x80000158 debug_example`func(my_var=13) at debug_example_main.c:22:12
19 int func(int my_var) {
20 int acc = 0;
21
-> 22 for (int i = 0; i < my_var; ++i) {
^
23 acc = foo(acc + i);
24 }
25
(lldb) thread step-over
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 42
- Hook 2 (frame variable my_var)
(int) my_var = 13
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step over
frame #0: 0x80000188 debug_example`func(my_var=13) at debug_example_main.c:23:15
20 int acc = 0;
21
22 for (int i = 0; i < my_var; ++i) {
-> 23 acc = foo(acc + i);
^
24 }
25
26 return acc;
(lldb) thread step-over
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 42
- Hook 2 (frame variable my_var)
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = breakpoint 1.1
frame #0: 0x80000278 debug_example`misc_func(param1=0, param2=42) at debug_example_misc.c:6:18
3 int32_t global_variable = 42;
4
5 uint32_t misc_func(uint32_t param1, uint32_t param2) {
-> 6 global_variable++;
^
7 uint32_t add = param1 + param2;
8 uint32_t mult = param1 * param2;
9 return add | mult;
At each step, we can see the value of our tracked variables (the value of my_var is only printed if the variable exists).
We can also check the parameters of the misc_func function.
Let’s step to see the global_variable variable being incremented and then step-out until we are back into the func function:
(lldb) thread step-over
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 43
- Hook 2 (frame variable my_var)
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step over
frame #0: 0x80000290 debug_example`misc_func(param1=0, param2=42) at debug_example_misc.c:7:18
4
5 uint32_t misc_func(uint32_t param1, uint32_t param2) {
6 global_variable++;
-> 7 uint32_t add = param1 + param2;
^
8 uint32_t mult = param1 * param2;
9 return add | mult;
10 }
(lldb) target variable global_variable
(int32_t) global_variable = 43
(lldb) thread step-out
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 43
- Hook 2 (frame variable my_var)
(int) my_var = 42
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step out
frame #0: 0x80000098 debug_example`bar(var=0) at debug_example_main.c:9:3
6 int bar(int var) {
7 int my_var = global_variable;
8
-> 9 return misc_func(var, my_var);
^
10 }
11
12 int foo(int var) {
(lldb) thread step-out
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 43
- Hook 2 (frame variable my_var)
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step out
frame #0: 0x80000110 debug_example`foo(var=0) at debug_example_main.c:16:3
13 if (var > 10)
14 return bar(var + 1);
15
-> 16 return bar(var);
^
17 }
18
19 int func(int my_var) {
(lldb) thread step-out
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 43
- Hook 2 (frame variable my_var)
(int) my_var = 13
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step out
frame #0: 0x800001a8 debug_example`func(my_var=13) at debug_example_main.c:23:9
20 int acc = 0;
21
22 for (int i = 0; i < my_var; ++i) {
-> 23 acc = foo(acc + i);
^
24 }
25
26 return acc;
(lldb) thread step-over
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 43
- Hook 2 (frame variable my_var)
(int) my_var = 13
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = step over
frame #0: 0x800001b0 debug_example`func(my_var=13) at debug_example_main.c:24:3
21
22 for (int i = 0; i < my_var; ++i) {
23 acc = foo(acc + i);
-> 24 }
^
25
26 return acc;
27 }
Use of conditional breakpoints
In some cases, you may want to trigger a breakpoint only when some conditions are verified.
Here, we want to break at the last iteration of the loop, where i is equal to my_var-1:
(lldb) breakpoint set -l 23 -c "i == (my_var - 1)"
Breakpoint 4: where = debug_example`func + 80 at debug_example_main.c:23:15, address = 0x80000188
(lldb) breakpoint delete 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) process continue
- Hook 1 (target variable global_variable)
(int32_t) global_variable = 54
- Hook 2 (frame variable my_var)
(int) my_var = 13
Process 21312 resuming
Process 21312 stopped
* thread #1, name = 'DPUthread0', stop reason = breakpoint 4.1
frame #0: 0x80000188 debug_example`func(my_var=13) at debug_example_main.c:23:15
20 int acc = 0;
21
22 for (int i = 0; i < my_var; ++i) {
-> 23 acc = foo(acc + i);
^
24 }
25
26 return acc;
(lldb) frame variable
(int) my_var = 13
(int) acc = 445
(int) i = 12
We can see that we stop at the last iteration of the loop where i is equal to my_var-1.
Finishing off debugging
Let’s continue to let the program finish and then exit dpu-lldb:
(lldb) process continue
Process 21312 resuming
Process 21312 exited with status = 156 (0x0000009c)
(lldb) exit
As explained in the tutorial, the exit status provided by dpu-lldb is the 8 LSB of the return value the thread 0.