Example Development Flow
This page will contain brief examples of how to compile and load your program into QEMU, and then debug it.
Acquiring the Tools
For this example you will need:
aarch64-linux-gnu-gcc
gdb-multiarch
oraarch64-linux-gnu-gdb
- device tree compiler (
dtc
) scp
(on the guest)
aarch64-linux-gnu-gcc
, aarch64-linux-gnu-gdb
, and dtc
are bundled with PetaLinux, Yocto, or Vitis tools.scp
is available on the guest by default if using these tools as well.
Compiling Your Application
For this example, we're going to use an example bare metal program called myapp
, and we're going to build it for the ARM64 application processing unit (APU) on a ZCU102 development board.
myapp.c
can be found here.
This application transmits data through UART0 and has a simple menu with three options:
- Generate data to be printed out to the UART
- Print out data to the UART
- Exit
For compiling it, we'll use AArch64 gcc and include debugging symbols.
aarch64-linux-gnu-gcc -g -Wall myapp.c -o myapp.elf
Loading Your Application
There are a few ways to load your application into the guest image.
For this page we will use the SSH method.
TFTP
Files can be copied between the host and guest by using TFTP.
Remember that the gateway IP between the guest and host is 10.0.2.2
.
root@xilinx-zcu102-2020_2:~# tftp -g -r myapp.elf 10.0.2.2 root@xilinx-zcu102-2020_2:~# chmod 755 myapp.elf root@xilinx-zcu102-2020_2:~# ls -la total 20 drwx------ 2 root root 60 Dec 2 19:29 . drwxr-xr-x 3 root root 60 Aug 28 05:26 .. -rwxr-xr-x 1 root root 18656 Dec 2 19:29 myapp.elf
See also: File Transfer with TFTP.
SSH
After booting into Linux, you can copy your application from the host machine to the guest by using SSH.
First, find the IP of the host machine
$ ifconfig eth0 Link encap:Ethernet HWaddr <MAC addr> inet addr:<host IP> Bcast:<Bcast IP> Mask:255.255.252.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:29112513 errors:0 dropped:0 overruns:0 frame:0 TX packets:112423727 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:16274872729 (16.2 GB) TX bytes:163292974163 (163.2 GB) Memory:90100000-9017ffff lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:31093619 errors:0 dropped:0 overruns:0 frame:0 TX packets:31093619 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:2366150233 (2.3 GB) TX bytes:2366150233 (2.3 GB)
Then on the guest machine, use scp
to copy the file from the host to the guest.
root@xilinx-zcu102-2020_2:~# scp <your host machine username>@<your host machine IP>:/scratch/doc-example/myapp.elf . Host '<host IP>' is not in the trusted hosts file. (ecdsa-sha2-nistp256 fingerprint sha1!! 18:7e:92:d0:33:ed:97:e7:cb:b2:f7:b1:5d:52:5f:a6:34:9a:97:f9) Do you want to continue connecting? (y/n) y <host user>@<host IP>'s password: myapp.elf 100% 18KB 17.7KB/s 00:00 root@xilinx-zcu102-2020_2:~#
See also: File Transfer with SSH.
Loading your Application to the Guest Image Using PetaLinux
To add your application to the guest by using PetaLinux, it first must be part of a PetaLinux project.
Adding an Application to the RootFS of a PetaLinux Project
$ cd <petalinux-project-root> $ petalinux-create -t apps --template install --name myapp --enable
This will create a new application that can be added to your PetaLinux project.
The application can be found in: <petalinux-project-root>/project-spec-meta-user/recipes-apps/<your-application-name>
.
Change directory to the files
directory and remove the existing myapp
application; then copy your application to the current directory.
Building the Application
$ petalinux-config -c rootfs # apps -> ensure your application is checked. $ petalinux-build
The newly built image will appear under the <petalinux-project-root>/images/linux/
directory on the host.
Your application will appear in /usr/bin/<your-application-name>
in the guest after booting your image.
For more information on the above steps, see chapter 8 in the PetaLinux Tools Reference Guide.
Kernel-Intrusive Application Debugging
Now that myapp.elf
is on the guest machine, let's see if it works.
When we debug myapp.elf
, we will use GDB on the host machine and connect it to QEMU's GDB server.
This means that we are debugging the QEMU image (the Linux Kernel), along with our application.
More details on intrusive debugging with GDB can be found here.
root@xilinx-zcu102-2020_2:~# ./myapp.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit g Gen: 71 a7 5e c9 c7 67 82 8f a7 1d 8b 7a 31 44 ad f5 TX->71 TX->a7 TX->5e TX->c9 TX->c7 TX->67 TX->82 TX->8f TX->a7 TX->1d TX->8b TX->7a TX->31 TX->44 TX->ad TX->f5 g Gen: 81 4d 91 4b 57 9d b8 08 a1 3d 19 8a ff 11 59 70 TX->81 TX->4d TX->91 TX->4b TX->57 TX->9d TX->b8 TX->08 TX->a1 TX->3d TX->19 TX->8a TX->ff TX->11 TX->59 TX->70
It mostly works. It looks like when we generate the UART data it's transmitted right away.
Let's debug it.
komlodi@machine:/scratch/doc-example$ gdb-multiarch GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 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 "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://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". (gdb) symbol-file myapp.elf Reading symbols from myapp.elf...done. (gdb) b 103 Breakpoint 1 at 0x400a80: file myapp.c, line 103. (gdb) target remote :9000 Remote debugging using :9000 0xffffff8008112eb4 in ?? () (gdb) c Continuing.
Re-run myapp.elf
on the guest.
root@xilinx-zcu102-2020_2:~# ./myapp.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit
Now our GDB running on the host should hit the breakpoint in the while loop.
Breakpoint 1 at 0x400a40: file myapp.c, line 103. (gdb) c Continuing. [Switching to Thread 1.2] Thread 2 hit Breakpoint 1, prog_loop (uart=0x7f8c055000) at myapp.c:103 103 while (!done) { (gdb) n 104 fgets(buf, sizeof(buf), stdin); (gdb) [Switching to Thread 1.4] 106 switch(buf[0]) { (gdb) 108 printf("Gen:"); (gdb) Thread 4 received signal SIGTRAP, Trace/breakpoint trap.
This is a perfect situation of when we want to ignore a signal. GDB received a SIGTRAP and lost control of the program.
Let's make sure this doesn't happen again.
^C Thread 4 received signal SIGINT, Interrupt. 0xffffff80087d7b20 in ?? () (gdb) thread apply all handle SIGTRAP nostop ignore Thread 4 (Thread 1.4): SIGTRAP is used by the debugger. Are you sure you want to change it? (y or n) y Signal Stop Print Pass to program Description SIGTRAP No Yes No Trace/breakpoint trap Thread 3 (Thread 1.3): SIGTRAP is used by the debugger. Are you sure you want to change it? (y or n) y Signal Stop Print Pass to program Description SIGTRAP No Yes No Trace/breakpoint trap Thread 2 (Thread 1.2): SIGTRAP is used by the debugger. Are you sure you want to change it? (y or n) y Signal Stop Print Pass to program Description SIGTRAP No Yes No Trace/breakpoint trap Thread 1 (Thread 1.1): SIGTRAP is used by the debugger. Are you sure you want to change it? (y or n) y Signal Stop Print Pass to program Description SIGTRAP No Yes No Trace/breakpoint trap (gdb) c Continuing.
Now let's try that again.
[Switching to Thread 1.3] Thread 3 hit Breakpoint 2, prog_loop (uart=0x7f8c055000) at myapp.c:106 106 switch(buf[0]) { (gdb) Continuing. Thread 3 hit Breakpoint 2, prog_loop (uart=0x7f8c055000) at myapp.c:106 106 switch(buf[0]) { (gdb) n 108 printf("Gen:"); (gdb) 109 for (i=0; i<sizeof(buf); ++i) { (gdb) 110 buf[i] = rand() & 0xFF; (gdb) 111 printf(" %.2x", buf[i]); (gdb) 109 for (i=0; i<sizeof(buf); ++i) { (gdb) 110 buf[i] = rand() & 0xFF; (gdb) 111 printf(" %.2x", buf[i]); (gdb) 109 for (i=0; i<sizeof(buf); ++i) { (gdb) b 113 Breakpoint 3 at 0x400adc: file myapp.c, line 113. (gdb) c Continuing. [Switching to Thread 1.2] Thread 2 hit Breakpoint 3, prog_loop (uart=0x7f8c055000) at myapp.c:113 113 puts(""); (gdb) n 115 uart_tx(uart, buf, sizeof(buf)); (gdb)
So after we generate the data, it goes right to the transmit case statement. Let's double check the switch statement.
switch(cmd[0]) { case 'g': printf("Gen:"); for (i=0; i<sizeof(buf); ++i) { buf[i] = rand() & 0xFF; printf(" %.2x", buf[i]); } puts(""); case 't': uart_tx(uart, buf, sizeof(buf)); break; case '\n': done = true; break; default: printf("Unknown command %s", cmd); break; }
We forgot to put a break
at the end of the 'g'
case statement, so our code fell through to the next case.
This is an easy fix.
switch(cmd[0]) { case 'g': printf("Gen:"); for (i=0; i<sizeof(buf); ++i) { buf[i] = rand() & 0xFF; printf(" %.2x", buf[i]); } puts(""); break; // <-- case 't': uart_tx(uart, buf, sizeof(buf)); break; case '\n': done = true; break; default: printf("Unknown command %s", cmd); break; }
Now let's put our new code on the guest machine, reload our GDB symbols, and run it.
komlodi@machine:/scratch/doc-example$ aarch64-linux-gnu-gcc -g -Wall myapp.c -o myapp.elf
^C Thread 2 received signal SIGINT, Interrupt. 0xffffff80080cedf4 in ?? () (gdb) symbol-file myapp.elf Load new symbol table from "myapp.elf"? (y or n) y Reading symbols from myapp.elf...done. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000400a80 in prog_loop at myapp.c:106 breakpoint already hit 5 times 2 breakpoint keep y 0x0000000000400a40 in prog_loop at myapp.c:102 breakpoint already hit 1 time 3 breakpoint keep y 0x0000000000400ae8 in prog_loop at myapp.c:113 breakpoint already hit 1 time (gdb) d 1 2 3 (gdb) c
root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/doc-example/myapp.elf . <host user>@1<host IP's password: myapp.elf 100% 18KB 18.2KB/s 00:00 root@xilinx-zcu102-2020_2:~# ./myapp.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit g Gen: 0f 08 45 3e a8 34 72 37 8d 98 8c ae 87 6a 77 e5 t TX->0f TX->08 TX->45 TX->3e TX->a8 TX->34 TX->72 TX->37 TX->8d TX->98 TX->8c TX->ae TX->87 TX->6a TX->77 TX->e5 g Gen: 17 69 69 b3 1c f2 46 3b 62 af 08 39 ac 4b c4 bb t TX->17 TX->69 TX->69 TX->b3 TX->1c TX->f2 TX->46 TX->3b TX->62 TX->af TX->08 TX->39 TX->ac TX->4b TX->c4 TX->bb
There, that looks better.
Non-Kernel-Intrusive Application Debugging
For this example we will load another application called myapp-segfault.c
, which can be found here.
It can be loaded using any of the methods outlined in Loading Your Application, so we will not repeat that here.
In this case, we need to load the source file(s) onto the QEMU guest as well, otherwise GDB will not be able to display what line we're on.
When we debug something non-intrusively in QEMU, we have GDB on the QEMU machine.
Debugging in this way behaves exactly like it would as if you were debugging a program locally on a Linux machine.
This means you cannot debug your kernel using this method.
More information on non-intrusive application debugging can be found here.
First, let's load GDB onto our machine. We'll use scp
in this example and assume we've already downloaded an ARM64 GDB package.
komlodi@machine:/scratch/development-example$ sudo dpkg-deb -R gdb_7.12-6_arm64.deb . komlodi@machine:/scratch/development-example$ sudo zip -r gdb.zip etc usr
Then on the QEMU guest:
root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/development-example/gdb.zip . root@xilinx-zcu102-2020_2:~# unzip gdb.zip root@xilinx-zcu102-2020_2:~# cp -rv etc usr /
Now let's run myapp-segfault.c
and see if it works.
root@xilinx-zcu102-2020_2:~# ./myapp-segfault.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit g Segmentation fault root@xilinx-zcu102-2020_2:~# ./myapp-segfault.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit t TX->50 TX->72 TX->6f TX->67 TX->72 TX->61 TX->6d TX->20 TX->73 TX->74 TX->61 TX->72 TX->74 TX->21 TX->0a g Segmentation fault root@xilinx-zcu102-2020_2:~#
It looks like we consistently have a segmentation fault whenever we generate new data.
Let's debug it.
root@xilinx-zcu102-2020_2:~# gdb gdb: /lib/libncurses.so.5: no version information available (required by gdb) gdb: /lib/libncurses.so.5: no version information available (required by gdb) gdb: /lib/libncurses.so.5: no version information available (required by gdb) gdb: /lib/libtinfo.so.5: no version information available (required by gdb) GNU gdb (Debian 7.12-6) 7.12.0.20161007-git Copyright (C) 2016 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 "aarch64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://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". (gdb) file myapp-segfault.elf Reading symbols from myapp-segfault.elf...done. (gdb) r Starting program: /home/root/myapp-segfault.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit g Program received signal SIGSEGV, Segmentation fault. 0x0000000000400c68 in prog_loop (uart=0x7fbf6fb000) at myapp-segfault.c:89 89 buf[i] = rand() & 0xFF;
We segfault when accessing buf
, let's look closer.
(gdb) backtrace #0 0x0000000000400c68 in prog_loop (uart=0x7fbf6fb000) at myapp-segfault.c:89 #1 0x0000000000400d88 in main () at myapp-segfault.c:120 (gdb) info locals i = 0 buf = 0x400e68 "Program start!\n" cmd = "g\n\000\000\000\000\000\000\230\r@\000\000\000\000" done = false
buf
isn't NULL
, but the address it points to is very close to execution memory.
Since "Program start!\n"
is a constant, it's most likely going to reside in a read-only section of memory.
Let's verify that.
(gdb) maintenance info sections Exec file: `/home/root/myapp-segfault.elf', file type elf64-littleaarch64. [0] 0x00400200->0x0040021b at 0x00000200: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS [1] 0x0040021c->0x0040023c at 0x0000021c: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS [2] 0x0040023c->0x00400260 at 0x0000023c: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS [3] 0x00400260->0x00400308 at 0x00000260: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS [4] 0x00400308->0x004004d0 at 0x00000308: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS [5] 0x004004d0->0x0040059a at 0x000004d0: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS [6] 0x0040059a->0x004005c0 at 0x0000059a: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS [7] 0x004005c0->0x00400600 at 0x000005c0: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS [8] 0x00400600->0x00400648 at 0x00000600: .rela.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS [9] 0x00400648->0x004007c8 at 0x00000648: .rela.plt ALLOC LOAD READONLY DATA HAS_CONTENTS [10] 0x004007c8->0x004007dc at 0x000007c8: .init ALLOC LOAD READONLY CODE HAS_CONTENTS [11] 0x004007e0->0x00400900 at 0x000007e0: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS [12] 0x00400900->0x00400e14 at 0x00000900: .text ALLOC LOAD READONLY CODE HAS_CONTENTS [13] 0x00400e14->0x00400e24 at 0x00000e14: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS [14] 0x00400e28->0x00400f1f at 0x00000e28: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS [15] 0x00400f20->0x00400f24 at 0x00000f20: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS [16] 0x00411de0->0x00411de8 at 0x00001de0: .init_array ALLOC LOAD DATA HAS_CONTENTS [17] 0x00411de8->0x00411df0 at 0x00001de8: .fini_array ALLOC LOAD DATA HAS_CONTENTS [18] 0x00411df0->0x00411df8 at 0x00001df0: .jcr ALLOC LOAD DATA HAS_CONTENTS [19] 0x00411df8->0x00411fd8 at 0x00001df8: .dynamic ALLOC LOAD DATA HAS_CONTENTS [20] 0x00411fd8->0x00411fe8 at 0x00001fd8: .got ALLOC LOAD DATA HAS_CONTENTS [21] 0x00411fe8->0x00412080 at 0x00001fe8: .got.plt ALLOC LOAD DATA HAS_CONTENTS [22] 0x00412080->0x00412090 at 0x00002080: .data ALLOC LOAD DATA HAS_CONTENTS [23] 0x00412090->0x004120a8 at 0x00002090: .bss ALLOC [24] 0x00000000->0x0000003b at 0x00002090: .comment READONLY HAS_CONTENTS [25] 0x00000000->0x00000030 at 0x000020cb: .debug_aranges READONLY HAS_CONTENTS [26] 0x00000000->0x000004c7 at 0x000020fb: .debug_info READONLY HAS_CONTENTS [27] 0x00000000->0x0000016c at 0x000025c2: .debug_abbrev READONLY HAS_CONTENTS [28] 0x00000000->0x00000153 at 0x0000272e: .debug_line READONLY HAS_CONTENTS [29] 0x00000000->0x00000120 at 0x00002888: .debug_frame READONLY HAS_CONTENTS [30] 0x00000000->0x000002b5 at 0x000029a8: .debug_str READONLY HAS_CONTENTS
On line [14]
, we can see the address that buf
points to, 0x400e68
, resides in that range.
That section is called .rodata
, and is labeled as READONLY
. This is why we receive a segmentation fault when we write to it.
There are a few ways to fix this, the most straightforward change would be to make buf
an array and copy "Program Start!\n"
to it before the while loop.
static void prog_loop(void *uart) { size_t i; // char *buf = "Program start!\n"; char buf[16]; // <-- char cmd[16]; bool done; strcpy(buf, "Program start!\n"); // <-- while (!done) { fgets(cmd, sizeof(cmd), stdin); switch(cmd[0]) { case 'g': printf("Gen:"); for (i=0; i<strlen(buf); ++i) { buf[i] = rand() & 0xFF; printf(" %.2x", buf[i]); } puts(""); break; case 't': uart_tx(uart, buf, strlen(buf)); break; case '\n': done = true; break; default: printf("Unknown command %s", cmd); break; } } }
Now let's compile our changes, put the new binary on the guest, and run it.
komlodi@machine:/scratch/development-example$ aarch64-linux-gnu-gcc myapp-segfault.c -Wall -g -o myapp-segfault.elf
On the guest:
root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/development-example/myapp-segfault.elf . <host user>@<host IP>'s password: myapp-segfault.elf 100% 18KB 17.7KB/s 00:00 root@xilinx-zcu102-2020_2:~# ./myapp-segfault.elf ***************UART TX MENU*************** g: Generate new data to transmit t: Transmit the UART data <RETURN>: Exit t TX->50 TX->72 TX->6f TX->67 TX->72 TX->61 TX->6d TX->20 TX->73 TX->74 TX->61 TX->72 TX->74 TX->21 TX->0a g Gen: c9 ca 06 e6 9b 8b aa c2 d7 69 3d fb 59 27 5f t TX->c9 TX->ca TX->06 TX->e6 TX->9b TX->8b TX->aa TX->c2 TX->d7 TX->69 TX->3d TX->fb TX->59 TX->27 TX->5f root@xilinx-zcu102-2020_2:~#
QEMU Module Debug Printing
This section gives a brief example showing how to make QEMU modules print debug information.
A more detailed page showing how to do QEMU module debug printing is available here.
If building QEMU from source, QEMU provides a way to enable debug printing for modules.
Now let's say you wanted to see the register reads and writes of the UART.
To do this we need to find the UART module that QEMU is using and then enable debug printing.
Finding the Module
To find out what UART module QEMU is using, we will look in the device tree files.
If you have access to the DTS files, look in those. Otherwise, unflatten the DTB and look at the DTS output.
For this example, we'll unflatten the DTB.
komlodi@machine:/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images$ dtc -I dtb -O dts system.dtb -o system.dts komlodi@machine:/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images$ vim system.dts
We're working on a Zynq UltraScale+ MPSoC, so UART0 is at address 0xFF000000.
1803 serial@ff000000 { 1804 u-boot,dm-pre-reloc; 1805 compatible = "cdns,uart-r1p12", "xlnx,xuartps"; 1806 status = "okay"; 1807 interrupt-parent = <0x4>; 1808 interrupts = <0x0 0x15 0x4>; 1809 reg = <0x0 0xff000000 0x0 0x1000>; 1810 clock-names = "uart_clk", "pclk"; 1811 power-domains = <0xc 0x21>; 1812 clocks = <0x3 0x38 0x3 0x1f>; 1813 pinctrl-names = "default"; 1814 pinctrl-0 = <0x1d>; 1815 cts-override; 1816 device_type = "serial"; 1817 port-number = <0x0>; 1818 };
Here's the UART in the device tree. What we care about are the values of the compatible
string.
These values are used by QEMU to determine what module should be used to model the hardware.
QEMU will scan the compatible strings from left to right, and use the first one it is capable of modeling.
It isn't immediately obvious what a cdns,uart-r1p12
is, aside from it being a UART. Let's look for that.
komlodi@machine:/scratch/proj/qemu/build$ find .. -name "*uart*" ../hw/riscv/sifive_uart.c ../hw/char/lm32_juart.c ../hw/char/cmsdk-apb-uart.c ../hw/char/omap_uart.c ../hw/char/grlib_apbuart.c ../hw/char/xilinx_iomod_uart.c ../hw/char/exynos4210_uart.c ../hw/char/milkymist-uart.c ../hw/char/nrf51_uart.c ../hw/char/mcf_uart.c ../hw/char/xilinx_uartlite.c ../hw/char/lm32_uart.c ../hw/char/cadence_uart.c ../hw/char/digic-uart.c
cadence_uart.c looks like the most likely file, let's look at that one.
In most situations you can use grep
on the compatible string and find the module that way.
Enabling Module Debug Printing
Most modules will have a sequence of debug code at the top of the file that will look something like this:
#ifdef CADENCE_UART_ERR_DEBUG #define DB_PRINT(...) do { \ fprintf(stderr, ": %s: ", __func__); \ fprintf(stderr, ## __VA_ARGS__); \ } while (0) #else #define DB_PRINT(...) #endif
or this:
#ifndef XLNX_ZYNQMP_GPIO_ERR_DEBUG #define XLNX_ZYNQMP_GPIO_ERR_DEBUG 1 #endif
In cadence_uart.c
, we're looking at the top code block.
Add debug information by adding a definition for CADENCE_UART_ERR_DEBUG
.
This will print any time a register in cadence_uart.c
is accessed (if the module supports it), and any time the module code has a DB_PRINT
statement.
#define CADENCE_UART_ERR_DEBUG // <-- #ifdef CADENCE_UART_ERR_DEBUG #define DB_PRINT(...) do { \ fprintf(stderr, ": %s: ", __func__); \ fprintf(stderr, ## __VA_ARGS__); \ } while (0) #else #define DB_PRINT(...) #endif
Some peripherals, such as UART and GPIO, are sometimes used by the guest image for debugging purposes, such as outputting a heartbeat or stdio and stderr output.
This means that enabling debug printing can potentially cause a lot of messages to be printed.
This can be disabled when building your image.
After recompiling QEMU, when we re-run myapp.elf
we will see the UART reads and writes.
Gen: 74 0a 00 e7 74 7b 73 2a 6a 52 da 69 a0 b5 ba a2 TX->74 : uart_write: offset:c0 data:00000074 : uart_read: offset:b0 data:00000000 TX->0a : uart_write: offset:c0 data:0000000a : uart_read: offset:b0 data:00000000 TX->00 : uart_write: offset:c0 data:00000000 : uart_read: offset:b0 data:00000000 TX->e7 : uart_write: offset:c0 data:000000e7 : uart_read: offset:b0 data:00000000 TX->74 : uart_write: offset:c0 data:00000074 : uart_read: offset:b0 data:00000000 TX->4b : uart_write: offset:c0 data:0000004b : uart_read: offset:b0 data:00000000 TX->53 : uart_write: offset:c0 data:00000043 : uart_read: offset:b0 data:00000000 TX->2a : uart_write: offset:c0 data:0000002a : uart_read: offset:b0 data:00000000 TX->6a : uart_write: offset:c0 data:0000006a : uart_read: offset:b0 data:00000000 TX->52 : uart_write: offset:c0 data:00000052 : uart_read: offset:b0 data:00000000 TX->da : uart_write: offset:c0 data:000000da : uart_read: offset:b0 data:00000000 TX->69 : uart_write: offset:c0 data:00000069 : uart_read: offset:b0 data:00000000 TX->a0 : uart_write: offset:c0 data:000000a0 : uart_read: offset:b0 data:00000000 TX->b5 : uart_write: offset:c0 data:000000b5 : uart_read: offset:b0 data:00000000 TX->ba : uart_write: offset:c0 data:000000ba : uart_read: offset:b0 data:00000000 TX->a2 : uart_write: offset:c0 data:000000a2 : uart_read: offset:14 data:00000000
Related content
© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy