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 in debug_example_misc.h)

  • track the value of my_var (defined at multiple places in debug_example_main.c)

  • check the parameters of misc_func (defined in debug_example_misc.c) when the function is called

  • make some precise step by step debugging in the func function (defined in debug_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.