Anwendungsentwicklung mit QEMU

19. Juni 2019

Ein funktionierendes Linux-Image wurde im vorherigen Beitrag erstellt. Damit wurde der Grundstock gelegt, fehlt noch die eigene Anwendung. Gängige Praxis ist es sich über eine (virtuelle) Netzwerkschnittstelle mit dem Zielsystem zu verbinden und die Applikation aufzuspielen. Dazu bieten sich zwei Möglichkeiten. NFS und SSH. Bei ersterem muss der Host als Server dienen und das Zielsystem muss sich aktiv mit dem Host verbinden und bei zweiterem muss auf dem Zielsystem ein SSH-Server laufen mit dem sich der Host verbinden kann. Da der Umgang mittels SSH wesentlich geläufiger ist, ist es die erste Wahl.

In der Standardeinstellung stellt QEMU einen DHCP-Server bereit, der selbst auf die IP-Adresse 10.0.2.2 hört und Adressen ab 10.0.2.15 verteilt. Weiterhin ist über 10.0.2.3 noch ein virtueller DNS-Server vorhanden und unter 10.0.2.4 eine SAMBA-Freigabe. Der dafür nötige Aufruf vereinfacht das Skript wie folgt.

#!/bin/sh

qemu-system-arm \
        -M versatilepb \
        -kernel QemuArmVersatile/images/zImage \
        -dtb QemuArmVersatile/images/versatile-pb.dtb \
        -drive file=QemuArmVersatile/images/rootfs.ext2,if=sd,format=raw \
        -append "root=/dev/sda console=ttyAMA0,115200" \
        -m 32 \
        -serial stdio \
    	-name Versatile_ARM_EXT2
#!/bin/sh

qemu-system-arm \
        -M vexpress-a9 \
        -kernel QemuArmVexpress/images/zImage \
        -dtb QemuArmVexpress/images/vexpress-v2p-ca9.dtb \
        -drive file=QemuArmVexpress/images/rootfs.ext2,if=sd,format=raw \
        -append "rw console=ttyAMA0 console=tty root=/dev/mmcblk0" \
        -cpu cortex-a9 \
        -m 32 \
        -serial stdio \
    	-name Vexpress_ARM

Um zu überprüfen, ob das Zielsystem auf die Außenwelt zugreifen kann, kommt einem normalerweise als erstes ping in den Kopf. Das funktioniert allerdings nicht! ICMP-Pakete werden nicht über TCP/IP verschickt und man benötigt deshalb root-Rechte. Viel einfacher geht es mit der Installation des Pakets links, einem Webbrowser für die Konsole, so gelangt man mit links google.com ins Internet.

Fehlt noch die Gegenrichtung. Hierbei tritt als generelle Problematik auf, dass ein Dienst verwendet werden soll, der auf Ports arbeiten möchte, die i.d.R. vom Host-System belegt sind bzw. für deren Verwendung Administratorrechte nötig wären. Bei SSH ist das der Port 22. Um im Zielsystem nicht unnötige Konfigurationen vornehmen zu müssen, greift man auf Portweiterleitung innerhalb QEMUs zurück. Auf diesen Weg kann z.B. der Port 22 des Zielsystem über den Port 2222 des Hostsystems angesprochen werden. Damit wird aus ssh root@zielsystem, das so nicht erreichbar ist, ssh -p 2222 root@localhost. Später wird noch ein weiterer Port benötigt über den der gdbserver mit seinem Client gdb kommunizieren kann. Standardmäßig tun sie das auf Port 2459 und da auf dem Hostsystem standardmäßig kein gdbserver im Hintergrund läuft, können hier die gleichen Ports genutzt werden.

Das Skript muss dafür um die Weiterleitung erweitert werden.

#!/bin/sh

qemu-system-arm \
        -M versatilepb \
        -kernel QemuArmVersatile/images/zImage \
        -dtb QemuArmVersatile/images/versatile-pb.dtb \
        -drive file=QemuArmVersatile/images/rootfs.ext2,if=sd,format=raw \
        -append "root=/dev/sda console=ttyAMA0,115200" \
        -m 32 \
        -serial stdio \
        -nic user,hostfwd=tcp::2222-:22 \
        -nic user,hostfwd=tcp::2459-:2459 \
    	-name Versatile_ARM_EXT2
#!/bin/sh

qemu-system-arm \
        -M vexpress-a9 \
        -kernel QemuArmVexpress/images/zImage \
        -dtb QemuArmVexpress/images/vexpress-v2p-ca9.dtb \
        -drive file=QemuArmVexpress/images/rootfs.ext2,if=sd,format=raw \
        -append "rw console=ttyAMA0 console=tty root=/dev/mmcblk0" \
        -cpu cortex-a9 \
        -m 32 \
        -serial stdio \
        -nic user,hostfwd=tcp::2222-:22 \
        -nic user,hostfwd=tcp::2459-:2459 \
    	-name Vexpress_ARM

Momentan kann das gebootete Image mit den Anfragen an Port 22 nicht viel anfangen, da der Dienst für diese im Image noch nicht enthalten ist und das root Passwort nicht gesetzt ist. Die Konfiguration muss daher nochmals angeworfen werden.

$ make O=../QemuArmVersatile menuconfig
$ make O=../QemuArmVexpress menuconfig

Für den SSH-Server wird der deutlich schlankere dropbear verwendet. Der liegt unter Target packages ---> Networking applications ---> dropbear und beim Aktivieren werden automatisch die client programs mitaktiviert, die allerdings nicht benötigt werden. Mit diesen Einstellungen könnte man zwar Verbindungsversuche mit dem SSH-Server unternehmen, allerdings wird man bei der Eingabe des Passworts scheitern. Das muss noch gesetzt werden. Dazu setzt man unter System configuration ---> Root password das Passwort im Klartext oder verschlüsselt mittels mkpasswd.

$ mkpasswd 
Password: 
$1$C5PwcjBt$GLW7U7xx7nNqwunEfpY680

Die Ausgabe von mkpasswd muss hierbei noch angefasst werden, da einzeln vorkommende $-Zeichen als Variablen interpretiert werden. Damit wird $1$C5PwcjBt$GLW7U7xx7nNqwunEfpY680 zu $$1$$C5PwcjBt$$GLW7U7xx7nNqwunEfpY680. Anschließend lässt man das Image neu erstellen.

$ make O=../QemuArmVersatile
$ make O=../QemuArmVexpress

Damit ist nun die erste Grundvoraussetzung gelegt.

gdbserver

Zum Debuggen braucht es den gdbserver für dessen Installation die Konfiguration nochmals angefasst werden.

$ make O=../QemuArmVersatile menuconfig
$ make O=../QemuArmVexpress menuconfig

Im Menüpunkt Toolchain müssen folgende Einstellungen gesetzt werden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  Toolchain ─────────────────────────────────────────────────────────────────────────
 ┌──────────────────────────────────── Toolchain ────────────────────────────────────┐
  ┌───────────────────────────────────────────────────────────────────────────────┐   
          Toolchain type (Buildroot toolchain)  --->                                
          *** Toolchain Buildroot Options ***                                       
      (buildroot) custom toolchain vendor name                                      
          C library (uClibc-ng)  --->                                               
          *** Kernel Header Options ***                                             
          Kernel Headers (Same as kernel being built)  --->                         
          Custom kernel headers series (4.19.x)  --->                               
          *** uClibc Options ***                                                    
      (package/uclibc/uClibc-ng.config) uClibc configuration file to use?           
      ()  Additional uClibc configuration fragment files                            
      [*] Enable WCHAR support                                                      
      [ ] Enable toolchain locale/i18n support                                      
          Thread library implementation (Native POSIX Threading (NPTL))  --->       
      [*] Thread library debugging                                                  
      [ ] Enable stack protection support                                           
      [*] Compile and install uClibc utilities                                      
          *** Binutils Options ***                                                  
          Binutils Version (binutils 2.31.1)  --->                                  
      ()  Additional binutils options                                               
          *** GCC Options ***                                                       
          GCC compiler Version (gcc 7.x)  --->                                      
      ()  Additional gcc options                                                    
      [*] Enable C++ support                                                        
      [ ] Enable Fortran support                                                    
      [ ] Enable compiler link-time-optimization support                            
      [ ] Enable compiler OpenMP support                                            
      [ ] Enable graphite support                                                   
          *** Host GDB Options ***                                                  
      [*] Build cross gdb for the host                                              
      [ ]   TUI support                                                             
      [ ]   Python support                                                          
      [ ]   Simulator support                                                       
            GDB debugger Version (gdb 8.1.x)  --->                                  
          *** Toolchain Generic Options ***                                         
      [*] Enable MMU support                                                        
      ()  Target Optimizations                                                      
      ()  Target linker options                                                     
      [*] Register toolchain within Eclipse Buildroot plug-in                       
  └───────────────────────────────────────────────────────────────────────────────┘   
 ├───────────────────────────────────────────────────────────────────────────────────┤  
              <Select>    < Exit >    < Help >    < Save >    < Load >                
 └───────────────────────────────────────────────────────────────────────────────────┘  

