Laboratorul 04. Tools

Până acum ați compilat programe pe același sistem pe care le-ați și rulat. Multe programe însă necesită un proces complex de compilare, care intern execută mulți pași și, în funcție de configurarea aleasă, poate dura un timp îndelungat. Pentru a reduce acest timp se dorește bineînțeles folosirea unei mașini cât mai puternice. În cazul sistemelor embedded este puțin probabil ca procesorul folosit să fie unul performant (în comparație cu un sistem desktop) sau ca memoria RAM disponibilă să fie mare. De altfel, nu de puține ori, avem de-a face cu un sistem care nici măcar nu are destul spațiu de stocare pentru sursele și fișierele binare generate în timpul compilării.

Pentru a elimina inconvenientele compilării pe sistemul embedded (target-ul) compilarea se face de obicei pe un sistem desktop (host-ul). Bineînțeles, acum pot apărea probleme dacă target-ul și host-ul folosesc procesoare cu arhitecturi diferite (executabilul generat de host nu va fi înțeles de procesorul target-ului). Aceste probleme apar deoarece compilarea va folosi în mod implicit compilatorul host-ului: host-compiler-ul (ex: gcc).

Rezolvarea constă în instalarea pe host a unui compilator care poate genera executabile înțelese de target. Acest compilator poartă denumirea de cross-compiler, el rulând pe arhitectura host-ului, dar generând cod pentru arhitectura target-ului. Procesul prin care un program este compilat pe un alt sistem diferit de sistemul target se numește cross-compiling.

Această tehnică este folosită pentru a separa mediul de dezvoltare de mediul în care programul trebuie să ruleze. Acest lucru ne este util în cazul:

  • sistemelor embedded, sisteme limitate din punct de vedere hardware, unde resursele nu sunt suficiente pentru întreg mediul de dezvoltare (ex: AVR, iOS, Android etc.);
  • compilării pentru arhitecturi diferite, un exemplu fiind distribuțiile Linux, unde se poate folosi o singură mașină pentru a compila pentru diferite arhitecturi (ex: x86, x86-64, ARM etc.) kernelul și restul distribuției;
  • compilării programului într-o fermă de servere, unde pentru performanță maximă, se va putea folosi orice mașină disponibilă, indiferent de arhitectura procesorului host sau a versiunii sistemului de operare.

Diferențierea între host-compiler și cross-compiler se face prin prefixarea acestuia din urmă cu un string, denumit prefix, care, prin convenție, conține o serie de informații despre arhitectura target (ex: aarch64-linux-gnu-). De asemenea, și restul utilitarelor folosite pentru compilare (ex: as, ld, objcopy etc.) vor avea același prefix. Tot ce trebuie să facem este să instruim sistemul de build să folosească cross-complier-ul pentru compilare.

Prefixul unui cross compiler se termină întotdeaduna cu -. El va fi concatenat la numele utilitarelor (ex: gcc) pentru a obține numele complet (ex: aarch64-linux-gnu-gcc)

Prin convenție, majoritatea sistemelor de build folosite în lumea Linux acceptă variabila de mediu CROSS_COMPILE pentru specificarea prefixului care trebuie folosit atunci când se dorește o cross-compilare.

Toolchain

Toolchain-ul reprezintă colecția de programe de dezvoltare software care sunt folosite pentru a compila și a obține un program executabil.

Programele care în majoritatea cazurilor sunt incluse în toolchain sunt:

  • gcc - compilatorul de C;
  • g++ - compilatorul de C++;
  • as - assamblorul;
  • ld - linker-ul;
  • objcopy - copiază dintr-un fișier obiect în alt fișier obiect;
  • objdump - afișează informații despre fișierul obiect;
  • gdb - debugging.

make

Un alt program important pentru dezvoltarea unui sistem embedded, și nu numai, îl reprezintă make. Acest utilitar ne permite automatizarea și eficientizarea procesului de compilare prin intermediul fișierelor Makefile. Pentru o reamintire a modului de scriere a unui Makefile revedeți urmatoarea resursa - makefiletuturial.

Pentru ușurarea dezvoltării pe multiple sisteme embedded, fiecare având toolchain-ul lui propriu, vom dori să scriem Makefile-uri generice, care pot fi refolosite atunci când prefixul cross-compiler-ului se schimbă. Pentru aceasta va trebui să parametrizăm numele utilitarelor apelate în Makefile. Putem folosi în acest caz variabile de mediu în cadrul Makefile-ului. Acestea pot fi configurate apoi din exterior în funcție de sistemul target pentru care compilăm la un moment dat, fără a mai fi necesară editarea Makefile-urilor.

Cel mai simplu mod de a face acest lucru este să urmăm convenția deja stabilită pentru variabila de mediu care conține prefixul cross-compiler-ului: CROSS_COMPILE. Putem folosi această variabilă de mediu în cadrul Makefile-ului nostru utilizând sintaxa de expandare unei variabile, $(<variabila>), și prefixând numele utilitarului cu variabila pentru prefix.

Makefile
hello: hello.c
	$(CROSS_COMPILE)gcc hello.c -o hello

Orice variabilă exportată în shell-ul curent va fi disponibilă și în fișierul Makefile. Putem de asemenea pasa variabile utilitarului make și sub formă de parametri, astfel:

$ make CROSS_COMPILE=aarch64-linux-gnu- hello

bash

O mare parte din dezvoltarea unui sistem embedded se face prin intermediul terminalului. Shell-ul care rulează în terminal permite personalizarea unor aspecte utile pentru dezvoltare precum variabilele de mediu încărcate la fiecare rulare. Aceste personalizări se fac însă în fișiere de configurare specifice fiecărui shell.

Pentru bash aceste fișiere reprezintă niste script-uri care sunt rulate automat și se găsesc în /etc (afectează toți utilizatorii) și în $HOME (afectează un singur utilizator). Prin intermediul fișierelor din $HOME fiecare utilizator își poate personaliza shell-urile pentru propriile nevoi. Aceste fișiere sunt:

  • .bash_profile - este executat când se pornește un shell de login (ex: primul shell după logare);
  • .bashrc - este executat cand se pornește orice shell interactiv (ex: orice terminal deschis);
  • .bash_logout - este executat când shell-ul de login se închide.

Un alt fișier util folosit de bash este .bash_history, care memorează un istoric al comenzilor interactive rulate. Istoricul comenzilor este salvat în acest fișier la închiderea unui shell. Pentru o reamintire a unor comenzi utile în linia de comandă puteți revizita laboratorul de USO - Automatizare în linia de comandă.

În dezvoltarea unui sistem embedded este deseori utilă adăugarea în variabila $PATH a căilor către diferitele tool-uri folosite, pentru ca acestea să poată fi accesate direct prin numele executabilului. Modificarea variabilei $PATH pentru fiecare shell pornit se poate face ușor prin intermediul fișierelor de personalizare a shell-ului.

Setup

Atenție! Pentru rezolvarea laboratorului vom folosi aceeaşi mașină virtuală Ubuntu 22.04 pe care am folosit-o în Laboratorul 3 rulată în VmWare.

QEMU

Vom configura accesul target-ului la Internet.

  • Întâi asigurati-va ca aveti access la internet in cadrul masinii virtuale.
  • Emulați interfața de rețea folosind un USB network adaptor virtual. Pentru a avea access la serviciul de SSH din QEMU o sa avem nevoie de port-forwarding pentru portul 22 (SSH default). Pentru a realiza acest lucru, adaugam parametrul hostfwd=tcp::5555-:22 in optiunea de -netdev din comanda de qemu-system-aarch64:
     ...
    -device usb-net,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22 \
  • Rulăm distribuția Debian 12 (Bookworm) folosind QEMU în modul system emulation.
    • Kernel-ul, Device Tree Blob-ul (.dtb) si imaginea de InitRD trebuie extrase din imaginea de Debian conform instructiunilor din Laboratorul 3, sectiunea System-mode emulation.
sudo qemu-system-aarch64 \
    -machine raspi3b \
    -kernel vmlinuz-5.18.0-3-arm64 \
    -initrd initrd.img-5.18.0-3-arm64 \
    -dtb bcm2837-rpi-3-b.dtb \
    -sd 20220808_raspi_3_bookworm.img \
    -append "console=ttyS1 root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4" \
    -device usb-net,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22 \
    -nographic \
    -serial null \
    -serial mon:stdio
  • Logarea pe target se face doar cu user-ul root.
  • Verificam interfetele disponibile
