Part 3 - Debugging Hello World

Today we will dive into debugging our very simple, “Hello world!”, program.

Let’s review our code.

  1. #include <stdio.h>
  2. #include "pico/stdlib.h"
  3. int main()
  4. {
  5. stdio_init_all();
  6. while(1)
  7. {
  8. printf("Hello world!\n");
  9. sleep_ms(1000);
  10. }
  11. return 0;
  12. }

Please make sure you build Radare2 from source. Before each lesson PLEASE complete the following.

  1. git pull
  2. radare2 sys/install.sh

You can check that the version is up to date.

  1. radare2 -v

In my case, as it will be different for you.

  1. radare2 5.2.0-git 25988 @ darwin-x86-64 git.5.1.1
  2. commit: 510ddab0e523bed173b3954e5f61abf395812f7d build: 2021-03-21__05:40:51

Now back to our project repo. Let’s fire up our debugger.

  1. radare2 -w arm -b 16 0x02_hello_world.elf

Let’s auto analyze.

  1. aaaa

Let’s seek to main.

  1. s main

Let’s go into visual mode by typing V and then p twice to get to a good debugger view.

Part 3 - Debugging Hello World - 图1

Let’s break this very simple program down.

  1. push {r4, lr}

We are simply setting up our function arguments where we pushing the value of r4 and lr (link register) to the stack.

We then bl (branch long) to the sym.stdio_init_all function which init’s standard input and output.

  1. bl sym.stdio_init_all

We then load the value at the location 0x00000338 into the r4 register. This is where the, “Hello world!” string lives.

  1. ldr r4, [0x00000338]

To prove this we can do the following by pressing : inside of the current Visual mode and then typing the following.

  1. :> psz @ [0x00000338]
  2. Hello world!
  3. :> psz @ 0x00004cf8
  4. Hello world!

As you can clearly see the value inside of 0x00000338 _is the value at _0x0004cf8.

We then move and set the flags (that is the s in movs) the contents of r4 into r0.

  1. movs r0, r4

We then branch long to the puts wrapper. The debugger converted our _printf _function in our code to this wrapper function.

  1. bl sym.__wrap_puts

We then movs _250 decimal, 0xfa hex, which is 1/4 our 1000 millisecond sleep into _r0.

  1. movs r0, 0xfa

We then logically shift left, 2, and set the flags. This of course multiplies our 250 value by 2 and then again by 2 which takes 250 decimal to 1000 decimal which is our millisecond delay and places that 1000 decimal value into r0.

  1. lsls r0, r0, 2

If you are not familiar with ARM 32 Assembly instructions, please reference this great table provided by Keil.

https://developer.arm.com/documentation/ddi0210/c/Introduction/Instruction-set-summary/ARM-instruction-summary?lang=en

We then branch long to our sleep_ms function.

  1. bl sym.sleep_ms

We then branch unconditional back to 0x328 which is our while loop.

  1. b 0x328

You can also see the graph view by pressing V again in the current window.

Part 3 - Debugging Hello World - 图2

This is a great way to trace through more elaborate code. I wanted to show you all this as you can use this going forward as you do larger analysis.

In our next lesson we will hack our simple program and convert it back to a .uf2 and re-flash to the Pico.