How to Single Step Arm Assembly in Gdb on Qemu

Breakpoints not working for gdb while debugging remote arm target on qemu

GDB is placing breakpoints based on where the ELF file says the code is. You can see that in your transcript it thinks the _start function in boot.S is at address 0x0. However, when you tell QEMU to load your binary file, you are not doing that in a way that matches what the ELF file says. So the actual code being executed is at an entirely different address, and the breakpoints are not in addresses that match the executing code, so they don't hit. Since you're not compiling the code to be position-independent, when it runs from this address it is working mostly by luck (because even non-position-independent aarch64 code often doesn't have position-dependent instructions in it).

The reason the addresses don't match up is because your ELF file is saying code starts at address 0x0, but you're passing QEMU a binary file to the -kernel option, which means "I am a Linux kernel, boot me the way the Linux kernel boot protocol says to do that" (see https://www.kernel.org/doc/Documentation/arm64/booting.txt). This means a number of things, including that (for the current QEMU implementation -- this isn't strictly mandated by the booting protocol) we load the image to the address 0x80000, and run a bit of stub code generated by QEMU which sets up some registers and jumps to that location. That is why when you set your linker script to link the image to that address it happens to start working.

The solution to this is to make a choice about how you want to boot your guest code:

  • You can make it honour the various requirements of the Linux kernel boot protocol, and pass it as a binary file to -kernel
  • You can write it to be a pure bare-metal image that includes a vector table at address 0x0, and load it with the QEMU "generic loader", which will take an ELF file and load all its segments as the ELF headers specify. (It is also possible to pass an ELF file to -kernel, but the generic loader makes more sense in this situation.)

QEMU does not support "load this ELF file and start it in the way that the Raspberry Pi firmware supports running an ELF file that it loads from an SD card". So you may need to make some adjustments to bare-metal code tutorials that were designed only for running on real hardware.

(For more info on the various QEMU options for loading guest code, see this answer.)

GDB stepping through instructions on a particular core in baremetal development on QEMU

You probably have an issue with the versions of gdb or qemu you are using, since I was not able to reproduce your problem with a version 10.1 of aarch64-elf-gdb and a version 6.2.0 of qemu-system-aarch64 compiled from scratch on an Ubuntu 20.04.3 LTS system:

wfe.s:

        .global _start
_start:
1: wfe
b 1b

Building wfe.elf:

/opt/arm/10/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-gcc -g -ffreestanding -nostdlib -nostartfiles -Wl,-Ttext=0x80000 -o wfe.elf wfe.s

Looking at generated code:

/opt/arm/10/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-objdump -d wfe.elf

wfe.elf: file format elf64-littleaarch64


Disassembly of section .text:

0000000000080000 <_stack>:
80000: d503205f wfe
80004: 17ffffff b 80000 <_stack>

Starting qemu in a shell session:

/opt/qemu-6.2.0/bin/qemu-system-aarch64 -M raspi3b -kernel wfe.elf -display none -S -s

Starting gdb in another:

/opt/gdb/gdb-10.1-aarch64-elf-x86_64-linux-gnu/bin/aarch64-elf-gdb wfe.elf -ex 'target remote localhost:1234' -ex 'break *0x80000' -ex 'continue'

gdb session:

GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=aarch64-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
Remote debugging using localhost:1234
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x0000000000080000 in ?? ()
Breakpoint 1 at 0x80000
Continuing.
[Switching to Thread 1.4]

Thread 4 hit Breakpoint 1, 0x0000000000080000 in ?? ()
(gdb) break *0x80000 thread 2
Note: breakpoint 1 (all threads) also set at pc 0x80000.
Breakpoint 2 at 0x80000
(gdb) info threads
Id Target Id Frame
1 Thread 1.1 (CPU#0 [running]) 0x0000000000080000 in ?? ()
2 Thread 1.2 (CPU#1 [running]) 0x0000000000080000 in ?? ()
3 Thread 1.3 (CPU#2 [running]) 0x0000000000080000 in ?? ()
* 4 Thread 1.4 (CPU#3 [running]) 0x0000000000080000 in ?? ()
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, 0x0000000000080000 in ?? ()
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000080000
breakpoint already hit 2 times
2 breakpoint keep y 0x0000000000080000 thread 2
stop only in thread 2
breakpoint already hit 1 time
(gdb) del 1
(gdb) info b
Num Type Disp Enb Address What
2 breakpoint keep y 0x0000000000080000 thread 2
stop only in thread 2
breakpoint already hit 1 time
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb)

The anwers to your two questions are therefore:

  1. What can I do get a breakpoint on core 2?

Exactly what you are doing.


  1. What am I doing wrong here?

Nothing, but may be using old/buggy versions of gdb and/or qemu - my guess would be that gdb is the culprit is your case, but I may be wrong.

You can easily verify by testing again using the version of gdb provided in the gcc toolchain available from Arm, AArch64 ELF bare-metal target (aarch64-none-elf) - I tried, and it worked fine as well:

/opt/arm/10/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-gdb wfe.elf -ex 'target remote localhost:1234' -ex 'break *0x80000' -ex 'continue'
GNU gdb (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 10.2.90.20210621-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=aarch64-none-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.linaro.org/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from wfe.elf...
Remote debugging using localhost:1234
_start () at wfe.s:3
3 1: wfe
Breakpoint 1 at 0x80000: file wfe.s, line 3.
Continuing.

Thread 1 hit Breakpoint 1, _start () at wfe.s:3
3 1: wfe
(gdb) break *0x80000 thread 2
Note: breakpoint 1 (all threads) also set at pc 0x80000.
Breakpoint 2 at 0x80000: file wfe.s, line 3.
(gdb) info threads
Id Target Id Frame
* 1 Thread 1.1 (CPU#0 [running]) _start () at wfe.s:3
2 Thread 1.2 (CPU#1 [running]) _start () at wfe.s:3
3 Thread 1.3 (CPU#2 [running]) _start () at wfe.s:3
4 Thread 1.4 (CPU#3 [running]) _start () at wfe.s:3
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, _start () at wfe.s:3
3 1: wfe
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000080000 wfe.s:3
breakpoint already hit 2 times
2 breakpoint keep y 0x0000000000080000 wfe.s:3 thread 2
stop only in thread 2
breakpoint already hit 1 time
(gdb) del 1
(gdb) info b
Num Type Disp Enb Address What
2 breakpoint keep y 0x0000000000080000 wfe.s:3 thread 2
stop only in thread 2
breakpoint already hit 1 time
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3 1: wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3 1: wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3 1: wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3 1: wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3 1: wfe
(gdb)

Please note that explaining how to build the latest versions of gdb and qemu is out of the scope of the current answer.

Issue debugging a 32-bits assembly Hello world with GDB on a Raspberry Pi running a 64 bit version of Linux

This answer is more an answer to the question: "How can I learn 32 bit assembly language on my raspberry Pi" than a direct answer to yours:

If your goal is to learn Aarch32 T32 or A32 assembly language on your raspberry Pi, I would strongly suggest to do so on a 32 bit distribution - I am not sure at this stage that you can debug a user mode Aarch32 program on an Aarch64 Linux system using an Aarch64 multiarch GDB or an Aarch32 version of GDB, my own attempts having been unsuccessful, and having not found to this day examples of how exactly to do this.
Another pro of this approach is that you will be able to concentrate on learning 32 bit Arm, and not asking yourself if your programs are not working because of a bug, or because off a potential problem/bug in the tools you are running on your Aarch64 system - my two cents.

If you have a spare 8GiB micro-SD card, you can install a 32 bit version of Ubuntu Server 22.04 from here.

One installed, here is what I am getting on my system:

cat /sys/firmware/devicetree/base/model
Raspberry Pi 3 Model B Rev 1.2
uname -a
Linux ubuntu 5.15.0-1005-raspi #5-Ubuntu SMP PREEMPT Mon Apr 4 12:25:49 UTC 2022 armv7l armv7l armv7l GNU/Linux

Install gcc and gdb:

sudo-apt -get install gcc gdb

Create hello-world.s, adapted from this example:

        .arch armv7a
.file "hello-world.s"
.text
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
mov r0, #1 @ 1 = stdout
ldr r1, =hello_world @ str pointer
mov r2, #13 @ str len
mov r7, #4 @ linux write syscall
svc 0 @ software interrupt call write
exit:
mov r0, #0 @ return code
mov r7, #1 @ linux exit syscall
svc 0 @ software interrupt call exit
.data
hello_world:
.ascii "Hello World!\n"
.end

as -g -o hello-world.o hello-world.s
gcc -g -o hello-world hello-world.o

./hello-world
Hello World!

GDB debug session:

gdb ./hello-world
GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./hello-world...
(gdb) b main
Breakpoint 1 at 0x4e0: file hello-world.s, line 10.
(gdb) run
Starting program: /home/ubuntu/hello-world
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/arm-linux-gnueabihf/libthread_db.so.1".

Breakpoint 1, main () at hello-world.s:10
10 mov r0, #1 @ 1 = stdout
(gdb) step
11 ldr r1, =hello_world @ str pointer
(gdb)
12 mov r2, #13 @ str len
(gdb)
13 mov r7, #4 @ linux write syscall
(gdb)
14 svc 0 @ software interrupt call write
(gdb)
Hello World!
exit () at hello-world.s:16
16 mov r0, #0 @ return code
(gdb)
17 mov r7, #1 @ linux exit syscall
(gdb)
18 svc 0 @ software interrupt call exit
(gdb)
[Inferior 1 (process 3043) exited normally]
(gdb) quit

Using gdb to single-step assembly code outside specified executable causes error cannot find bounds of current function

You can use stepi or nexti (which can be abbreviated to si or ni) to step through your machine code.



Related Topics



Leave a reply



Submit