root@rpi3-20220807:~# ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enx405400123457: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 40:54:00:12:34:57 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::4254:ff:fe12:3457/64 scope link 
       valid_lft forever preferred_lft forever

Daca nu aveti nicio interfata de tipul enx…, reporniti QEMU.

  • Putem observa ca nu avem nicio adresa IP asociata interfetei. Pentru a realiza acest lucru, vom cere una prin intermediul protocolului DHCP.
dhclient $(ls -1 /sys/class/net | grep enx)
  • Verificati ca avem o adresa IP asociata interfetei enx.
root@rpi3-20220807:~# ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enx405400123457: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 40:54:00:12:34:57 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enx405400123457
       valid_lft 84722sec preferred_lft 84722sec
    inet6 fec0::4254:ff:fe12:3457/64 scope site dynamic mngtmpaddr 
       valid_lft 86288sec preferred_lft 14288sec
    inet6 fe80::4254:ff:fe12:3457/64 scope link 
       valid_lft forever preferred_lft forever
  • Verificati ca avem acces la internet.
root@rpi3-20220807:~# ping 8.8.8.8
  • Creati o parola pentru utilizatorul root.
root@rpi3-20220807:~# passwd
  • Configurati serivicul de SSH sa accepte logarea ca root
root@rpi3-20220807:~# vim /etc/ssh/sshd_config

PermitRootLogin yes
  • Reporniti serviciul de SSH.
root@rpi3-20220807:~# systemctl restart ssh
  • Connectati-va la QEMU prin intermediul port-ului forward-uit catre masina noastra.
student@virtual-machine:~$ ssh -p 5555 root@localhost

Raspberry Pi

  • Conectiati-va la reteaua Wi-Fi LabSI-PR703 cu parola Il0v3-rpi.
  • Conectati-va prin intermediul serivicului de SSH la Raspberry Pi-ul prezent pe masa voastra.
    • Folositi user-ul root si parola Il0v3-rpi
ssh root@10.3.14.X

Exerciții

1. Scrieți și compilați pe host un program hello world. Rulați acest executabil atât pe host, cât și pe target-ul RaspberyPi. Ce observaţi când rulaţi pe target?

  • Folosiți scp <sursă> <destinaţie> pentru a copia executabilul pe target, unde <sursă> este calea către fişierul local (de pe host), iar <destinaţie> este calea remote către directorul de pe target unde se va salva (<username>@<hostname>:<cale pe target>)
  • QEMU:
    scp -P 5555 hello root@localhost:.
  • Raspberry Pi:
    scp hello root@10.3.14.X:.

2. Compilați pe host program-ul hello world pentru a putea rula pe target-ul RaspberryPi. Salvați comanda folosită pentru compilare. Rulaţi programul compilat pe target.

  • Instalați toolchain-ul pentru RaspberryPi (local, pe host), dacă acesta nu există deja, pentru a putea cross-compila programe pentru RaspberryPi:
     sudo apt-get install gcc-aarch64-linux-gnu 
  • Prefixul cross-compiler-ului este aarch64-linux-gnu-. Compilatorul este aarch64-linux-gnu-gcc.

3. Creați un Makefile generic pentru programul hello world care poate compila pentru orice sistem target în funcție de variabilele primite (convenția CROSS_COMPILE). Compilați programul pentru host și pentru target-ul RaspberryPi, apoi salvați executabilele generate.

  • Dacă o variabilă nu este setată, construcția $(<variabilă>) într-un Makefile va fi echivalentă cu șirul vid.

4. Ce puteți spune despre conținutul celor 2 fișiere executabile create la exercițiul anterior?

  • Pentru informaţii legate de tipul fişierelor se poate folosi comanda file;
  • Conținutul unui fișier executabil poate fi inspectat cu utilitarul objdump (ptr target folosiţi utilitarul din toolchain: aarch64-linux-gnu-objdump)

Resurse

si/lab/2022/laboratoare/04.txt · Last modified: 2023/09/26 14:15 (external edit)
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0