Erst dann ist unter Target packages ---> Debugging, profiling and benchmark gdb mit all seinen Unterpunkten anwählbar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
→ Target packages → Debugging, profiling and benchmark ───────────────────────────────
 ┌─────────────────────── Debugging, profiling and benchmark ────────────────────────┐
 │ ┌────↑(-)───────────────────────────────────────────────────────────────────────┐ │  
 │ │    [ ] dstat (NEW)                                                            │ │  
 │ │    [ ] dt                                                                     │ │  
 │ │    [ ] duma (NEW)                                                             │ │  
 │ │    [ ] fio                                                                    │ │  
 │ │    [*] gdb                                                                    │ │  
 │ │    [*]   gdbserver                                                            │ │  
 │ │    [*]   full debugger                                                        │ │  
 │ │    [*]   TUI support                                                          │ │  
 │ │    [ ] google-breakpad (NEW)                                                  │ │  
 │ │    [ ] iozone                                                                 │ │  
 │ │    [ ] kexec                                                                  │ │  
 │ └────↓(+)───────────────────────────────────────────────────────────────────────┘ │  
 ├───────────────────────────────────────────────────────────────────────────────────┤  
 │              Select>    < Exit >    < Help >    < Save >    < Load >              │  
 └───────────────────────────────────────────────────────────────────────────────────┘  

Es ist gut möglich, dass das Bauen durch die neu gesetzten Einstellungen für die Toolchain fehlschlägt. Am schnellsten hilft das Leeren des build Ordners.

Vorausgesetzt das erneute Bauen des Zielsystems hat funktioniert, bedarf es noch einer Applikation. Als typisches Beispiel sei ein Hallo-Welt-Programm gezeigt.

#include<stdio.h>

void main()
{
	int res=0;
	res+=10;
	printf("Hallo %d Welt!\n",res);
}

Dieses liegt in seinem eigenen Ordner helloWorld, der sich auf der gleichen Ebene befindet wie das QEMU-Startskript. Der Ordner kann auch wo anders liegen, aber die Pfadangabe zum Compiler wird im Folgenden relativ angegeben. Übersetzt wird der Code nun mit wie folgt.

$ cd helloWorld
$ ../QemuArmVersatile/host/bin/arm-buildroot-linux-uclibcgnueabi-gcc -g3 -o helloWorld helloWorld.c 
$ file helloWorld
helloWorld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, with debug_info, not stripped
$ cd helloWorld
$ ../QemuArmVexpress/host/bin/arm-buildroot-linux-uclibcgnueabihf-gcc -g3 -o helloWorld helloWorld.c 
$ file helloWorld
helloWorld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, with debug_info, not stripped

Kurz besprochen seien die Parameter -g, der die Menge der Debuginformationen festlegt (-g0 = keine, -g1 = minimal, -g standard, -g3 maximal), -O zum Setzen des Optimierungslevels (-O0 keine, -Os größenoptimiert, -O2 und -O3 geschwindigkeitsoptimiert) und -o setzt den Dateinamen des Kompilats. Der anschließende Aufruf von file soll nur zeigen, dass das Kompilat auch für das Zielsystem erstellt wurde.

Jetzt muss die Datei noch ins Zielsystem übertragen werden. Das geht mittels scp das zur SSH gehört.

$ scp -P 2222 helloWorld root@localhost:/root

Auf dem Zielsystem kann die Anwendung so

$ ./helloWorld 
Hallo 10 Welt!

gestartet werden und die erwartete Ausgabe bewundert werden.

So weit so gut, aber gedebuggt wurde bis jetzt kein Stück. Dazu muss auf dem Zielsystem der gdbserver mit der zu debuggenden Anwendung und dem Port als Parameter aufgerufen werden.

$ gdbserver :2459 ./helloWorld
Process ./helloWorld created; pid = 873
Listening on port 2459

Der Server lauscht nun auf Port 2459, fehlt noch der Client des Hostsystems, der sich mit ihm verbindet.

$ cd helloWorld
$ ../QemuArmVersatile/host/bin/arm-buildroot-linux-uclibcgnueabi-gdb ./helloWorld
GNU gdb (GDB) 8.1.1
Copyright (C) 2018 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=arm-buildroot-linux-uclibcgnueabi".
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"...
Reading symbols from ./helloWorld...done.
(gdb) target remote localhost:2459
Remote debugging using localhost:2459
$ cd helloWorld
$ ../QemuArmVexpress/host/bin/arm-buildroot-linux-uclibcgnueabihf-gdb ./helloWorld
GNU gdb (GDB) 8.1.1
Copyright (C) 2018 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=arm-buildroot-linux-uclibcgnueabihf".
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"...
Reading symbols from ./helloWorld...done.
(gdb) target remote localhost:2459
Remote debugging using localhost:2459

Prinzipiell kann es jetzt losgehen! Die Benutzung der Konsole zum Debuggen ist nicht jedermanns Sache, deshalb wird ein weiterer Beitrag folgen, der das Entwickeln mit einer IDE zeigt, was das Debuggen deutlich vereinfacht.

dotfiles

6. April 2023

Screenshots nach PDF

9. Dezember 2021

PDFs + OCR

7. Oktober 2020