tag:linuxfr.org,2005:/users/piedLinuxFr.org : les contenus de Pinaraf2023-12-16T11:03:14+01:00/favicon.pngtag:linuxfr.org,2005:Diary/409482023-11-21T09:10:10+01:002023-11-21T09:10:10+01:00Petitboot sur ARM, le bon, le bad et le uglyLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-1-le-bios-mais-k%C3%A9sako">1) Le BIOS, mais késako</a></li>
<li><a href="#toc-2-des-alternatives-au-biosuefi">2) Des alternatives au BIOS/UEFI</a></li>
<li><a href="#toc-3-labsence-de-firmware-%C3%A7a-donne-quoi">3) L'absence de firmware, ça donne quoi</a></li>
<li><a href="#toc-4-et-une-exception-dans-le-chaos">4) Et une exception dans le chaos ?</a></li>
<li><a href="#toc-5-le-bad">5) Le bad</a></li>
<li><a href="#toc-6-le-ugly">6) Le ugly</a></li>
<li><a href="#toc-7-the-conclusion">7) The conclusion</a></li>
</ul>
<p>Oui bon ça va hein, la traduction française elle passe pas pour le titre.</p>
<p>Bonjour !</p>
<p>Aujourd'hui, comme évoqué dans des discussions précédentes (et c'est mon deuxième journal avec plusieurs mois de retard), je voudrais vous parler de petitboot, mon bootloader préféré, en me focalisant sur la seule implémentation à laquelle j'ai accès aujourd'hui, avec ses problèmes mineurs et ses problèmes… pas majeurs, mais disons franchement ridicules.<br>
On va donc parler de firmware dans le style du BIOS ou de l'UEFI. Mais avant de pouvoir faire ça, un petit rappel des faits est requis.</p>
<h2 id="toc-1-le-bios-mais-késako">1) Le BIOS, mais késako</h2>
<p>Le BIOS est une invention un peu spéciale de l'IBM PC. Lors de la conception du PC, IBM voulait utiliser du matériel disponible «sur l'étagère» : les premiers PCs étaient un assemblage de composants existants. Problème pour IBM : comment empêcher la création de clones du PC ?<br>
La solution : le BIOS. Un composant logiciel intégré sur la carte mère, dont dépendra l'ensemble de la pile logicielle du PC (MS-DOS et ses applications, pour simplifier), et qu'IBM ne diffusera pas aux concurrents. Simple et efficace, non ? Et puis tant qu'à faire, autant y embarquer un interpréteur BASIC pour que la machine serve à quelque chose même sans disquette de système…<br>
Le BIOS avait donc à la fois le rôle de bootloader (qui a évolué avec le temps, l'apparition des disques durs, du réseau, des CD-ROM…) et un rôle d'abstraction par dessus le matériel. Par exemple : pas besoin de pilotes pour utiliser un clavier USB sous DOS avec une application de 1980… Comment est-ce possible ? Grâce au BIOS, qui expose une API basique pour interagir avec le clavier, et fait abstraction du contrôleur clavier. De même pour l'accès à un disque dur PATA vs SATA…<br>
Bien sûr, IBM n'avait pas anticipé la créativité et les compétences des ingénieurs de Compaq et Phoenix et la création de BIOS compatibles ne dépendant pas d'IBM. (Et leur tentative de rattraper la chose avec le PS/2 et son MCA n'a heureusement pas réussi)</p>
<p>Les systèmes d'exploitation «modernes» n'ont pas une telle dépendance au BIOS. Ils ont des pilotes pour gérer eux-même l'USB, le stockage… avec des bien meilleures performances. Mais pour cela, ils doivent être capables de découvrir le matériel. Et c'est là qu'intervient un rôle ajouté au BIOS, ou plutôt en parallèle du BIOS : la description du matériel, avec en particulier la norme ACPI. (Je sais c'est simplifié, il y a eu aussi l'ISAPnP, le concept de PnP intégré à PCI et à l'USB… mais c'est pas le but de ce journal).</p>
<p>Donc en résumé : deux rôles pour le firmware, bootloader et description du matériel. Notez bien cela, nous y reviendrons.</p>
<h2 id="toc-2-des-alternatives-au-biosuefi">2) Des alternatives au BIOS/UEFI</h2>
<p>Alors il faut bien reconnaître, ça ne court pas les cartes mères.<br>
Il fut un temps où l'alternative la plus répandue était OpenFirmware, utilisé sur les machines SPARC, POWER, certains ARM… Pendant quelques années c'était même un standard IEEE, mais il est «rétracté» depuis. Lors de la mise en place de l'UEFI sur les PCs, des débats avaient eu lieu vantant cette solution, mais les chevaliers du NIH ne l'ont pas entendu de cette oreille.</p>
<p>Avec la fin d'OpenFirmware, une autre solution était nécessaire sur les machines OpenPower, et cette solution a été appelée OPAL, OpenPower Abstraction Layer. Avec OPAL, le boot est assez simple (après x étapes trop bas niveau pour ce journal) : il y a deux parties, skiboot avec un ensemble de services de runtime utilisables par l'OS, et skiroot le bootloader, composé d'un noyau Linux et d'un initramfs contenant Petitboot. Petitboot suit une idée simple elle aussi, similaire à l'idée de LinuxBIOS : puisque le firmware doit gérer le matériel, des systèmes de fichiers, du réseau… autant prendre un noyau complet, un système allégé, et utiliser ce dernier pour aller chercher un noyau, le mettre en mémoire comme il faut et faire un goto, avec l'appel système kexec().</p>
<p>Évidemment, il y a aussi les alternatives libres pour PC, mais avec les processeurs et chipsets modernes le travail est devenu assez difficile, surtout si l'on veut quelque chose d'entièrement libre. On peut donc citer les coreboot (ex-LinuxBIOS, qui reprenait déjà en 2001 l'idée de mettre un noyau Linux dans le firmware), Libreboot (version sans blob de coreboot), LinuxBoot (dérivé de NERF, qui élimine toute une partie de l'EFI pour la remplacer par un noyau Linux)…</p>
<h2 id="toc-3-labsence-de-firmware-ça-donne-quoi">3) L'absence de firmware, ça donne quoi</h2>
<p>Nous avons parlé du firmware, maintenant parlons de l'anti-firmware, avec le désastre des cartes ARM.<br>
On va prendre une distribution spécialisée ARM pour illustrer le problème, Armbian. Direction armbian.com, section téléchargement. Et il faut choisir la carte que l'on a, et on télécharge une image spécialisée pour cette carte.<br>
J'insiste. Une image spécialisée. Parce qu'il faut avoir dans cette image le bootloader compatible avec la carte, les fichiers DTB (Device Tree Blob) décrivant la carte… C'est une calamité. Et ça, c'est pour les fabricants qui jouent le jeu, parce que si vous tombez sur une machine avec un SoC mediatek, vous avez un noyau d'il y a X années patché dégueulassement et démerdez-vous…<br>
Microsoft, en proposant Windows pour ARM64, refuse cette situation (normal, ils ne veulent pas qu'on touche à leur noyau) et exige la présence d'un UEFI pour Windows, permettant donc la mise en place d'une image unique pour l'ensemble des cartes ARM compatibles.<br>
J'ai suffisamment soupé d'UEFI et de ses bugs pour ne pas en vouloir plus sur mes machines, mais il faut bien reconnaître que l'absence de firmware n'est pas une solution acceptable (surtout quand on pense aux autres OS qui auraient un travail colossal à faire pour supporter l'armée de cartes ARM sur le marché). D'autant plus quand on rencontre des bugs avec le bootloader et qu'on se retrouve sans solution pour comprendre ce qu'il se passe… (non, le port série n'est pas une solution, ça fait disparaitre le bug dans mon cas, tristesse)</p>
<p>Pour les plus curieux, je vous invite à regarder à quoi ressemble un fichier DeviceTree. Je vais prendre en exemple une de mes machines ARM64, à base de puce Amlogic S905X3. Cette machine a heureusement un DTS (Device Tree Source) dans le noyau, donc elle est officiellement supportée «upstream» comme on dit.<br>
<a href="https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/amlogic/meson-sm1-odroid-hc4.dts">https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/amlogic/meson-sm1-odroid-hc4.dts</a></p>
<p>On observe une description de l'ensemble des composants, le ventilateur, les leds, les régulateurs de tension, les différents bus… Parce que la machine ne dispose d'aucune solution d'énumération du matériel. Et bien sûr, impossible d'aller au hasard sur les GPIO du SoC pour voir ce qui est branché où, c'est un coup à, au mieux, éteindre la machine soudainement (et au pire, à la détruire). Que ce soit clair : ce fichier DTS n'est pas spécifique à un SoC, il est spécifique à une implémentation d'un SoC ! Les odroid C4 et HC4, bien qu'extrêmement proches et avec le même SoC, n'ont pas le même DeviceTree, et il est probable que booter l'un avec le DT de l'autre ne marche pas du tout.</p>
<h2 id="toc-4-et-une-exception-dans-le-chaos">4) Et une exception dans le chaos ?</h2>
<p>Au moins un fabricant de cartes ARM a compris qu'embarquer un firmware sur ses cartes était une solution simple pour améliorer beaucoup de choses. Ou alors pour simplifier son travail, je ne sais pas trop leur motivation. Il s'agit de hardkernel et de ses cartes odroid. L'intégration par défaut de petitboot est «relativement» récente, avec une annonce initiale en 2019 et une préinstallation qui a commencé dans les mois qui ont suivi.<br>
Du coup, commençons enfin le sujet de ce journal, avec le bon.<br>
Prenons mon serveur installé avec petitboot, et faisons un fdisk -l.</p>
<pre><code># fdisk -l /dev/nvme0n1
Disk /dev/nvme0n1: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disk model: Samsung SSD 980 1TB
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 16384 bytes / 131072 bytes
Disklabel type: dos
Disk identifier: 0xc7716abe
Device Boot Start End Sectors Size Id Type
/dev/nvme0n1p1 * 2048 999423 997376 487M 83 Linux
/dev/nvme0n1p2 999424 1953523711 1952524288 931G 8e Linux LVM
</code></pre>
<p>Pas de partition FAT32. Une simple partition ext4 contenant /boot, et c'est réglé. Contrairement aux systèmes UEFI qui imposent une partition ESP à un format compris par l'UEFI, donc dans 99,9999% des cas en FAT.<br>
J'eus pu faire avec une unique partition / et pas un /boot, mais je préfère mettre tout ce qui peut l'être en LVM.</p>
<p>Bien sûr, ce n'est pas grand chose, la partition FAT n'a jamais tué personne. Alors continuons avec un exemple très concrêt de ce que m'apporte Petitboot ici. Pour installer ma debian, je n'ai pas eu besoin de clé USB ou de carte SD à écrire sur mon PC. Non. J'ai pris ma carte ARM, j'ai vissé un disque NVMe, branché un écran, un clavier, un RJ45, et c'est tout (bon et l'alimentation hein, c'est pas de la magie).<br>
Petitboot est apparu à l'écran. Je ferme l'écran de boot pour obtenir un shell, et je lance une commande fournie par hardkernel, netboot_default. Cette commande ajoute automatiquement des entrées au boot pour aller chercher l'installeur debian et l'installeur ubuntu en HTTP au démarrage. Je démarre l'installeur debian comme ça, j'installe mon système… et aucune clé USB ou carte SD à écrire. Très, très, très confortable.<br>
Bien sûr, petitboot lui-même est super pratique : si vous cassez votre système, vous avez un shell, un noyau et un espace utilisateur connus, à partir desquels vous pouvez monter votre système et faire les réparations dont vous avez besoin.</p>
<p>Quelques photos du menu initial, du shell, et le menu une fois les entrées de netboot chargées et une carte SD insérée…</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f646c66702d7065746974626f6f742f646c66702d70622d312e6a7067/dlfp-pb-1.jpg" alt="L'écran avec le menu initial" title="Source : https://rock.pinaraf.info/~pinaraf/dlfp-petitboot/dlfp-pb-1.jpg"></p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f646c66702d7065746974626f6f742f646c66702d70622d322e6a7067/dlfp-pb-2.jpg" alt="Le shell (busybox), avec quelques utilitaires classiques" title="Source : https://rock.pinaraf.info/~pinaraf/dlfp-petitboot/dlfp-pb-2.jpg"></p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f646c66702d7065746974626f6f742f646c66702d70622d342e6a7067/dlfp-pb-4.jpg" alt="L'écran avec les entrées de netboot et après avoir inséré une carte SD avec un OS…" title="Source : https://rock.pinaraf.info/~pinaraf/dlfp-petitboot/dlfp-pb-4.jpg"></p>
<h2 id="toc-5-le-bad">5) Le bad</h2>
<p>Hardkernel ne fournit pas de méthode ou de documentation pour reproduire l'image Petitboot. C'est plusieurs fois dommage : cela ne permet pas de bidouiller facilement le système, de vérifier son contenu… Et c'est même en violation des licences des composants de Petitboot, ça risque de leur retomber méchamment dessus.<br>
Pour ma part, ce point m'embête également pour régler un autre problème qui me semble vraiment "bad" pour le coup : pourquoi des utilitaires disque à mes yeux basiques ne sont pas inclus ? Cela fait presque 20 ans que j'installe mes machines avec du LVM, je sais que les installeurs ne mettent pas ça assez en avant, mais le gain en vaut largement la peine. Et petitboot le gère, mais la version de hardkernel oublie d'embarquer le binaire…<br>
À la croisée entre ces deux problèmes, la validation du système et les disques : cryptsetup n'est pas pris en charge. Et c'est dommage, il serait possible d'avoir un disque intégralement chiffré, avec une passphrase demandée au boot, et transmise au noyau démarré pour qu'elle n'ait pas à être saisie deux fois.<br>
Enfin, un dernier bad : dropbear n'est pas présent. Pour des machines sans affichage, cela simplifierait énormément les choses… Mais à nouveau, oubli bien dommage de leur part.</p>
<h2 id="toc-6-le-ugly">6) Le ugly</h2>
<p>Vous noterez que je n'ai parlé que d'une des facettes principales du firmware (le bootloader) et un peu survolé la partie «outils pour diagnostiquer un problème».<br>
Les plus attentifs d'entre vous auront noté que je n'ai pas parlé de la description du matériel et sa transmission au système démarré.<br>
Et là, on entre dans le franchement ugly.</p>
<pre><code># ls /boot/*`uname -r`
/boot/config-6.2.0-odroid-arm64 /boot/dtb-6.2.0-odroid-arm64 /boot/initrd.img-6.2.0-odroid-arm64 /boot/System.map-6.2.0-odroid-arm64 /boot/vmlinuz-6.2.0-odroid-arm64
</code></pre>
<p>J'ai besoin d'un fichier dtb encore ? Mais pourquoi ! Pourquoi pourquoi pourquoi !<br>
Rendez-vous compte, le firmware a déjà un DeviceTree ou équivalent pour booter son Linux. Et il fonctionne très bien. Alors pourquoi ? Vous étiez à deux doigts de pouvoir avoir des systèmes génériques, pire encore, à quatre doigts d'avoir une interface de configuration pour vos accessoires qui activerait au boot les bons morceaux de dtb… Et vous avez gâché cette opportunité. Et comme vous ne donnez pas le code source, impossible de rattraper votre bêtise crasse.</p>
<h2 id="toc-7-the-conclusion">7) The conclusion</h2>
<p>Je reste heureux de mes machines Petitboot, et il est hors de question que je retourne en arrière là dessus. Le gain en confort est indéniable, malgré les problèmes évoqués. De plus, ces problèmes ne sont pas insurmontables, et sont des problèmes d'implémentation, pas des problèmes fondamentaux sur le fonctionnement de Petitboot. Puis qui sait, avec un peu de chance, la future machine OpenPower 10 de Raptor Computing ne sera pas trop onéreuse… (ok, beaucoup beaucoup beaucoup beaucoup de chance)<br>
J'entends par contre que cette solution ne soit pas optimale pour les plus petits OS qui n'ont pas nécessairement les moyens de développer une solution de boot supplémentaire spécifique, démarrable en kexec(). On pourrait imaginer pour ces cas là un kexec() vers un environnement UEFI éventuellement… (ou juste contribuer aux projets concernés, promis quand j'aurai du temps, mais pour le moment je rédige un journal linuxfr)</p>
<div><a href="https://linuxfr.org/users/pied/journaux/petitboot-sur-arm-le-bon-le-bad-et-le-ugly.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/133966/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/petitboot-sur-arm-le-bon-le-bad-et-le-ugly#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/133966/comments.atomtag:linuxfr.org,2005:Diary/409172023-10-20T15:14:26+02:002023-10-20T15:14:26+02:00Coroutines, histoire d'un nouvel inutilitaire…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-choix-des-armes">Choix des armes</a></li>
<li><a href="#toc-le-d%C3%A9veloppement">Le développement</a></li>
<li><a href="#toc-le-r%C3%A9sultat">Le résultat</a></li>
</ul>
<p>Salutations !</p>
<p>À mon travail, nous utilisons actuellement pour la plupart de nos besoins des serveurs dédiés loués à une petite PME roubaisienne (connue pour son étroite collaboration avec le SDIS). Nous devons avoir à la louche une vingtaine de serveurs chez cet hébergeur. Globalement, on peut le dire, tout va bien. Son réseau est en carton, pirouette cacahouete, ses escaliers non-ignifugés… mais on a des redondances entre centres de données, tout devrait bien se passer.<br>
Mais il y a une chose qui est difficile à supporter : le célèbre "Manager", l'espace client. Quiconque l'a déjà utilisé sera d'accord avec ces chiffres, mais pour d'autres ce sera peut-être une surprise.</p>
<ul>
<li>Temps requis pour se connecter : 7 secondes.</li>
<li>Temps requis pour lister les serveurs après connexion : 3 secondes.</li>
<li>Temps requis pour afficher les informations d'un serveur : 5 secondes.</li>
</ul>
<p>Je ne plaisante pas, et il était en forme lors de mon test, j'ai déjà eu des temps à plus de 15 secondes pour afficher les informations d'un serveur par exemple.<br>
Quand une urgence demande qu'on se connecte au KVM d'un serveur, ces temps s'ajoutent aux 15 secondes nécessaires pour que le KVM soit accessible et fait donc plus que le doubler (et encore, dans le manager c'est plus long que 15 secondes). Insupportable. Sans oublier tous les problèmes d'ergonomie inhérents aux applications web modernes (notamment parce qu'il faut 'aérer' les interfaces, quitte à ce que rien ne soit faisable sans devoir défiler ou aller dans différents onglets, mais aussi parce que les applications web ont leur limites ou juste parce que les concepteurs vivent sur leur nuage). Or, la logique chez OVH −au diable la subtilité− est simple : si les clients veulent une interface plus pratique, plus adaptée à leurs besoins, on donne des APIs donc ils peuvent la coder eux-même. C'est d'ailleurs sur cette logique que les applications mobiles et desktop (MoM et Momi) ont disparu. Du coup… ça m'emmerde, je déteste faire le travail d'une entreprise que je paie déjà pour ce service en théorie, mais pas le choix, il va falloir que je code un outil pour automatiser l'accès au KVM.</p>
<h2 id="toc-choix-des-armes">Choix des armes</h2>
<p>Comme je le dis souvent (et j'ai toujours raison), on ne choisit pas vraiment son langage de programmation. Si on a, par chance, à l'intersection de toutes les contraintes plusieurs langages, alors on a un choix, mais voilà, ça ne sera pas un choix pleinement libre.</p>
<p>Mes principales contraintes :</p>
<ul>
<li>avoir des bibliothèques pour faire des applications graphiques (et pas du web, non merci),</li>
<li>avoir dans les composants disponibles un moteur de rendu HTML complet (parce que le KVM est en «HTML5»),</li>
<li>pouvoir intégrer des appels HTTP sans que ça ne soit la mort (pas de callbacks ou autre fonctionnement à l'ancienne, il va y avoir trop d'appels pour que ça ne finisse pas vite en code spaghetti),</li>
<li>j'ai envie d'apprendre des nouveaux trucs, mais il faut quand même que je puisse avancer assez vite, la courbe d'apprentissage ne doit pas ressembler à un mur de parpaings.</li>
</ul>
<p>À la base je voulais utiliser FreePascal et Lazarus, mais je n'ai pas trouvé de solution acceptable pour intégrer le KVM HTML5 (petits regrets sur l'absence d'un équivalent de COM sur nos environnements de bureau, il y a le KParts de KDE éventuellement mais quitte à utiliser Qt autant prendre QtWebEngine dans ce cas ).<br>
Java, même combat pour le rendu HTML. C# avec WebkitGtkSharp par contre, ça pourrait le faire.<br>
J'ai regardé Rust, mais rebelote sur le HTML, et le développement d'applications graphiques c'est pas encore trop ça.<br>
Et il y a les traditionnels GTK/Qt en C/C++, et éventuellement les bindings Python (ou autre langage) pour ces derniers, à condition qu'ils intègrent QtWebEngine, QtWebKit ou GtkWebKit.<br>
Pour que les appels HTTP soient «jolis», le plus simple reste les coroutines. Chance, c'est possible en C++20 avec les coroutines et qcoro pour l'intégration Qt (mais je n'ai rien trouvé pour gtkmm). Ou avec Python3 et qasync/asyncqt pour l'intégration avec Qt. Ça semble plus facile sur Python / GTK, mais je ne trouve pas de solution simple pour intégrer GtkWebKit dans ce cadre.</p>
<p>Étant donné ma contrainte sur la « quantité » d'apprentissage, j'ai préféré me rabattre sur C++20/Qt, pour me concentrer sur la découverte et l'apprentissage de cette implémentation des coroutines. C#/GTK m'intrigue, mais je reste inquiet par rapport aux outils de développement (MonoDevelop est mort, VSCode est propriétaire et ses plugins C# sont de mémoire propriétaires également)</p>
<h2 id="toc-le-développement">Le développement</h2>
<p>Je n'aimerais pas lire un journal «tuto - écrire une application C++/Qt». Par contre je pense que vous apprécierez peut-être d'avoir des informations sur les coroutines en C++ avec Qt, parce que franchement, c'est une bouffée d'air frais pour ce genre d'application.<br>
Tout d'abord, si vous n'avez jamais utilisé QNetworkAccessManager et ses APIs pour faire des appels HTTP, petit rappel des faits. Vous instanciez cette classe et vous appelez dessus des méthodes get, post, put ou deleteResource (delete étant un mot clé du C++, le nom change, forcément…). Vous obtenez alors en retour un pointeur vers un QNetworkReply. Une fois que ce dernier émet le signal finished(), vous pouvez récupérer ce que le serveur vous a répondu.<br>
Le problème, concrêtement, c'est si vous devez enchaîner deux, trois, quatre appels. Vous aurez alors un code ressemblant à :</p>
<pre><code class="c++"><span class="k">auto</span> <span class="n">reply1</span> <span class="o">=</span> <span class="n">nam</span><span class="o">-></span><span class="n">get</span><span class="p">(</span><span class="n">QUrl</span><span class="p">(</span><span class="s">"http://www.example.com/1"</span><span class="p">));</span>
<span class="n">QObject</span><span class="o">::</span><span class="n">connect</span><span class="p">(</span><span class="n">reply1</span><span class="p">,</span> <span class="o">&</span><span class="n">QNetworkReply</span><span class="o">::</span><span class="n">finished</span><span class="p">,</span> <span class="p">[</span><span class="n">reply1</span><span class="p">,</span> <span class="n">ui</span><span class="p">,</span> <span class="n">nam</span><span class="p">]</span> <span class="p">()</span> <span class="p">{</span>
<span class="n">ui</span><span class="o">-></span><span class="n">label1</span><span class="o">-></span><span class="n">setText</span><span class="p">(</span><span class="s">"Request finished, yay"</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">target</span> <span class="o">=</span> <span class="n">QUrl</span><span class="p">(</span><span class="n">QString</span><span class="o">::</span><span class="n">fromUtf8</span><span class="p">(</span><span class="n">reply1</span><span class="o">-></span><span class="n">readAll</span><span class="p">()));</span>
<span class="k">auto</span> <span class="n">reply2</span> <span class="o">=</span> <span class="n">nam</span><span class="o">-></span><span class="n">get</span><span class="p">(</span><span class="n">target</span><span class="p">);</span>
<span class="n">QObject</span><span class="o">::</span><span class="n">connect</span><span class="p">(</span><span class="n">reply2</span><span class="p">,</span> <span class="o">&</span><span class="n">QNetworkReply</span><span class="o">::</span><span class="n">finished</span><span class="p">,</span> <span class="p">[</span><span class="n">reply2</span><span class="p">,</span> <span class="n">ui</span><span class="p">,</span> <span class="n">nam</span><span class="p">]</span> <span class="p">()</span> <span class="p">{</span>
<span class="n">ui</span><span class="o">-></span><span class="n">label1</span><span class="o">-></span><span class="n">setText</span><span class="p">(</span><span class="s">"Finished request 2, yay again"</span><span class="p">);</span>
<span class="n">reply2</span><span class="o">-></span><span class="n">deleteLater</span><span class="p">();</span>
<span class="p">});</span>
<span class="n">reply1</span><span class="o">-></span><span class="n">deleteLater</span><span class="p">();</span>
<span class="p">});</span></code></pre>
<p>Et là, c'est pour deux appels. Lancer le KVM avec l'API d'OVH prend 3 appels, puis une boucle d'attente avec un appel répété, puis un dernier appel pour avoir l'URL… Bonjour l'armée de callbacks !</p>
<p>Écrivons avec QCoro l'équivalent du code qui précède :</p>
<pre><code class="c++"><span class="k">auto</span> <span class="n">reply1</span> <span class="o">=</span> <span class="n">co_await</span> <span class="n">nam</span><span class="o">-></span><span class="n">get</span><span class="p">(</span><span class="n">QUrl</span><span class="p">(</span><span class="s">"http://www.example.com/1"</span><span class="p">));</span>
<span class="n">ui</span><span class="o">-></span><span class="n">label1</span><span class="o">-></span><span class="n">setText</span><span class="p">(</span><span class="s">"Request finished, yay"</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">target</span> <span class="o">=</span> <span class="n">QUrl</span><span class="p">(</span><span class="n">QString</span><span class="o">::</span><span class="n">fromUtf8</span><span class="p">(</span><span class="n">reply1</span><span class="o">-></span><span class="n">readAll</span><span class="p">()));</span>
<span class="k">auto</span> <span class="n">reply2</span> <span class="o">=</span> <span class="n">co_await</span> <span class="n">nam</span><span class="o">-></span><span class="n">get</span><span class="p">(</span><span class="n">target</span><span class="p">);</span>
<span class="n">ui</span><span class="o">-></span><span class="n">label1</span><span class="o">-></span><span class="n">setText</span><span class="p">(</span><span class="s">"Finished request 2, yay again"</span><span class="p">);</span>
<span class="k">delete</span> <span class="n">reply2</span><span class="p">;</span>
<span class="k">delete</span> <span class="n">reply1</span><span class="p">;</span></code></pre>
<p>C'est beaucoup plus clair et lisible. On attend le résultat du premier get, on exécute du code, on attend le deuxième get, on exécute du code. En procédant ainsi, on se simplifie tellement la vie.<br>
Et c'est pour ainsi dire «gratuit». Les coroutines sont des fonctions interruptibles, donc on n'a pas lancé des threads ou des processus en parallèle. Tout au plus utilise-t-on une quantité négligeable de RAM pour stocker l'état d'une fonction interrompue, pas beaucoup plus gros que ce que l'on utilisait (sans le voir) précédemment pour le contexte de nos fonctions lambdas.</p>
<p>Et du coup, pour les gens que ça intéresse, mon mini projet a une encapsulation basique de l'API d'OVH avec Qt et QCoro.<br>
L'utilisation est simple :</p>
<pre><code class="c++"> <span class="k">auto</span> <span class="n">ovh</span> <span class="o">=</span> <span class="k">new</span> <span class="n">OvhApi</span><span class="p">{</span><span class="s">"https://eu.api.ovh.com/1.0"</span><span class="p">,</span> <span class="k">this</span><span class="p">};</span>
<span class="n">ovh</span><span class="o">-></span><span class="n">setConsumerKey</span><span class="p">(</span><span class="n">key</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">meInfo</span> <span class="o">=</span> <span class="p">(</span><span class="n">co_await</span> <span class="n">ovh</span><span class="o">-></span><span class="n">get</span><span class="p">(</span><span class="s">"/me/"</span><span class="p">)).</span><span class="n">object</span><span class="p">();</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="n">meInfo</span><span class="p">;</span></code></pre>
<p>Sont cachés l'authentification avec la signature des requêtes, la construction de JSON, les concaténations d'URLs… On pourrait aller plus loin, mais à court terme, ce sera amplement suffisant.<br>
Par ailleurs, si une base de données est instanciée (avec QtSql), on peut passer à get un paramètre pour préciser une durée de mise en cache de la réponse. Cela accélère d'une façon phénoménale l'application, l'API OVH restant désespérément lente…</p>
<pre><code class="c++"> <span class="k">auto</span> <span class="n">ovh</span> <span class="o">=</span> <span class="k">new</span> <span class="n">OvhApi</span><span class="p">{</span><span class="s">"https://eu.api.ovh.com/1.0"</span><span class="p">,</span> <span class="k">this</span><span class="p">};</span>
<span class="n">ovh</span><span class="o">-></span><span class="n">setConsumerKey</span><span class="p">(</span><span class="n">key</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">meInfo</span> <span class="o">=</span> <span class="p">(</span><span class="n">co_await</span> <span class="n">ovh</span><span class="o">-></span><span class="n">get</span><span class="p">(</span><span class="s">"/me/"</span><span class="p">,</span> <span class="mi">1</span><span class="n">h</span><span class="p">)).</span><span class="n">object</span><span class="p">();</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="n">meInfo</span><span class="p">;</span></code></pre>
<p>Certaines informations ne changeant que rarement (les spécifications d'un serveur, le détail d'une facture…), on peut mettre un cache très aggressif pour certaines données et avoir alors des appels instantanés.</p>
<h2 id="toc-le-résultat">Le résultat</h2>
<p>C'est une application graphique, donc capture d'écran obligatoire.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f6f76686b766d2f6f76686b766d2d312e706e67/ovhkvm-1.png" alt="l'interface de base avec un serveur affiché" title="Source : https://rock.pinaraf.info/~pinaraf/ovhkvm/ovhkvm-1.png"></p>
<p>Avec un KVM ouvert…</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f6f76686b766d2f6f76686b766d2d322e706e67/ovhkvm-2.png" alt="et ici avec un KVM dans un deuxième onglet" title="Source : https://rock.pinaraf.info/~pinaraf/ovhkvm/ovhkvm-2.png"></p>
<p>Les temps de chargement sont sans commune mesure avec les temps du manager d'OVH, surtout quand les caches sont chargés.<br>
Et en prime, les pires pièges de facturation d'OVH que je connaisse (notamment l'engagement tacitement reconduit) sont clairement indiqués dans l'interface, pour aider à s'y retrouver.</p>
<p>Il y a plein de choses qu'on pourrait y ajouter. Mais ça fait le taf pour moi et mes collègues.</p>
<p>Pour ceux que ça intéresse, le code est là, sous licence GPL3 : <a href="https://git.entrouvert.org/entrouvert/OvhKvm">https://git.entrouvert.org/entrouvert/OvhKvm</a></p>
<p>Forkez à votre guise, faites-en ce que vous voulez, comme je l'ai dit ça a fait son taf pour nous.</p>
<p>La bise, bon week-end à vous</p>
<p>PS…<br>
«Tu devrais diffuser cette application aux autres clients d'OVH, je suis sûr que ça pourrait plaire.»<br>
À cela, j'ai répondu, un peu en troll mais beaucoup parce que je refuse d'avoir à payer Apple pour diffuser mon appli «Peut-être, mais je refuse de diffuser une version sous forme de binaire pour MacOS de l'application, par contre une version Haiku pourquoi pas…»</p>
<p>C'est vrai ça, pourquoi pas… Pourquoi pas…</p>
<p>Hop, une VM avec Haiku, et c'est parti ! Qt a été porté sur Haiku, il y a un G++ récent, «ça va bien se passer» comme dit l'autre.<br>
Surprise, dans les ports, QCoro est disponible !<br>
Bon ben maintenant il faut que je soumette sur haikuports une mise à jour de QCoro, peut-être deux/trois autres petits trucs…</p>
<p>PPS…<br>
Alors moi je bosse en DBA et admin sys, mais on recrute des devs python, pour ceux que ça intéresse… <a href="https://www.entrouvert.com/actualites/2023/embauche-developpeuse-eur-python-django/">https://www.entrouvert.com/actualites/2023/embauche-developpeuse-eur-python-django/</a></p>
<div><a href="https://linuxfr.org/users/pied/journaux/coroutines-histoire-d-un-nouvel-inutilitaire.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/133693/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/coroutines-histoire-d-un-nouvel-inutilitaire#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/133693/comments.atomtag:linuxfr.org,2005:Diary/406622023-04-17T09:37:32+02:002023-04-17T09:37:32+02:00LUKS, TPM et bouletteLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-contexte">Contexte</a></li>
<li><a href="#toc-utilisation">Utilisation</a></li>
<li><a href="#toc-au-secours-jai-perdu-ma-passphrase">Au secours, j'ai perdu ma passphrase…</a></li>
</ul>
<h2 id="toc-contexte">Contexte</h2>
<p>À mon taf actuel, on nous demande que les disques durs de données de nos serveurs soient chiffrés. L'objectif est avant tout de se protéger d'un vol d'un disque par un technicien de l'opérateur qui nous héberge, ou d'un mauvais effaçage après recyclage du disque en cas d'incident.<br>
Mais du coup, comment conjuguer ça avec la volonté d'avoir un serveur capable de redémarrer "seul" en cas d'incident ?<br>
Afin d'avoir une solution où la passphrase ne se retrouve pas dans un fichier en clair sur le serveur ou dans l'initramfs, le plus simple reste d'utiliser un élément qui reste quelque peu honni par ses origines : le TPM.</p>
<p>Le TPM est une puce de chiffrement, similaire à une carte à puce, intégrée sur la carte mère des machines (et requis pour Windows), qui permettra de déchiffrer des secrets si l'état d'intégrité de la machine n'est pas altéré. Concrètement, si la signature du firmware, du bootloader ou du noyau n'est pas bonne, les secrets sont inaccessibles. C'est le principe de Secure Boot.<br>
Dans notre cas, l'intérêt est surtout de lier les disques à une machine. La probabilité du vol d'un serveur complet est considérée négligeable face au risque du vol d'un disque.</p>
<h2 id="toc-utilisation">Utilisation</h2>
<p>Du coup, étape 1 : activer Secure Boot. Bonne chance, ça dépend du firmware de chaque machine, et si, en prime, vous avez installé en mode "legacy" au lieu d'UEFI, vous allez avoir une ou deux étapes de souffrance additionnelle (ajouter une partition ESP, changer de grub…)<br>
Par contre, une fois Secure Boot activé, vous avez normalement au boot ceci :</p>
<pre><code># dmesg | grep secureboot
[ 0.000000] secureboot: Secure boot enabled
</code></pre>
<p>Ce qui veut dire que le TPM est normalement accessible, en avant pour la suite !</p>
<p>Étape 2 : créer un volume avec LUKS et une passphrase. Cette opération est classique, je ne vais pas tout détailler. Grosso modo: <code>crypsetup luksFormat /dev/mapper/mon-volume</code><br>
Et notez bien la passphrase, elle vous servira de clé de secours en cas de perte du TPM (imaginez un instant que votre hébergeur ait une fuite sur son refroidissement et que votre carte mère soit sous l'eau…)</p>
<p>Étape 3 : installer les éléments requis pour avoir le TPM fonctionnel. Dans mon cas, il s'agit d'un serveur en bullseye, or il est bien plus simple d'utiliser le systemd de bookworm pour ce qui suit (il faut beaucoup plus bidouiller cryptsetup pour qu'il utilise le TPM), du coup, activons les backports et <code>apt install -t bullseye-backports systemd</code>. En complément, pour le TPM : <code>apt install libtss2-esys-3.0.2-0 libtss2-rc0</code><br>
Désormais, la commande <code>systemd-cryptenroll --tpm2-device=list</code> devrait lister un TPM disponible pour ce qui suit</p>
<p>Étape 4 : enroller le TPM dans le luks. C'est facile, systemd-cryptenroll s'en charge: <code>systemd-cryptenroll --tpm2-device=/dev/tpmrm0 --tpm2-pcrs=7 /dev/mapper/mon-volume</code><br>
Bien sûr, la passphrase sera demandée pour pouvoir ajouter le TPM.</p>
<p>Étape 5 : enjoy and profit.<br>
Pour vérifier que ça fonctionne, il suffit de faire <code>/usr/lib/systemd/systemd-cryptsetup attach volume-clear /dev/mapper/mon-volume - tpm2-device=/dev/tpmrm0</code><br>
Normalement, le volume sera accessible en clair sur /dev/mapper/volume-clear.<br>
Pour le monter au boot, il suffit alors d'ajouter dans /etc/crypttab quasiment ce que l'on vient de saisir, à savoir:<br>
<code>volume-clear /dev/mon/volume - tpm2-device=/dev/tpmrm0</code></p>
<p>Facile, n'est-ce-pas ?</p>
<h2 id="toc-au-secours-jai-perdu-ma-passphrase">Au secours, j'ai perdu ma passphrase…</h2>
<p>Ça peut arriver à tout le monde, donc pour votre sécurité, j'ai décidé de supprimer ma passphrase et pourtant de réussir à en rajouter une nouvelle grâce à la clé du TPM… (Oui bon ça arrive à tout le monde une fausse manip, ça va, puis j'ai fait ça en recette, pas en prod)</p>
<p>Tout d'abord, un rappel : comment marche LUKS. C'est très simple, un en-tête est ajouté sur le volume, qui contient un ensemble de données en JSON (sigh). Dans ces données se trouvent surtout des slots. Chaque slot permet de déchiffrer une clé de volume, qui elle permet de déchiffrer les données du reste du disque. Dans la procédure qui précède, nous avons donc deux slots : le slot 0 avec notre passphrase, le slot 1 pour le TPM.<br>
On peut donc avoir une dizaine de passphrases différentes, les révoquer… sans pour autant rechiffrer tout le disque, heureusement.<br>
Par contre si on fuite la clé du volume, là effectivement il va falloir travailler un peu plus, mais c'est pas le sujet ici.<br>
Historiquement, il était d'ailleurs assez simple de récupérer la clé du volume sur un volume déjà monté, mais depuis un certain temps elle est stockée dans un keyring noyau interdit d'accès à l'espace utilisateur, il va donc falloir faire autrement.</p>
<p>systemd-cryptenroll permet d'ajouter une passphrase, un TPM, un FIDO ou autre, mais il faut systématiquement lui donner une passphrase existante pour accéder au volume. Il refuse d'utiliser un TPM pour ajouter une passphrase, limite d'implémentation je suppose.<br>
MAIS… rien ne nous interdit d'aller avec gdb voir la clé utilisée pour déverrouiller le volume avec le TPM, non ?</p>
<p>C'est parti, <code>apt install gdb systemd-dbgsym</code>…</p>
<p><code>sudo gdb /usr/lib/systemd/systemd-cryptsetup</code></p>
<p>Quand on regarde le code source de systemd et le code de la libcryptsetup, on voit une fonction au nom fort tentant qui est utilisée pour ouvrir le volume : <code>crypt_activate_by_passphrase</code>.<br>
Cf. <a href="https://github.com/systemd/systemd/blob/v252/src/cryptsetup/cryptsetup.c#L1177-L1186">https://github.com/systemd/systemd/blob/v252/src/cryptsetup/cryptsetup.c#L1177-L1186</a></p>
<p>Un breakpoint bien placé devrait nous aider…<br>
<code>b crypt_activate_by_passphrase</code><br>
<code>r attach volume-test /dev/mapper/mon-volume - tpm2-device=/dev/tpmrm0</code></p>
<p>Et hop, notre breakpoint s'active ! Par contre j'ai pas les symboles de debug de libcryptsetup moi…<br>
<code>up</code><br>
Et je suis dans le code de systemd…<br>
<code>p base64_encoded</code><br>
Et voilà la passphrase. Je n'ai plus qu'à l'utiliser pour ajouter une nouvelle passphrase, et le tour est joué.</p>
<div><a href="https://linuxfr.org/users/pied/journaux/luks-tpm-et-boulette.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/130943/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/luks-tpm-et-boulette#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/130943/comments.atomtag:linuxfr.org,2005:Diary/403682022-09-06T23:25:34+02:002022-09-06T23:25:34+02:00Sobriété, j'écris ton nomLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-1-nan-ze-veut-pas">1) Nan, ze veut pas</a></li>
<li><a href="#toc-2-zai-dit-ze-veut-pas">2) Z'ai dit, ze veut pas</a></li>
<li><a href="#toc-3-mais-pourquoi-tu-cognes-ta-t%C3%AAte-sur-ton-bureau">3) Mais pourquoi tu cognes ta tête sur ton bureau ?</a></li>
<li><a href="#toc-4-pourquoi-y-a-t-il-encore-28mb-pour-une-page-web">4) Pourquoi y a-t-il encore 28MB pour une page web</a></li>
<li><a href="#toc-5-conclusions-douloureuses">5) Conclusions douloureuses…</a></li>
</ul>
<p>Depuis quelques jours, je suis l'heureux client d'une crèche. Lors de l'intégration de ma fille dans la structure, un point a été ré-évoqué que j'avais oublié depuis le moment où nous nous étions inscrits : une application mobile est disponible.<br>
Joie ! Volupté ! Une application mobile, génial, oui, super…<br>
Bon, en fait elle peut servir (échanger des messages avec l'équipe, avoir accès aux jours de fermeture et autres éléments administratifs), du coup, va bien falloir que je regarde ça…</p>
<h2 id="toc-1-nan-ze-veut-pas">1) Nan, ze veut pas</h2>
<p>J'ai pour intelliphone un PinePhone, avec un OS pas du tout Android. Alors certes, ça pourrait être l'occasion pour moi d'installer un waydroid ou équivalent, mais clairement, pour juste ça, j'ai pas envie.<br>
Du coup, je note le nom de l'application, et je cherche si un site web n'existe pas avec la même fonctionnalité… Y'a un <a href="https://appli.app/">https://appli.app/</a> qui répond, mais c'est le backoffice côté crèche, pas côté progéniteurs. <a href="https://appli.io">https://appli.io</a> me sort une page «firebase», ce qui ne me rassure pas…</p>
<h2 id="toc-2-zai-dit-ze-veut-pas">2) Z'ai dit, ze veut pas</h2>
<p>Bon ben on va télécharger l'apk et décompiler le java pour voir comment ça marche. Ça doit pas être bien dur sur une appli comme ça (j'ai un autre projet en cours bien plus fun avec du code natif dans l'apk, j'en parlerai)…<br>
Je cherche donc sur un site de téléchargement d'apk l'appli en question. 19MB de téléchargement.<br>
C'est beaucoup quand même.<br>
Je télécharge, je décompresse, et je commence à regarder.</p>
<pre><code>snoopy@peanuts2 ~/temp/dlfp % du -shc *
12K AndroidManifest.xml
28M assets
4.4M classes.dex
4.0K firebase-common.properties
4.0K firebase-components.properties
4.0K firebase-datatransport.properties
4.0K firebase-encoders-json.properties
4.0K firebase-iid-interop.properties
4.0K firebase-iid.properties
4.0K firebase-installations-interop.properties
4.0K firebase-installations.properties
4.0K firebase-measurement-connector.properties
4.0K firebase-messaging.properties
328K META-INF
4.0K play-services-basement.properties
4.0K play-services-base.properties
4.0K play-services-location.properties
4.0K play-services-places-placereport.properties
4.0K play-services-stats.properties
4.0K play-services-tasks.properties
8.8M res
584K resources.arsc
4.0K transport-api.properties
4.0K transport-backend-cct.properties
4.0K transport-runtime.properties
42M total
</code></pre>
<p>42MB. C'est <em>beaucoup</em>.<br>
Ce qui m'intrigue là dedans, c'est le dossier assets. Que peut-il bien contenir qui fasse 28MB.</p>
<h2 id="toc-3-mais-pourquoi-tu-cognes-ta-tête-sur-ton-bureau">3) Mais pourquoi tu cognes ta tête sur ton bureau ?</h2>
<p>Le dossier assets contient un sous-dossier, nommé public, et un fichier json à la con qui parle de npm, PushNotifications…<br>
Le dossier public contient…</p>
<pre><code>android-chrome-192x192.png android-chrome-512x512.png apple-touch-icon.png cordova.js cordova_plugins.js css favicon.ico favicon-16x16.png favicon-32x32.png fonts img index.html js mstile-150x150.png native-bridge.js robots.txt safari-pinned-tab.svg
</code></pre>
<p>robots.txt ? favicon.ico ? index.html ?<br>
J'ouvre index.html dans mon firefox, et magie… un formulaire de login apparait avec le logo. Je saisis login, mot de passe… et bingo, j'ai accès à toutes les fonctionnalités.</p>
<h2 id="toc-4-pourquoi-y-a-t-il-encore-28mb-pour-une-page-web">4) Pourquoi y a-t-il encore 28MB pour une page web</h2>
<p>Bon, première stupeur passée, regardons pourquoi la page prend tant de place.</p>
<pre><code>snoopy@peanuts2 ~/temp/dlfp/assets/public % du -sh * | grep M
1.1M css
6.9M fonts
9.4M img
9.8M js
</code></pre>
<p>Oui, évidemment, logique… Mais 10MB de JS, c'est beaucoup.</p>
<pre><code>192K js/app.ccdd617e.js
548K js/app.ccdd617e.js.map
2.3M js/chunk-vendors.f307d948.js
6.8M js/chunk-vendors.f307d948.js.map
</code></pre>
<p>Ha ben… le JS est minifié, mais on laisse quand même le .map utile pour débugguer, et on l'envoi sur les téléphones de tout le monde. Bravo, beau travail !<br>
Donc on vire les .map, on tombe à 20M, continuons.</p>
<pre><code>snoopy@peanuts2 ~/temp/dlfp/assets/public % ls fonts/
GrandHotel-Regular.000c98f7.ttf SF-UI-Text-Medium.19d73610.otf fa-brands-400.d71bb30a.woff fa-brands-400.79192c04.eot fa-duotone-900.12f37b45.ttf fa-light-300.04c6f174.eot fa-regular-400.d6dfd56d.woff2 fa-regular-400.255beb43.eot fa-solid-900.25c8182a.eot
SF-UI-Text-Bold.587003e2.otf SF-UI-Text-Regular.5b838b00.otf fa-brands-400.ffb23988.woff2 fa-duotone-900.c7a6963a.eot fa-duotone-900.303f350f.woff2 fa-light-300.4f6e39d6.woff2 fa-regular-400.6c2e7024.woff fa-solid-900.0fe9b01b.ttf fa-solid-900.727b7753.woff2
SF-UI-Text-Light.2425d3ea.otf SF-UI-Text-Semi-Bold.f7e513bb.otf fa-brands-400.6381ec23.ttf fa-duotone-900.eb782a17.woff fa-light-300.0dc9a6cb.woff fa-light-300.10839702.ttf fa-regular-400.83aa8612.ttf fa-solid-900.3e9b250e.woff
</code></pre>
<p>Ha ben oui. Tant qu'à faire. Autant mettre les polices dans tous les formats, des fois que, on sait jamais.<br>
Mon firefox utilise les .woff2, donc je vire les autres, et le dossier fonts passe de 6,9 à 1,9MB (et l'application totale à 16MB)… au suivant !</p>
<pre><code>snoopy@peanuts2 ~/temp/dlfp/assets/public % du -sh img/*
4.0K img/activity.ca3d17b5.svg
4.0K img/apple_0.5f669073.svg
4.0K img/apple_1.364ca09d.svg
4.0K img/apple_2.6dcdf98f.svg
8.0K img/baby_bottle.f0294369.svg
4.0K img/baby_diaper.f25b485a.svg
8.0K img/balance.12e7260b.svg
8.0K img/breastfeeding.227234e8.svg
4.0K img/diaper.735a652a.svg
716K img/fa-brands-400.8070c568.svg
2.5M img/fa-duotone-900.b326b0d1.svg
2.4M img/fa-light-300.6bbd339e.svg
2.1M img/fa-regular-400.11d3abbe.svg
1.7M img/fa-solid-900.717283fb.svg
4.0K img/late.1ce604a2.svg
52K img/meeko_head.966f2d9d.png
4.0K img/moon_2.629376ba.svg
8.0K img/no-avatar.1c823611.png
8.0K img/observation.e2beb80d.svg
4.0K img/pills.f808635c.svg
4.0K img/potty.842f185c.svg
4.0K img/thermometer.84895d0b.svg
4.0K img/toilet.1522b49c.svg
</code></pre>
<p>Ho, mais… fa-brands, fa-duotone… on l'a vu juste au dessus, ce sont encore des polices, mais en SVG cette fois. Des fois que, donc, on sait jamais, on a vraiment tous les formats de police possible. Je supprime, et hop le dossier img passe à 144KB, et l'appli à 5,8MB. Et j'ai plus qu'à mettre ça sur mon serveur perso et j'ai accès à «l'application» depuis partout, sans rien installer, magie !</p>
<h2 id="toc-5-conclusions-douloureuses">5) Conclusions douloureuses…</h2>
<p>Tout d'abord : pourquoi est-ce une application ? Si c'est pour embarquer une page web qui de toute façon ne fera rien sans vos webservices, ben autant l'héberger et filer le lien à tout le monde. J'ose pas regarder le contenu du .dex du coup. 4MB pour mettre juste une WebView en place, ou y a-t-il un contenu qui ne me plairait vraiment pas ?<br>
Et surtout, surtout : bordel, pourquoi est-ce-que ça doit prendre tant de place ? Même en imaginant un instant qu'il soit logique de distribuer une page web dans une application Android, quel est l'intérêt, dans le CSS et je suppose partout ailleurs, d'avoir tous les hacks nécessaires pour être compatible Internet Explorer ? D'avoir toutes les polices au complet, et dans tous les formats possible, même les pires ?</p>
<p>Certes, ce n'est «que» de l'espace disque utilisé. Mais sur un téléphone portable. Qui n'a pas forcément la possibilité d'avoir son stockage étendu. Sur le téléphone de ma compagne, l'application une fois installée consomme 37MB sur le disque.</p>
<p>Je ne sais pas où on va avec ce genre de chose, mais on y va sans réfléchir, c'est sûr…</p>
<div><a href="https://linuxfr.org/users/pied/journaux/sobriete-j-ecris-ton-nom.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/128679/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/sobriete-j-ecris-ton-nom#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/128679/comments.atomtag:linuxfr.org,2005:Diary/402992022-06-21T20:26:14+02:002022-06-21T20:26:14+02:00UEFI : y'a pas que les linuxiens qui pleurent…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-le-t%C3%A9l%C3%A9chargement-dun-windows-10">Le téléchargement d'un Windows 10…</a></li>
<li><a href="#toc-copier-liso-sur-une-cl%C3%A9-usb">Copier l'ISO sur une clé USB</a></li>
<li><a href="#toc-copier-liso-sur-une-cl%C3%A9-usb-bis">Copier l'ISO sur une clé USB, bis</a></li>
<li><a href="#toc-inversons-lordre-dinstallation">Inversons l'ordre d'installation</a></li>
<li><a href="#toc-retentative-dinstallation-de-windows">Retentative d'installation de Windows</a></li>
<li><a href="#toc-rufus-projet-libre-indispensable">Rufus, projet libre indispensable…</a></li>
<li><a href="#toc-conclusion-path%C3%A9tique-%C3%A0-un-triste-journal">Conclusion pathétique à un triste journal</a></li>
</ul>
<p>Bonjour bonjour</p>
<p>Aujourd'hui, mauvaise surprise : un ami se pointe avec un ordinateur portable «reconditionné» acheté à son travail, donc disque vierge de tout OS, et me demande d'y installer Windows, éventuellement Linux en dual boot… Bon. Ça me gonfle d'avoir une installation de Windows à faire, mais soit, je suis (trop) gentil, je devrais vraiment pas accepter ça mais j'accepte.<br>
Puis c'est pas très compliqué : on télécharge l'ISO, on la copie sur clé USB avec dd, et le tour est joué…</p>
<h2 id="toc-le-téléchargement-dun-windows-10">Le téléchargement d'un Windows 10…</h2>
<p>Microsoft, au moins jusqu'à Windows 10, mais probablement pareil pour le 11, ne fournit pas d'ISO hybride. C'est une ISO de CD à graver absolument sur DVD ou BD, ce n'est pas une table de partitions valide pour une clé USB. Donc vous pouvez faire dd, mais derrière le BIOS/UEFI va refuser le truc, méchamment.<br>
C'est un piège connu, mais comme un bleu, je suis tombé dedans. (Et ça m'a pris quelques reboot)</p>
<p>Note : première copie de 5GB en USB…</p>
<h2 id="toc-copier-liso-sur-une-clé-usb">Copier l'ISO sur une clé USB</h2>
<p>Bon ben je formate ma clé USB, en FAT32, et je copie dedans les fichiers de l'ISO Windows préalablement montée.<br>
Ha ben non, y'a un fichier de plus de 4GB pour l'installation, ça ne passe pas en FAT32.</p>
<p>Note : deuxième copie de 5GB en USB…</p>
<h2 id="toc-copier-liso-sur-une-clé-usb-bis">Copier l'ISO sur une clé USB, bis</h2>
<p>Je formate ma clé USB en NTFS, je copie les fichiers de l'ISO.<br>
Voilà, là ça devrait être enfin bon, non ?<br>
Non<br>
Du tout. Ça boote pas, le firmware n'affiche aucun message d'erreur, c'est comme si la clé était vide.</p>
<p>Note : troisième copie de 5GB en USB…</p>
<h2 id="toc-inversons-lordre-dinstallation">Inversons l'ordre d'installation</h2>
<p>Puisque l'installeur Windows ne démarre pas pour une raison inconnue, autant installer d'abord un Linux pour que j'ai des éléments de diagnostic et d'analyse de la situation.<br>
Là évidemment, pas grand chose à signaler (à part qu'une installation de Kubuntu par défaut sort des snap pour des applis comme Firefox, ce qui est horripilant et accroit considérablement le temps de démarrage de Firefox). Je laisse volontairement une partition vierge que je formaterai et monterai en NTFS après coup pour y copier l'ISO windows (gain de temps par rapport à l'USB).</p>
<p>Note : cette fois c'est seulement 3.5GB (wow ça a pris du poids ubuntu) copiés en USB</p>
<h2 id="toc-retentative-dinstallation-de-windows">Retentative d'installation de Windows</h2>
<p>Bon, j'ai un linux, efibootmgr, de quoi télécharger et déposer des fichiers localement. C'est plus confortable, ça devrait devenir facile.</p>
<p>Je tente de rajouter une entrée pointant vers la partition, avec le bot fichier .efi à booter. Échec, nada, quedal.</p>
<p>Je copie dans la partition ESP (/boot/efi) les fichiers efi et le dossier boot de l'ISO windows. Ça devrait lancer l'installeur et me permettre de lui dire où sont ses petits, non ?<br>
Non ?</p>
<p>Non.<br>
«Disque corrompu, fichiers introuvables»<br>
Merci.</p>
<p>Je gromelle, je tape du poing sur la table, je mets un bandage sur ma main, et je me dis «bon, ben on va tenter le shell EFI»<br>
Téléchargement depuis <a href="https://github.com/tianocore/edk2/releases">https://github.com/tianocore/edk2/releases</a>, copie dans /boot/efi, ajout d'une entrée de boot pour ça, redémarrage…</p>
<p>Hoo, il ne voit que la partition /boot/efi, les autres sont vues mais pas «montées».<br>
Listons les pilotes avec la commande EFI drivers :<br>
Mais… il n'y a que FAT ? Pas NTFS, pas ISO, pas UDF… ??<br>
Ben non.<br>
Du coup, si on veut booter l'installation de Windows sur une telle machine, comment est-on supposé faire ?</p>
<h2 id="toc-rufus-projet-libre-indispensable">Rufus, projet libre indispensable…</h2>
<p>C'est là qu'arrive un projet libre indispensable, <a href="https://rufus.ie/en/">https://rufus.ie/en/</a> (et ses projets liés)<br>
C'est avant tout un utilitaire Windows pour écrire des ISOs sur des CD/DVD/BD/clé USB, mais avec quelques options très intéressantes comme «permettre de démarrer l'installeur Windows même si l'UEFI ne gère pas NTFS». <br>
Ce qui m'arrange moins, c'est que c'est un utilitaire Windows, donc je vais devoir prendre les différents éléments utiles et les déposer dans le /boot/efi du Linux.<br>
Les éléments en question sont :<br>
- UEFI:NTFS : un bootloader (une application UEFI quoi) qui va chercher le pilote NTFS, une partition NTFS et démarrer l'exécutable /efi/boot/boot$arch.efi dedans<br>
- un port de ntfs-3g en EFI OU un port des systèmes de fichiers de grub</p>
<p>Quoi ? Comment ça «OU» ? Apprenons une puputerie de Microsoft à l'occasion : pour valider secure boot, il faut que tous les composants soient signés. Et la signature de Microsoft est la seule reconnue dans tous les UEFI. Donc il faut demander (poliment) à Microsoft de signer l'exécutable. Or… a priori Microsoft refuse de signer des exécutables sous licence GPL3. Du coup, vous pouvez utiliser le pilote ntfs.efi derivé de ntfs-3g en licence GPL2, ou utiliser un ntfs.efi dérivé de Grub, en GPL3, donc qui ne peut être signé par Microsoft.</p>
<p>Bref. Je pose les bons fichiers, je démarre le shell EFI, je lance uefi:ntfs… Et alleluia, l'installeur Windows démarre !</p>
<h2 id="toc-conclusion-pathétique-à-un-triste-journal">Conclusion pathétique à un triste journal</h2>
<p>L'installation de Windows est en plusieurs étapes, avec quelques redémarrages. S'ils veulent après tout, tant que ça marche… Au deuxième ou troisième redémarrage, j'ai été accueilli par un merveilleux écran Windows pour la création de compte, avec comme titre :<br>
«Bienvenue chez $CORP</p>
<p>Pour vous authentifier, veuillez saisir votre adresse mail @$CORP.COM»</p>
<p>Voilà. Le reconditionnement d'un PC pro, c'est pas juste formater le disque, c'est aussi supprimer des variables EFI qui me sont inconnues ! Ou des éléments dans la puce TPM qui sait…<br>
Ayant <s>pris la machine en</s> choppé une grippe, j'ai pas regardé encore comment rattraper ça. Mais le prochain que j'entends me dire qu'installer Linux ou même FreeBSD c'est compliqué, je lui fais manger un clavier.</p>
<div><a href="https://linuxfr.org/users/pied/journaux/uefi-y-a-pas-que-les-linuxiens-qui-pleurent.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/128074/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/uefi-y-a-pas-que-les-linuxiens-qui-pleurent#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/128074/comments.atomtag:linuxfr.org,2005:Diary/398902021-08-21T17:16:05+02:002021-08-21T17:16:05+02:00PDF, mais que fait la policeLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-1-regard-dutilisateur">1) Regard d'utilisateur</a></li>
<li><a href="#toc-2-plus-technique-le-contenu-du-pdf">2) Plus technique, le contenu du PDF…</a></li>
<li><a href="#toc-3-commen%C3%A7ons-avec-mupdf">3) Commençons avec mupdf</a></li>
<li><a href="#toc-4-que-fait-pdfjs">4) Que fait pdf.js ?</a></li>
<li><a href="#toc-5-quid-de-poppler">5) Quid de poppler</a></li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
<p>Salutations</p>
<p>Le PDF, quel format merveilleux. On génère un fichier et on est sûrs qu'il va s'afficher correctement partout, que ce soit avec des lecteurs libres (Poppler et donc Okular/Evince, mupdf, pdf.js, pdfium et donc Chromium…) ou propriétaires.<br>
Ou pas.<br>
«tiens, je me demandais, ton okular aussi il fait un rendu dégueulasse des fonts sur une attestation d'assurance maladie ameli ?»</p>
<p>En voilà une question qu'elle est bonne. Et oui, il fait un rendu dégueulasse. Bon, le document de test utilisé ici sera une attestation de mutuelle, mais l'idée est la même. Alors plongeons dans la fosse à purin qu'est le rendu de texte en PDF…</p>
<h2 id="toc-1-regard-dutilisateur">1) Regard d'utilisateur</h2>
<p>Bon. Ouvrons le PDF avec Okular (une version anonymisée est disponible sur <a href="https://rock.pinaraf.info/%7Epinaraf/dlfp-pdf/test-pdf-fonts-anon.pdf">https://rock.pinaraf.info/~pinaraf/dlfp-pdf/test-pdf-fonts-anon.pdf</a>). Le texte de l'adresse est effectivement dégueulasse. Le MA de MADAME est tout compressé, avec le M qui mord sur le A (ou l'inverse, je n'ai jamais étudié la férocité relative des lettres). De même sur le DA ou le D mord le A. C'est très sale.<br>
Okular a le bon goût, dans la fenêtre des propriétés du document (menu Fichier), d'indiquer les polices utilisées et leurs substitutions, et même le chemin du fichier de police sur le disque.<br>
On observe que 6 polices sont utilisées, deux Arial (dispo gratuitement avec une licence flexible), deux Calibri (coucou Microsoft qui interdit d'installer ces polices sur Linux sans licence), une Monospace821BT-Roman inconnue au bataillon et une UNWXZK+Windings-Regular, du monde de microsoft à nouveau. Sur toutes ces polices, seule la Wingdings est embarquée dans le document. Pour toutes les autres, le moteur de rendu a du trouver la police sur le système, ou procéder à une substitution, plus ou moins heureuse…<br>
Arial BoldMT et ArialMT sont chez moi remplacées par Liberation Sans Bold et DejaVu Sans. Le remplacement de ArialMT par DejaVu Sans n'est pas le plus heureux qu'il soit, c'est incohérent avec Arial BoldMT, mais admettons.<br>
Les Calibri sont remplacés par DejaVu Sans. C'est la même famille (sans-serif, les hors la loi se font plaisir), mais par contre il n'y a pas de garantie de compatibilité de chasse, donc le résultat risque d'être médiocre.<br>
La dernière, Monospace821BT-Roman, est remplacée par DejaVu Sans. Et là, ça pique. Monospace821BT est une police à chasse fixe (tous les caractères ont la même taille). DejaVu Sans ne l'est pas du tout. Et c'est fort probablement cette police qui est utilisée pour l'adresse, ce qui explique l'apparence affreuse de l'adresse sur le document.</p>
<p>Quand on ouvre le document avec mupdf, on observe que l'adresse n'est pas très belle, les caractères semblent déformés, ce qui est particulièrement visible sur le I de MACHIN qui est extrêmement large. Mais le document est globalement plus propre qu'avec poppler.</p>
<p>Avec pdf.js et pdfium par contre (donc Firefox et Chromium), le résultat est correct. Et pdf.js faisant du rendu sur un document HTML, on peut utiliser l'inspecteur de Firefox pour regarder ce qui est fait ! On observe que l'adresse utilise une police à chasse fixe (font-family monospace), ce qui permet un rendu tout à fait correct du texte.</p>
<p>Pour achever ce tour du point de vue de l'utilisateur, regardons ce que fait la concurrence propriétaire, Acrobat Reader (dans une VM sous Windows 10, avec uniquement les polices de base). L'adresse apparait comme sur mupdf, avec des espacement entre caratères assez désagréable, mais elle est lisible, et le caractère I n'est pas sur-gras. Dans les propriétés du document, la police Monospace821BT-Roman a été remplacée par Adobe Sans MM, erreur similaire à Poppler.</p>
<h2 id="toc-2-plus-technique-le-contenu-du-pdf">2) Plus technique, le contenu du PDF…</h2>
<p>Un PDF est un flux constitué d'une succession d'objets. Cela permet notamment des fonctionnalités très pratiques en cas de contraintes de place ou de bande passante : si vous avez un fichier de 1 GB (avec plein d'images ou de vidéos par exemple), nul besoin de lire tout le document pour afficher la première page (si le PDF est bien construit).<br>
Pour regarder le contenu d'un PDF, le meilleur outil que je connaisse est podofobrowser (<a href="https://github.com/KubaO/podofobrowser">https://github.com/KubaO/podofobrowser</a>).<br>
La vue par défaut est arborescente, ce qui sera plus pratique pour nous.<br>
L'élément de départ est le catalogue, un dictionnaire. Ce qui nous intéresse dedans est la liste des polices de caractères. Pour cela, on va se promener dans Pages, qui contient une liste de 1 élément (normal, il n'y a qu'une page). L'objet Page (position 5 0 dans le document) est un dictionnaire, qui contient des Resources, dont les polices utilisées.<br>
On y retrouve bien nos 6 polices de caractères, et la cinquième est la Monospace821BT-Roman. Dans les propriétés listées ici, on observe des informations sur la chasse des caractères (commune à chaque caractère, <code>602</code>), des flags (valeur 32), un type (Type1)… Rien de transcendant pour nous pour le moment.<br>
Mais que contient ce flags ? Le code source de mupdf est assez clair là dessus :</p>
<pre><code class="c"><span class="k">enum</span>
<span class="p">{</span>
<span class="n">PDF_FD_FIXED_PITCH</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">0</span><span class="p">,</span>
<span class="n">PDF_FD_SERIF</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">1</span><span class="p">,</span>
<span class="n">PDF_FD_SYMBOLIC</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">2</span><span class="p">,</span>
<span class="n">PDF_FD_SCRIPT</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">3</span><span class="p">,</span>
<span class="n">PDF_FD_NONSYMBOLIC</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">5</span><span class="p">,</span>
<span class="n">PDF_FD_ITALIC</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">6</span><span class="p">,</span>
<span class="n">PDF_FD_ALL_CAP</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">16</span><span class="p">,</span>
<span class="n">PDF_FD_SMALL_CAP</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">17</span><span class="p">,</span>
<span class="n">PDF_FD_FORCE_BOLD</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">18</span>
<span class="p">};</span></code></pre>
<p>La police est donc juste indiquée comme «non symbolique» (rappel, 2**5 = 32). Et pas comme ayant une chasse fixe. Du coup, forcément, ça complique le remplacement…</p>
<p>Avec podofobrowser, je change donc ce flag pour y mettre la valeur 33, je sauvegarde, je recharge dans okular… et rien, nada, toujours la même police… Bigre… Mais si je change le BaseFont (le nom donc) en «/Monospace», là le remplacement est bien fait et le texte s'affiche correctement. Sur mupdf, le flag est suffisant par contre pour qu'une belle police à chasse fixe soit prise.</p>
<p>Bigre bigre. Il va donc falloir regarder comment fait chaque implémentation libre alors.</p>
<h2 id="toc-3-commençons-avec-mupdf">3) Commençons avec mupdf</h2>
<p>mupdf a un code assez clair, même s'il est très peu commenté. Tout se passe dans <a href="https://github.com/ArtifexSoftware/mupdf/blob/master/source/pdf/pdf-font.c">https://github.com/ArtifexSoftware/mupdf/blob/master/source/pdf/pdf-font.c</a><br>
La fonction qui déclenche tout est <code>pdf_load_font_descriptor</code>. Elle va lire les différents éléments du PDF, puis appeler <code>pdf_load_system_font</code> qui passe la balle à <code>pdf_load_substitute_font</code>. Si la police n'est pas trouvée sur le système, alors <code>pdf_lookup_substitute_font</code> prend le relais, avec les informations sur monospace/serif/gras/italique. Si la police est mono, alors la police Courier est prise, en serif ce sera Times et sinon Helvetica.</p>
<p>mupdf suppose donc que les flags sont correctement renseignés. Sinon, la police de substitution choisie sera nécessairement mauvaise. Pour compenser, le code de dessin utilise la largeur des caractères pour contraindre le rendu de chaque glyphe (fonction <code>fz_adjust_ft_glyph_width</code> appelée par <code>do_ft_render_glyph</code> dans <code>source/fitz/font.c</code>)<br>
C'est donc pour cela que le rendu de mupdf n'est pas parfait, mais reste acceptable : les caractères ne sont pas prévus pour être en chasse fixe, et sont individuellement redimensionnés. D'où le I qui semble bien trop large.</p>
<h2 id="toc-4-que-fait-pdfjs">4) Que fait pdf.js ?</h2>
<p>Nous avons vu que la police n'a pas le bon flag, et pourtant pdf.js choisit bien une police en chasse fixe. Un petit coup d'inspecteur web montre un <code>font-family: monospace</code>.<br>
Donc regardons le code source de pdf.js.</p>
<pre><code class="shell">projects/pdf.js/src $ git grep monospace
core/evaluator.js: // Heuristic: detection of monospace font by checking all non-zero widths
core/evaluator.js: <span class="nb">let</span> <span class="nv">monospace</span> <span class="o">=</span> false<span class="p">;</span>
core/evaluator.js: <span class="nv">monospace</span> <span class="o">=</span> true<span class="p">;</span>
core/evaluator.js: monospace,
core/evaluator.js: <span class="o">(</span>metrics.monospace ? FontFlags.FixedPitch : <span class="m">0</span><span class="o">)</span> <span class="p">|</span>
core/fonts.js: <span class="nv">fallbackName</span> <span class="o">=</span> <span class="s2">"monospace"</span><span class="p">;</span></code></pre>
<p>C'est tentant ce commentaire, regardons… <a href="https://github.com/mozilla/pdf.js/blob/master/src/core/evaluator.js#L3552">https://github.com/mozilla/pdf.js/blob/master/src/core/evaluator.js#L3552</a><br>
Le code vérifie la largeur de chaque caractère de la police. Si toutes les largeurs non-nulles sont égales, alors la police est considérée comme étant à chasse fixe, même en l'absence du flag FixedPitch. Simple et efficace. Le code traduit ensuite ce flag en fallbackName "monospace" qui correspondra à la famille de police en CSS, <a href="https://github.com/mozilla/pdf.js/blob/master/src/core/fonts.js#L871">https://github.com/mozilla/pdf.js/blob/master/src/core/fonts.js#L871</a></p>
<h2 id="toc-5-quid-de-poppler">5) Quid de poppler</h2>
<p>Dans le cas de poppler, la gestion de la substitution est… différente. Tout d'abord, un algorithme similaire à celui de pdf.js est bien présent pour identifier un flag FixedWidth manquant : <a href="https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/GfxFont.cc#L1362">https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/GfxFont.cc#L1362</a>. Le reste de la substitution a lieu dans <a href="https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/FontInfo.cc#L189">https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/FontInfo.cc#L189</a><br>
Si la police n'est pas embarquée dans le PDF, alors il appelle findSystemFontFile, <a href="https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/GlobalParams.cc#L873">https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/GlobalParams.cc#L873</a><br>
Cette fonction va construire une requête fontconfig pour trouver la police qui correspond. Fontconfig est en quelque sorte le gestionnaire de polices : il mantient un cache des polices installées, de leurs propriétés, et est capable de trouver une police en fonction d'un ensemble de critères (type de police, nom, langues supportées…) Ces critères sont regroupés dans ce que Fontconfig appelle un <code>pattern</code>.<br>
En posant dans gdb un breakpoint sur la fonction <code>FcConfigSubstitute</code>, on peut extraire le pattern et l'afficher avec un appel à <code>FcPatternPrint</code>. On obtient alors le pattern suivant:</p>
<pre><code>Pattern has 3 elts (size 16)
family: "Monospace821BT"(s)
spacing: 100(i)(s)
lang: "xx"(s)
</code></pre>
<p>Si on fait cette recherche en ligne de commande, à l'aide de fc-match, on obtient ce résultat :</p>
<pre><code>% fc-match "Monospace821BT:spacing=100:lang=xx" file family
DejaVu Sans:file=/usr/share/fonts/TTF/DejaVuSans.ttf
</code></pre>
<p>Et c'est… inattendu. Le critère <code>spacing: 100</code> correspond pourtant bien à une demande de police à chasse fixe si je comprends bien la signification de la constante dans le code (cf <a href="https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/main/fontconfig/fontconfig.h#L178">https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/main/fontconfig/fontconfig.h#L178</a>) et différentes personnes sur internet. Mais la police donnée ici ne l'est pas du tout.</p>
<p>Si je regarde mon catalogue de police complet :</p>
<pre><code>% fc-list : family spacing | grep "DejaVu Sans"
DejaVu Sans Mono:spacing=100
DejaVu Sans,DejaVu Sans Light
DejaVu Sans
DejaVu Sans,DejaVu Sans Condensed
</code></pre>
<p>Donc l'info de chasse fixe est bien présente, et uniquement sur la police DejaVu Sans Mono.</p>
<p>Bon… ben… on va creuser ce que fait fontconfig, ça a l'air rigolo…</p>
<p>En lisant un peu le code source, je suis tombé sur la sympatique variable d'environnement FC_DEBUG, qui contient un ensemble de flag afin d'activer plus ou moins de messages de debug : <a href="https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/main/src/fcint.h#L99">https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/main/src/fcint.h#L99</a><br>
La subtilité n'étant pas mon truc, je lance donc fc-match avec en variable d'environnement FC_DEBUG=8191. «On sait jamais».</p>
<p>Ça produit un log particulièrement volumineux, mais on aura au moins toutes les informations. Je vous l'ai mis ici à disposition : <a href="https://rock.pinaraf.info/%7Epinaraf/dlfp-pdf/log-fc.txt">https://rock.pinaraf.info/~pinaraf/dlfp-pdf/log-fc.txt</a><br>
Afin de sauter toute la configuration, cherchons où la substitution commence en cherchant notre police, Monospace821BT. La première occurence est à la ligne 46849 (oui, c'est beaucoup de messages de debug). En continuant les itérations, on observe à la ligne 47403 que la famille a été changée en <code>family: "Monospace821BT"(s) "sans-serif"(w)</code>, ce qui est évidemment faux. Ce remplacement a été fait par le fichier de règles <code>/etc/fonts/conf.d/49-sansserif.conf</code> recopié ici:</p>
<pre><code class="xml"><span class="cp"><?xml version="1.0"?></span>
<span class="cp"><!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"></span>
<span class="nt"><fontconfig></span>
<span class="nt"><description></span>Add sans-serif to the family when no generic name<span class="nt"></description></span>
<span class="c"><!--</span>
<span class="c"> If the font still has no generic name, add sans-serif</span>
<span class="c"> --></span>
<span class="nt"><match</span> <span class="na">target=</span><span class="s">"pattern"</span><span class="nt">></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">compare=</span><span class="s">"not_eq"</span><span class="nt">></span>
<span class="nt"><string></span>sans-serif<span class="nt"></string></span>
<span class="nt"></test></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">compare=</span><span class="s">"not_eq"</span><span class="nt">></span>
<span class="nt"><string></span>serif<span class="nt"></string></span>
<span class="nt"></test></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">compare=</span><span class="s">"not_eq"</span><span class="nt">></span>
<span class="nt"><string></span>monospace<span class="nt"></string></span>
<span class="nt"></test></span>
<span class="nt"><edit</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">mode=</span><span class="s">"append_last"</span><span class="nt">></span>
<span class="nt"><string></span>sans-serif<span class="nt"></string></span>
<span class="nt"></edit></span>
<span class="nt"></match></span>
<span class="nt"></fontconfig></span></code></pre>
<p>Si aucune famille générique n'a été trouvée, alors on ajoute la famille sans-serif… J'avoue ne pas comprendre la logique puisque cela ignore complètement le critère <code>spacing: 100</code>.</p>
<p>J'ai donc ajouté le fichier de configuration suivant, dans <code>/etc/fonts/conf.d/48-spacing.conf</code> :</p>
<pre><code class="xml"><span class="cp"><?xml version="1.0"?></span>
<span class="cp"><!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"></span>
<span class="nt"><fontconfig></span>
<span class="nt"><description></span>Add mono to the family when no generic name and spacing is 100<span class="nt"></description></span>
<span class="c"><!--</span>
<span class="c"> If the font has no generic name and spacing 100, add mono</span>
<span class="c"> --></span>
<span class="nt"><match</span> <span class="na">target=</span><span class="s">"pattern"</span><span class="nt">></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">compare=</span><span class="s">"not_eq"</span><span class="nt">></span>
<span class="nt"><string></span>sans-serif<span class="nt"></string></span>
<span class="nt"></test></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">compare=</span><span class="s">"not_eq"</span><span class="nt">></span>
<span class="nt"><string></span>serif<span class="nt"></string></span>
<span class="nt"></test></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">compare=</span><span class="s">"not_eq"</span><span class="nt">></span>
<span class="nt"><string></span>monospace<span class="nt"></string></span>
<span class="nt"></test></span>
<span class="nt"><test</span> <span class="na">qual=</span><span class="s">"all"</span> <span class="na">name=</span><span class="s">"spacing"</span> <span class="na">compare=</span><span class="s">"eq"</span><span class="nt">></span>
<span class="nt"><int></span>100<span class="nt"></int></span>
<span class="nt"></test></span>
<span class="nt"><edit</span> <span class="na">name=</span><span class="s">"family"</span> <span class="na">mode=</span><span class="s">"append_last"</span><span class="nt">></span>
<span class="nt"><string></span>monospace<span class="nt"></string></span>
<span class="nt"></edit></span>
<span class="nt"></match></span>
<span class="nt"></fontconfig></span></code></pre>
<p>Et là, miracle…</p>
<pre><code>% fc-match "Monospace821BT:spacing=100:lang=xx" file family
DejaVu Sans Mono:file=/usr/share/fonts/TTF/DejaVuSansMono.ttf
</code></pre>
<p>Et le PDF s'affiche correctement dans okular !</p>
<p>La merge request a bien sûr été réalisée pour le projet fontconfig, parce que même si c'est pas un bug (ce dont je doute), c'est un comportement piégeux qui a eu même des «grands» logiciels. <a href="https://gitlab.freedesktop.org/fontconfig/fontconfig/-/merge_requests/195">https://gitlab.freedesktop.org/fontconfig/fontconfig/-/merge_requests/195</a></p>
<p>Il restera encore à creuser le code de dessin des caractères de poppler pour qu'il respecte systématiquement la largeur demandée dans la police, mais ça, c'est pour une prochaine fois…</p>
<h2 id="toc-conclusion">Conclusion</h2>
<p>C'est pas parce que le PDF est lisible qu'il faut ignorer un problème. J'avais observé ce bug sur mes premières attestation de carte vitale il y a plus de 10 ans, mais je n'avais jamais pris la peine de l'explorer, en supposant qu'il était lié à ma frilosité sur l'installation de polices. C'est uniquement quand j'ai appris que d'autres avaient le problème que j'ai creusé… J'aurais du creuser il y a 10 ans…</p>
<div><a href="https://linuxfr.org/users/pied/journaux/pdf-mais-que-fait-la-police.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/125192/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/pdf-mais-que-fait-la-police#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/125192/comments.atomtag:linuxfr.org,2005:Diary/398132021-06-15T23:35:07+02:002021-06-15T23:35:07+02:00[HS] Ils étaient troisLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Ce journal est hors sujet et va parler d'aéronautique de loisir, mais bien que je sache à quel point ce sujet peut (et doit) être débattu notamment pour son impact écologique, j'aimerais que cela n'en soit pas la tribune. Merci d'avance pour votre compréhension.</p>
<p>Depuis quelques années maintenant, je suis pilote «du dimanche». J'ai ma licence et une inscription à un aéroclub, sur le terrain de Bondues (LFQO − Lille Marcq), qui me permettent d'aller explorer les paysages des environs, occasionnellement de rendre visite à la lointaine belle-famille…</p>
<p>C'est ce qu'une famille de Béziers a voulu faire. Ils étaient trois, se sont posés à Bondues avant de rejoindre Anvers, leur destination. Lors du décollage, malheureusement, une panne moteur est semble-t-il survenue. L'avion n'avait pas assez d'altitude pour faire demi tour, et s'est écrasé sur un parking d'une zone d'activité et a pris feu. Ils étaient trois.<br>
La couverture médiatique de ce drame m'a surpris (BFM TV, France Info…), mais un axe n'a pas été discuté ou trop peu, et je souhaite donc l'évoquer ici.</p>
<p>Le terrain de Bondues est ancien. Ouvert en 1936, il a servi pendant la seconde guerre mondiale avant de devenir terrain de loisir, marqué des stigmates de la guerre. Ces stigmates sont encore visibles du ciel aujourd'hui d'ailleurs, l'image aérienne de Google et de Bing montre encore les traces des bombardements de la RAF contre la Luftwaffe qui avait occupé le terrain.<br>
Néanmoins, son âge n'augmente pas pour autant le respect de la part des communes avoisinantes. Dans les années 90 déjà, la commune de Bondues décide de bâtir, à 700 mètres de la piste 35, un quartier résidentiel. Forcément, ses habitants hurlent depuis contre le bruit de l'activité aéronautique, mais fort heureusement le quartier ne couvre pas tout l'espace et il est possible de l'éviter au décollage.<br>
En 2007, la commune de Wambrechies, en pleine politique expansionniste, décide d'annexer les champs qui étaient en bout de la piste 25 et d'y installer une zone d'activité. Malin, ce n'est pas résidentiel, donc pas de plaintes pour le bruit… La zone d'activité est à 150 mètres du seuil de la piste 25.</p>
<p>Le bureau d'enquêtes et d'analyse n'a pas encore rendu son verdict. L'enquête prendra son temps, et je n'ai nulle prétention de prédire ce qu'il s'est passé ou d'enquêter à leur place. Néanmoins, je me permets, de vous proposer ce scenario, l'ayant imaginé des centaines de fois dans ma formation et mes vols (cela fait partie des pré-requis avant chaque vol).</p>
<p>− F-AB, je m'aligne et je décolle piste 25 avion<br>
Plein gaz, l'avion accélère. Le badin est actif, le cran de volet est sorti, la vitesse augmente comme prévu. Je tire légèrement sur le manche, les roues quittent le sol.<br>
Je maintiens ma direction, je reste bien dans l'axe de la piste.<br>
Tiens, j'ai l'impression d'accélérer moins vite que d'ha… Le silence. Le moteur vient de s'arrêter. Vu l'altitude, il me reste 5 secondes pour choisir où poser l'avion. Je parcourrai presque 200 mètres dans ce laps de temps.<br>
Je regarde devant, à droite et à gauche (sous 30°, impossible de tourner plus large)… des bâtiments à perte de vue. La voie rapide est trop loin. L'avenue Clément Ader est bien trop étroite et couverte d'arbres. Le cours d'eau est trop loin. Il ne me reste que ce parking qui est bien trop petit et trop loin, mais je n'ai que ça ou des bâtiments.</p>
<p>Au club, pour éviter ce scenario, nous n'avons qu'une seule solution : ne pas rester dans l'axe de la piste et dévier immédiatement vers la gauche, ce qui n'est dans aucune consigne officielle, afin d'être au dessus de la voie rapide. C'est la seule solution pour avoir une chance de s'en sortir. Et cette option n'est disponible que pour les avions, les ULMs ont l'obligation de rester droit puis virer vers la droite, ce qui les force à survoler la ville.</p>
<p>Une commune a décidé de construire une zone d'activités, au mépris du danger pour les usagers de l'aérodrome. L’appât du gain, de l'artificialisation des sols pour laisser une marque dans l'histoire a été le plus fort.</p>
<p>Ils étaient trois. Ils ont affronté le scenario que l'on craint tous. Le parking était trop petit et trop loin.</p>
<p>J'ignore quelles conclusions seront tirées, je n'ai pas le don de voyance. Va-t-on décréter la piste 25 dangereuse et la fermer, ce qui sera le premier clou dans le cercueil et un blanc-seing pour de futurs quartiers au pied des pistes afin de forcer sans débat la fermeture du terrain ? La responsabilité de la commune qui a pris ces décisions sera-t-elle évoquée ?</p>
<p>Je vous remercie pour votre attention.</p>
<div><a href="https://linuxfr.org/users/pied/journaux/hs-ils-etaient-trois.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/124610/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/hs-ils-etaient-trois#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/124610/comments.atomtag:linuxfr.org,2005:News/404132021-04-14T15:49:22+02:002021-04-14T15:49:22+02:00Hotspot, à la recherche du point chaud…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Depuis maintenant quelques semaines, j’ai repris les contributions au projet Calligra, et plus particulièrement au traitement de texte (cf <a href="//linuxfr.org/users/pied/journaux/723-5736-5696-un-mois-de-travail-de-resurrection-d-un-projet-libre">ce journal</a> pour plus d’informations). Du coup, quand sur la liste de courriel des développeurs un <a href="https://mail.kde.org/pipermail/calligra-devel/2021-March/017938.html">comparatif</a> a été envoyé, comparant LibreOffice et Calligra sur le temps de chargement d’un document volumineux (800+ pages, table des matières de 60+ pages), et révélant un sévère désavantage pour Calligra, mon sang ne fit qu’un tour : un facteur 4 dans le temps de chargement n’est pas acceptable, même s’il s’explique par l’absence de travail d’optimisation sur ce point…<br>
Partons donc à la recherche de ces lenteurs, et profitons-en pour parler des méthodes d’analyse des performances d’un programme sous Linux !</p>
</div><ul><li>lien nᵒ 1 : <a title="https://calligra.org/" hreflang="en" href="https://linuxfr.org/redirect/108235">Calligra</a></li><li>lien nᵒ 2 : <a title="https://github.com/KDAB/hotspot/" hreflang="en" href="https://linuxfr.org/redirect/108236">Hotspot (Github)</a></li><li>lien nᵒ 3 : <a title="https://perf.wiki.kernel.org/index.php/Main_Page" hreflang="en" href="https://linuxfr.org/redirect/108237">Linux perf</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-1-dans-des-temps-anciens-callgrind">1) dans des temps anciens, callgrind…</a></li>
<li>
<a href="#toc-2-performance-counters-for-linux-perf-la-r%C3%A9volution">2) Performance Counters for Linux, perf, la révolution…</a><ul>
<li><a href="#toc-2a-petit-exemple">2.a) Petit exemple</a></li>
<li><a href="#toc-2b-perf-top-pour-voir-ce-que-fait-notre-syst%C3%A8me">2.b) perf top pour voir ce que fait notre système</a></li>
<li><a href="#toc-2c-perf-record-pour-analyser-un-programme">2.c) perf record pour analyser un programme</a></li>
</ul>
</li>
<li><a href="#toc-3-hotspot-rendons-%C3%A7a-plus-visuel">3) hotspot, rendons ça plus visuel…</a></li>
</ul>
<h2 id="toc-1-dans-des-temps-anciens-callgrind">1) dans des temps anciens, callgrind…</h2>
<p>Valgrind est un outil bien connu des développeurs C/C++ notamment. Il est connu comme étant un excellent outil pour détecter et analyser les problèmes mémoire, qu’il s’agisse de fuites ou d’utilisation de pointeurs libérés… Mais c’est une vraie collection d’outils, avec également celui qui fut un grand allié dans la recherche des problèmes de performance, callgrind.<br>
Callgrind fonctionne en remplaçant le programme de chargement du binaire par le sien, et en faisant une analyse dynamique du code au fur et à mesure de son exécution. Cette analyse est complète (il ne s’agit pas d’instantanés à une fréquence donnée) mais a un coût colossal : un programme dans callgrind peut facilement être dix fois plus lent qu’en dehors de callgrind. Sur un programme complexe, on procède souvent à une analyse réduite avec un déclenchement retardé de callgrind, puis en l’interrompant quand l’événement que l’on souhaite analyser est passé. L’illustration en est donnée sur <a href="https://web.archive.org/web/20111105130616/http://blog.bepointbe.be/index.php/2008/10/19/30-a-bit-of-plasma-profiling">cet article d’analyse de plasma avec callgrind</a>.</p>
<p>Heureusement, ce temps est désormais révolu et d’autres outils sont disponibles maintenant, bien plus flexibles. Celui que je vais utiliser est désormais un indispensable dans ma boite à outil, qu’il s’agisse d’administration système, de travail de DBA ou de développement : <code>perf</code>.</p>
<h2 id="toc-2-performance-counters-for-linux-perf-la-révolution">2) Performance Counters for Linux, perf, la révolution…</h2>
<p>Bon la révolution ne date pas d’hier (2009 dans le noyau, 2010 dans RHEL, 2011 dans Debian Squeeze…), mais finalement on en a peu parlé sur DLFP, et ce n’est donc pas si connu que ça… Ou l’inverse, je ne sais plus…<br>
<code>perf</code> utilise les compteurs matériel, des points de traçage dans le noyau ou dans les applications afin de collecter des événements. Qu’est-ce qu’un événement, me direz-vous ? Je suis heureux que vous me posiez cette question : la commande <code>perf list</code> en liste plus de 300 chez moi… Ils se répartissent en deux catégories selon l’origine, logicielle ou matérielle. Par exemple chaque changement de tâche par l’ordonnanceur du noyau va être un événement logiciel. Les événements matériels correspondent quant à eux aux données de la PMU (Performance Monitoring Unit), une partie du processeur qui va surveiller des événements au niveau micro-architectural comme le nombre de cycles écoulés, les succès/échecs sur le cache…<br>
Bien évidemment, on ne peut pas enregistrer chaque occurrence de ces événements : à chaque seconde, des milliards de cycles s’écoulent, et traiter ces événements déclencherait à nouveau un nombre conséquent d’événements… Le processeur maintient donc des compteurs d’événements, que le noyau va consulter.<br>
<code>perf</code> est donc la face visible de tout ce travail, et permet donc pendant une période et à une fréquence donnée d’enregistrer et visualiser le nombre d’occurrences de ces événements.</p>
<h3 id="toc-2a-petit-exemple">2.a) Petit exemple</h3>
<p>Prenons un cas simple, <code>echo 'bonjour monde'</code> :</p>
<pre><code>% perf stat /bin/echo 'bonjour monde'
bonjour monde
Performance counter stats for '/bin/echo bonjour monde':
0.42 msec task-clock:u # 0.556 CPUs utilized
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
62 page-faults:u # 0.146 M/sec
205,471 cycles:u # 0.485 GHz
14,061 stalled-cycles-frontend:u # 6.84% frontend cycles idle
45,629 stalled-cycles-backend:u # 22.21% backend cycles idle
219,967 instructions:u # 1.07 insn per cycle
# 0.21 stalled cycles per insn
49,000 branches:u # 115.556 M/sec
<not counted> branch-misses:u (0.00%)
0.000763131 seconds time elapsed
0.000819000 seconds user
0.000000000 seconds sys
</code></pre>
<p>L’exécution de cette commande simple a duré 0,76ms, 205 000 cycles processeurs, 220 000 instructions, avec une consommation de 0,42ms de CPU.</p>
<p>Pour comparaison, avec <code>sleep 1</code> :</p>
<pre><code>% perf stat sleep 1
Performance counter stats for 'sleep 1':
0.45 msec task-clock:u # 0.000 CPUs utilized
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
62 page-faults:u # 0.137 M/sec
249,795 cycles:u # 0.554 GHz
21,583 stalled-cycles-frontend:u # 8.64% frontend cycles idle
60,183 stalled-cycles-backend:u # 24.09% backend cycles idle
221,601 instructions:u # 0.89 insn per cycle
# 0.27 stalled cycles per insn
49,186 branches:u # 109.046 M/sec
<not counted> branch-misses:u (0.00%)
1.001224745 seconds time elapsed
0.000899000 seconds user
0.000000000 seconds sys
</code></pre>
<p>On note que le task-clock reste très faible puisqu’il s’agit du temps processeur consommé, et sleep ne fait pas une attente active où il consommerait inutilement du processeur.</p>
<h3 id="toc-2b-perf-top-pour-voir-ce-que-fait-notre-système">2.b) perf top pour voir ce que fait notre système</h3>
<p>perf top permet d’avoir une vue instantanée de tout le système, noyau inclus. Lors de cette capture, un <code>btrfs scrub</code> est en cours sur deux disques en LUKS.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f696c6c757374726174696f6e732d646c66702f706572662d746f702d73637275622e706e67/perf-top-scrub.png" alt="Perf top avec un btrfs scrub en arrière-plan" title="Source : https://rock.pinaraf.info/~pinaraf/illustrations-dlfp/perf-top-scrub.png"></p>
<p>Vous notez en haut de l’interface que l’événement capturé est 'cycles' (le nombre de cycles processeur consommés), à une fréquence de 4 kHz (4 000 captures par seconde, pour les moins scientifiques d’entre nous). Cela montre le fonctionnement de perf qui réalise un échantillonnage du système.</p>
<p>Dans cette interface, on peut également aller dans chaque fonction et annoter les fonctions pour voir exactement où le CPU est consommé. Quand on ne dispose pas des symboles de debug (mon principal reproche contre Archlinux), le code assembleur sera affiché.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f696c6c757374726174696f6e732d646c66702f706572662d746f702d616e6e6f746174652e706e67/perf-top-annotate.png" alt="Détail du temps passé sur une fonction dans perf top" title="Source : https://rock.pinaraf.info/~pinaraf/illustrations-dlfp/perf-top-annotate.png"></p>
<p>Cette commande est utilisable également sur un serveur en production, et peut donc aider dans des cas sensibles d’analyse de performances : elle n’a pas d’impact sur le système quand il tourne, et permet de creuser très facilement sur les fonctions les plus gourmandes en CPU, qu’il s’agisse de fonctions dans l’espace utilisateur ou dans l’espace noyau. Grâce à cet outil, un collègue et moi-même avons su identifier (puis corriger avec les outils présentés ensuite) un problème de performance sur PostgreSQL où la réplication logique provoquait une forte consommation de CPU en système.</p>
<h3 id="toc-2c-perf-record-pour-analyser-un-programme">2.c) perf record pour analyser un programme</h3>
<p>La commande <code>perf record</code> est, dans le cas qui nous intéresse ici, la plus utile. Elle permet, pour un ensemble de processus donnés (ou l’ensemble du système) d’enregistrer un fichier <code>perf.data</code> qui va contenir un ensemble d’événements. Nous pourrons ensuite utiliser des outils comme <code>perf report</code> pour disposer de la même interface que <code>perf top</code>, mais sur un état figé. Quand on dispose d’un élément reproductible (ici, ouvrir et afficher le fichier OpenDocument-v1.2-part1.odt), on peut facilement modifier l’application pour comparer l’évolution.</p>
<p>Un paramètre assez important à <code>perf record</code> est <code>--call-graph</code> qui permet d’enregistrer les piles d’appel à chaque événement. Ainsi, au lieu de voir que l’on a passé 50 % du temps dans une fonction foo(), on peut découvrir qu’en fait on a passé 45 % du temps dans un appel de foo() par bar(), et 5% du temps dans des appels divers à foo(). Plusieurs valeurs peuvent être passées à --call-graph, pour spécifier la méthode de capture des piles d’appel, avec un choix entre <code>fp</code> (utilisation du 'frame pointer' qui doit être mis par le compilateur), <code>dwarf</code> (utilisation des données de debug du programme) et <code>lbr</code> (utilisation des registres LBR, disponibles uniquement sur les processeurs Intel récents). Dans mon cas, j’utilise dwarf qui m’a donné les meilleurs résultats. Un autre paramètre optionnel est <code>-F</code> pour spécifier la fréquence d’échantillonnage. Sur des périodes de captures courtes, ce paramètre peut être utile pour obtenir des traces plus utilisables.</p>
<p>Lançons ça sur Calligra Words :</p>
<pre><code>% perf record --call-graph dwarf ./words/app/calligrawords ../OpenDocument-v1.2-part1.odt
[… sortie diverse de debug de différents composants de calligra …]
[ perf record: Woken up 2749 times to write data ]
Warning:
Processed 93565 events and lost 21 chunks!
Check IO/CPU overload!
[ perf record: Captured and wrote 691.125 MB perf.data (85798 samples) ]
</code></pre>
<p>691 MB, c’est costaud. D’ailleurs, un certain nombre d’événements n’ont pas pu être capturés. Ajouter les paramètres <code>--aio -z</code> règlent ce souci en compressant la sortie et en faisant des IOs asynchrones.</p>
<p>Avec <code>perf report</code>, on peut explorer l’enregistrement, ce qui donne une interface (et des fonctionnalités) proches de <code>perf top</code> :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f696c6c757374726174696f6e732d646c66702f706572662d7265706f72742e706e67/perf-report.png" alt="Aperçu de l'enregistrement de Calligra dans perf report" title="Source : https://rock.pinaraf.info/~pinaraf/illustrations-dlfp/perf-report.png"></p>
<p>Néanmoins, sur un programme aussi complexe qu’un traitement de texte, cet affichage n’est pas le plus adapté… Heureusement…</p>
<h2 id="toc-3-hotspot-rendons-ça-plus-visuel">3) hotspot, rendons ça plus visuel…</h2>
<p>KDAB est une société de service en logiciels, spécialisée sur Qt, qui contribue au libre : plusieurs de ses salariés sont d’éminents développeurs KDE, elle a envoyé de nombreux patchs sur Qt, ouvert différents outils autour de Qt…<br>
Et l’un de ses derniers nés est hotspot, je cite, « L’interface à Linux perf pour l’analyse de performance », disponible en licence GPL ou commerciale.<br>
L’outil est extrêmement simple à prendre en main : on ouvre dans l’interface le fichier <code>perf.data</code>, et après quelques secondes, l’interface (très complète) apparaît.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f696c6c757374726174696f6e732d646c66702f706572662d686f7473706f742d73756d6d6172792e706e67/perf-hotspot-summary.png" alt="Aperçu de l'enregistrement de Calligra dans hotspot" title="Source : https://rock.pinaraf.info/~pinaraf/illustrations-dlfp/perf-hotspot-summary.png"></p>
<p>La visualisation la plus intéressante est le 'Flame Graph'. Grâce à lui, on peut en quelques instants voir où le temps s’est écoulé, et donc en déduire les endroits à optimiser.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f696c6c757374726174696f6e732d646c66702f706572662d686f7473706f742d666c616d652d6265666f72652e706e67/perf-hotspot-flame-before.png" alt="Flame-graph de l'enregistrement de Calligra dans hotspot" title="Source : https://rock.pinaraf.info/~pinaraf/illustrations-dlfp/perf-hotspot-flame-before.png"></p>
<p>On trouve donc facilement le point chaud, il n'y a « plus qu’à » le corriger… Bon, « le plus qu’à » a pris plusieurs jours, plusieurs patchs, des versions incorrectes, différents atermoiements… Les plus curieux peuvent aller regarder sur <a href="https://invent.kde.org/office/calligra/-/merge_requests/27">cette 'merge-request'</a> pour les correctifs de cette fonction, sachant que d’autres correctifs ont été intégrés entre-temps pour d’autres points 'tièdes' repérés avec hotspot également.<br>
Une fois ce point chaud corrigé, un second point chaud était assez apparent : l’ajout de texte dans un QTextDocument. L’analyse a permis de révéler un algorithme en O(n) lors de l’ajout de textes sur QTextDocument en fonction du nombre de curseurs qu’on maintient sur le document. Or, dans Calligra, chaque annotation et chaque marque-page dans le document est représenté avec un curseur, et il y en a plusieurs milliers. Là aussi, les plus curieux peuvent aller voir <a href="https://bugreports.qt.io/browse/QTBUG-92153">le bug Qt correspondant</a> que j’espère corriger dans les prochains mois… Hé oui, contribuer à un logiciel peut parfois amener à en corriger beaucoup plus…</p>
<p>Pour information, une fois les problèmes corrigés, je suis arrivé à ce niveau de performances :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f726f636b2e70696e617261662e696e666f2f7e70696e617261662f696c6c757374726174696f6e732d646c66702f706572662d686f7473706f742d666c616d652d61667465722e706e67/perf-hotspot-flame-after.png" alt="Flame-graph de l'enregistrement de Calligra dans hotspot après optimisation" title="Source : https://rock.pinaraf.info/~pinaraf/illustrations-dlfp/perf-hotspot-flame-after.png"></p>
<p>On voit que le temps requis pour ouvrir le fichier est passé de 20 secondes à 8 secondes, et hotspot va continuer de m’aider à trouver les endroits restant à optimiser.<br>
Attention tout de même : le principal inconvénient d’un tel outil est sa simplicité d’utilisation. C’est assez « enivrant » et l’on se prend vite au jeu de gratter des cycles CPU à droite et à gauche, quitte à délaisser le développement de fonctionnalités ou la correction de bugs…<br>
J’espère en tout cas que cet article vous permettra de trouver d’autres optimisations à faire sur les programmes que vous développez ou utilisez.</p>
</div><div><a href="https://linuxfr.org/news/hotspot-a-la-recherche-du-point-chaud.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/123924/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/hotspot-a-la-recherche-du-point-chaud#comments">ouvrir dans le navigateur</a>
</p>
PinarafNils Ratusznikpalm123Julien JorgeYsabeau 🧶 🧦https://linuxfr.org/nodes/123924/comments.atomtag:linuxfr.org,2005:Diary/396542021-03-17T22:13:17+01:002021-03-17T22:13:17+01:00723, +5736, -5696… un mois de travail de résurrection d'un projet libre…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-histoire-du-projet">Histoire du projet</a></li>
<li>
<a href="#toc-r%C3%A9surrection">Résurrection</a><ul>
<li><a href="#toc-environnement-de-travail">Environnement de travail</a></li>
<li><a href="#toc-premier-patch">Premier patch…</a></li>
<li><a href="#toc-premi%C3%A8re-compilation">Première compilation</a></li>
<li>
<a href="#toc-corrigeons-des-bugs">Corrigeons des bugs</a><ul>
<li>
<a href="#toc-cas-1-httpsbugskdeorgshow_bugcgiid239200">Cas #1 : </a><a href="https://bugs.kde.org/show_bug.cgi?id=239200">https://bugs.kde.org/show_bug.cgi?id=239200</a>
</li>
<li>
<a href="#toc-cas-2-httpsbugskdeorgshow_bugcgiid406014">Cas #2 : </a><a href="https://bugs.kde.org/show_bug.cgi?id=406014">https://bugs.kde.org/show_bug.cgi?id=406014</a>
</li>
<li>
<a href="#toc-cas-3-httpsbugskdeorgshow_bugcgiid391223">Cas #3 : </a><a href="https://bugs.kde.org/show_bug.cgi?id=391223">https://bugs.kde.org/show_bug.cgi?id=391223</a>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#toc-et-la-suite">Et la suite…</a></li>
</ul>
<p>Bonjour à tous</p>
<p>Le 10 février dernier, pris de nostalgie et d'ennui, je me suis lancé dans un projet un peu fou, probablement inutile et vain (donc indispensable) : faire revivre un projet auquel j'ai beaucoup contribué à une époque mais que j'ai perdu de vue ces dernières années. Le projet en question est complexe et volumineux puisqu'il s'agit de Calligra, la suite bureautique du projet KDE. Ayant été co-mainteneur de Calligra Words, je me focaliserai surtout sur ce dernier.</p>
<h2 id="toc-histoire-du-projet">Histoire du projet</h2>
<p>Calligra est né en 2010 d'un fork de KOffice par la majorité de l'équipe suite à un désaccord interne. Défaut usuel de gouvernance d'un projet libre : difficile d'éjecter une ou deux personnes, donc tout le monde est parti avec un clone du code voir ailleurs si l'herbe y était plus verte, et elle y était. À l'époque, beaucoup de gens contribuaient à Calligra, et pas mal de gens rémunérés par notamment Nokia (par l'intermédiaire de Ko Gmbh) qui voulait une suite bureautique pour afficher des documents sur sa nouvelle plateforme Meego… Si la communication avait été meilleure des deux côtés, j'eus même pu avoir en premier emploi de ma carrière un poste de développeur chez Ko…<br>
Puis vint 2011 et l'Elopcalypse : un gradé de Microsoft prend le contrôle de Nokia, annonce que «même si le N9 rencontre un succès, on ne fera pas de suite» et décide que l'avenir de Nokia est le Windows Phone… C'est dans une ambiance morose que nous faisons fin 2011 le dernier sprint Calligra de la grande époque, invités dans les locaux d'un Nokia à la dérive, avec nos interlocuteurs en mode, je cite, «distribuons l'argent tant qu'on en a encore»…<br>
Les années suivantes sont délicates : pour ma part, pour des raisons personnelles (santé) et professionnelles (travail chronophage), je m'éloigne du projet… les développeurs salariés ne le sont plus, les contributions chutent très vite : de 8000 à 9000 commits par an, on chute à 3000 commits. Seuls deux projets intégrés à la suite vivent encore, Krita et Kexi.<br>
En 2015, ces deux logiciels décident d'aller vivre leurs aventures de leur côté : leurs objectifs sont différents de ceux du reste du projet, et ils ne peuvent se ralentir en ayant à maintenir tout le reste de la suite bureautique. Le passage à Qt5/KF5 est à l'horizon (KF 5.0 est sorti en 2014), ils ne peuvent en assumer le poids à eux seuls.<br>
En 2017, c'est une équipe restreinte qui sort Calligra 3.0, première version Qt5/KF5, privée de certains outils par manque de mainteneurs et de temps, mais qui reste fonctionnelle. Il n'y a plus que 400 commits par an, et la chute continue : 238 en 2019, 194 en 2020…</p>
<p>Bref, le projet est dans le coma, alors essayons de lui donner un choc, on verra si ça reprend.</p>
<h2 id="toc-résurrection">Résurrection</h2>
<h3 id="toc-environnement-de-travail">Environnement de travail</h3>
<p>Bon, Calligra est un projet à base de technologies KDE… donc j'utilise l'environnement de développement KDevelop, qui s'est bien bonifié avec sa version 5. Je clone le projet depuis mon bon vieil alias git kde://calligra et… ha… non, ils sont passés à gitlab… dans un sens je préfère, les anciens outils étaient pas intégrés et ça posait franchement des soucis.</p>
<p>Donc je clone le projet, je l'ouvre dans KDevelop, je demande une compilation et… non, ça marche pas, ninja râle…</p>
<h3 id="toc-premier-patch">Premier patch…</h3>
<p>Pourquoi ninja ? Et pourquoi râle-t-il ? Alors moi je dis pourquoi pas pour ninja, je m'en fous à vrai dire, mais c'est supposé marcher, donc qu'il ne marche pas n'est pas acceptable. Il se plaint d'avoir deux cibles avec le même nom sur le projet. Allons bon…<br>
Ce sera donc le premier patch, très simple : <a href="https://invent.kde.org/office/calligra/-/commit/5866cef575c92803ff6970eaeb608602b9bec5b6">https://invent.kde.org/office/calligra/-/commit/5866cef575c92803ff6970eaeb608602b9bec5b6</a></p>
<h3 id="toc-première-compilation">Première compilation</h3>
<p>Ouch… l'arbre de noël… ça fait des warnings dans tous les sens ! Mais méchamment… D'où ça vient ?<br>
Le port à Qt5/KF5 a été fait, oui. Il est fonctionnel, oui. Mais Qt 5 a gardé des éléments de compatibilité avec Qt 4, et au fil des années des méthodes ont été dépréciées en faveur d'autres. De plus, le langage C++ a évolué, avec C++11 et C++14 des constructions ont été largement simplifiées, des éléments sont recommandés pour fiabiliser le code (le mot clef override par exemple).</p>
<p>Où en est Calligra dans ses pré-requis, pour savoir quoi éliminer ?<br>
<code><br>
set(REQUIRED_KF5_VERSION "5.7.0")<br>
set(REQUIRED_QT_VERSION "5.3.0")<br>
</code>Heu… aïe, comme on dit. Et aucun paramètre passé pour interdire dans le nouveau code l'appel à des méthodes obsolètes…<br>
Qt 5.3 c'est 2014, KF 5.7 c'est 2015… Je doute que quiconque teste le projet sur ces antiquités, et je vois mal comment me restreindre à ce point (il manque au projet une CI actuellement).<br>
Je lance donc la discussion sur la mailing list pour passer à Qt 5.9 ou 5.12 minimum, avec l'équivalent en terme de chronologie pour KF5… et dans la discussion, il apparait que ce choix «peinerait» l'un de nos clients extérieurs. Car Calligra ce n'est pas qu'un ensemble de composants finis, c'est également des briques intégrables, qui peuvent servir à construire un afficheur de documents, notamment sur mobile, comme c'est le cas sur les téléphones Jolla… qui reste en Qt 5.6 par peur de la licence (L)GPLv3…<br>
Bon.<br>
Pour leur faire plaisir, passons, on va se limiter à Qt 5.6 et le KF5 de la même époque, ça devrait éliminer une partie des endroits où les bactéries se déposent, mais ce sera difficile de se limiter à ça très longtemps avec l'approche de Qt 6, qui implique au moins de passer à Qt 5.15 pour limiter la taille du saut derrière.</p>
<p>Du coup, nouveaux patchs, pas trop compliqués…<br>
<a href="https://invent.kde.org/office/calligra/-/commit/a48d9611b77c3d67855f0febec2891def2576c78">https://invent.kde.org/office/calligra/-/commit/a48d9611b77c3d67855f0febec2891def2576c78</a><br>
<a href="https://invent.kde.org/office/calligra/-/commit/afb2049d785e7db374d574e675e8ceaad1b1d606">https://invent.kde.org/office/calligra/-/commit/afb2049d785e7db374d574e675e8ceaad1b1d606</a><br>
<a href="https://invent.kde.org/office/calligra/-/commit/8a6faa4c6e23f5da197a0249a4122afdb81ae597">https://invent.kde.org/office/calligra/-/commit/8a6faa4c6e23f5da197a0249a4122afdb81ae597</a></p>
<p>Ok, mais ça ne calme pas tant que ça l'arbre de Noël.<br>
Il n'y a pas une source de warning, hélas. Pire, des choses dans le code m'inquiètent qui ne font pas de warning, notamment l'utilisation de l'ancienne syntaxe de connexion de Qt, à savoir :</p>
<p>connect(source, SIGNAL(mySignal()), target, SLOT(mySlot()));</p>
<p>Alors que la syntaxe moderne permet de faire contrôler les paramètres par le compilateur, et non pas à l'exécution :</p>
<p>connect(source, &Source::mySignal, target, &Target::mySlot);</p>
<p>Du coup, pour cela, j'ai sorti Clazy : <a href="https://github.com/KDE/clazy/">https://github.com/KDE/clazy/</a><br>
Un outil qui va automatiquement corriger tout un ensemble de bugs, en signaler d'autres que le compilateur ne va pas lever… Parce que j'avais pas encore assez d'avertissements.</p>
<p>Je ne vous liste pas les commits, mais Clazy a généré des milliers de correctifs sur connect, des correctifs de perfs mineurs, signalé des syntaxes obsolètes…</p>
<h3 id="toc-corrigeons-des-bugs">Corrigeons des bugs</h3>
<p>Bon, rendre le projet plus maintenable, c'est une chose, mais il faut aussi s'occuper un peu des bugs qui trainent…</p>
<h4 id="toc-cas-1-httpsbugskdeorgshow_bugcgiid239200">Cas #1 : <a href="https://bugs.kde.org/show_bug.cgi?id=239200">https://bugs.kde.org/show_bug.cgi?id=239200</a>
</h4>
<p>Un bug sur un filtre, pourquoi pas, c'est simple, isolé…<br>
L'analyse a été l'occasion de remettre le pied à l'étrier, et même d'explorer un code que je n'avais pas regardé (à savoir les filtres ms office). Le fichier de test est très simple et permet rapidement de voir le problème (c'est à l'origine un rapport de bug des testeurs de Nokia d'ailleurs).<br>
Bon… je pourrais dire que le correctif était à la hauteur de l'analyse, mais perdu… Avec gdb, je me suis rendu compte que le code dans la fonction read_background ne lisait pas l'arrière plan à cause d'un paramètre displayBackgroundShape qui n'était pas valorisé comme il faut…<br>
Du coup, le correctif…<br>
<a href="https://invent.kde.org/office/calligra/-/merge_requests/11/diffs">https://invent.kde.org/office/calligra/-/merge_requests/11/diffs</a></p>
<h4 id="toc-cas-2-httpsbugskdeorgshow_bugcgiid406014">Cas #2 : <a href="https://bugs.kde.org/show_bug.cgi?id=406014">https://bugs.kde.org/show_bug.cgi?id=406014</a>
</h4>
<p>Un bug sur un autre filtre, cas simple à nouveau, mais avec un peu de windowseries, et la difficulté du format binaire historique de Word…<br>
Donc on reproduit le fonctionnement : trouver le code incriminé, le lire… et en déduire que c'est juste des encodages non gérés… Facile à nouveau…<br>
<a href="https://invent.kde.org/office/calligra/-/commit/be82faae699790e8b5d4f68a2e9e2663ff40477e">https://invent.kde.org/office/calligra/-/commit/be82faae699790e8b5d4f68a2e9e2663ff40477e</a></p>
<h4 id="toc-cas-3-httpsbugskdeorgshow_bugcgiid391223">Cas #3 : <a href="https://bugs.kde.org/show_bug.cgi?id=391223">https://bugs.kde.org/show_bug.cgi?id=391223</a>
</h4>
<p>En 2014, un plugin Okular a été ajouté dans Calligra pour donner à Okular un affichage bien plus complet pour les fichiers OpenDocument. Manque de bol, une fuite mémoire assassine les performances.<br>
Je m'apprête à une recherche compliquée, sortir valgrind… Je teste le plugin Okular, et effectivement, ça fuit. Enfin. Non. Ça explose façon challenger.<br>
J'ouvre le code pour une première imprégnation, et j'en déduis que ce code n'a jamais été relu ni testé sur plus que «ça affiche = ça marche»…</p>
<p><a href="https://invent.kde.org/office/calligra/-/commit/3e3b602aff077e189d2d38826f38fcc8a9e1e486">https://invent.kde.org/office/calligra/-/commit/3e3b602aff077e189d2d38826f38fcc8a9e1e486</a></p>
<h2 id="toc-et-la-suite">Et la suite…</h2>
<p>J'ai pas fait que ça ces dernières semaines sur Calligra. J'ai aussi travaillé sur des trucs qui m'ont concerné, comme <a href="https://invent.kde.org/office/calligra/-/commit/94c7e8fdc7fec0a51cfb2ebe88595712fc7e67e5">https://invent.kde.org/office/calligra/-/commit/94c7e8fdc7fec0a51cfb2ebe88595712fc7e67e5</a> qui corrige un vieil ennemi personnel qui est revenu me hanter lorsque j'ai essayé un document Word que je n'arrivais pas à lire dans LibreOffice et qui marche parfaitement dans Calligra, na. Également, un peu de refactoring, l'introduction de nouveaux tests unitaires qui remplacent des fichiers de test perdus il y a plus de 15 ans…<br>
J'espère continuer comme ça les prochaines semaines. Déjà parce que ça m'amuse, mais aussi parce que j'espère qu'en forçant de l'activité sur le projet je pourrai redonner à des gens envie de venir contribuer. C'est loin d'être difficile. Le code est bien découpé, assez bien organisé, avec plein de choses à faire qui ne sont pas nécessairement compliquées.<br>
Il faut que j'arrive à faire marcher une CI digne de ce nom, pour m'assurer que les prochains commits n'introduisent pas de régression et pour faciliter l'arrivée de nouveaux développeurs.<br>
Puis ça reste une suite bureautique largement moins lourde que LibreOffice. Sur mobile, notamment Plasma Mobile, ça me semble bien adapté… Et je me dis qu'on pourrait aussi en faire quelque chose couplé à Nextcloud notamment…</p>
<div><a href="https://linuxfr.org/users/pied/journaux/723-5736-5696-un-mois-de-travail-de-resurrection-d-un-projet-libre.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/123623/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/723-5736-5696-un-mois-de-travail-de-resurrection-d-un-projet-libre#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/123623/comments.atomtag:linuxfr.org,2005:Diary/389152020-01-28T00:33:36+01:002020-01-28T07:42:04+01:00The Qt Company annonce un changement dans ses « offres »Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Salutations !</p>
<p>Cela faisait quelques jours que le sujet commençait à pointer son ombre, notamment avec les rappels de KDE concernant l’accord FreeQt… Et ça y est, The Qt Company, qui a repris le développement de Qt après Digia, a annoncé un changement majeur dans l’« offre » de Qt.<br>
Tout d’abord, un rappel : on ne parle en aucun cas d’un changement de la licence de Qt. Cela reste du commercial (contre $, on en reparlera) ou du Libre (LGPL et GPL, selon les modules).<br>
L’objectif de The Qt Company est de faire venir dans le droit chemin de la licence commerciale les entreprises qui exploitent la version libre assez souvent sans respecter l’ensemble des contraintes qui y sont liées. En effet, il est (était ?) très aisé d’obtenir la version libre de Qt, développer et distribuer voire embarquer une application, sans jamais vraiment respecter les contraintes, notamment sur l’obligation de permettre de changer la version de Qt (simple application de la LGPL).</p>
<p>Pour cela, trois changements ont été annoncés :</p>
<ol>
<li><p>un compte Qt sera obligatoire pour télécharger les binaires<br>
L’idée étant probablement pour eux de pouvoir repérer ainsi une entreprise faisant de nombreux développements avec Qt sans payer de licence. Dans le cas d’un utilisateur libriste… mouais, mon Qt vient de chez Debian, l’installeur me permet de tester différentes versions, mais j’ai déjà un compte Qt… c’est chiant, mais pas dramatique.</p></li>
<li><p>une nouvelle offre pour les start‑ups et petites entreprises<br>
À cette échelle, on ne parle plus de TPE que de PME hélas : à seulement 499 USD par an, pour une entreprise de moins de 5 salariés, maximum 100 000 USD de revenus annuels… C’est très très très restreint. Et il semble y avoir un loup concernant les règles de distribution, à creuser pour ceux que cela intéresse. Mais encore une fois, pour un libriste, c’est pas très chiant, et ils ont annoncé que c’est sujet à évolution.</p></li>
<li><p>l’installeur hors ligne et les versions LTS deviennent limitées à la licence commerciale<br>
Là, je ne sais qu’en penser. L’installeur hors ligne, ça va faire chier des gens, je suppose. Obligation d’avoir un accès Internet, même quand on monte une VM… Pénible et chiant, mais acceptable.<br>
Par contre, la limitation de la branche LTS, là, on est sur du bien chiant. Qu’est‑ce que cela signifie pour les distributions LTS ? Les patchs seront‑ils sur des branches Git privées ? <em>Quid</em> des mises à jour de sécurité ?</p></li>
</ol>
<p>Il convient d’être prudent et rationnel. Cela ne met nullement en danger KDE (qui a toujours l’arme de l’accord FreeQt pour passer Qt en licence BSD si The Qt Company ferme trop Qt). Mais, à mon sens, c’est un signal assez négatif pour les développeurs occasionnels qui préfèreront fuir Qt plutôt que risquer d’entrer dans ce monde. Au final, l’impact sur l’image risque d’être fort, il suffit de voir les commentaires sur <em>Hacker News</em>, <em>LWN</em> ou ailleurs. J’espère juste que cela n’aboutira pas à plus d’applications en JS/HTML au détriment de vraies applications natives…</p>
<div><a href="https://linuxfr.org/users/pied/journaux/the-qt-company-annonce-un-changement-dans-ses-offres.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/119266/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/the-qt-company-annonce-un-changement-dans-ses-offres#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/119266/comments.atomtag:linuxfr.org,2005:Diary/388492019-12-21T17:29:25+01:002019-12-21T17:29:25+01:00 RaspberryPi, capteurs USB, dbus et systemd, utiliser des briques Linux "desktop" pour une architectLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-1-larchitecture-monolithique-pr%C3%A9c%C3%A9dente">1) L'architecture monolithique précédente</a></li>
<li>
<a href="#toc-2-la-nouvelle-architecture">2) La nouvelle architecture</a><ul>
<li><a href="#toc-a-systemd-pour-lancer-un-process-par-carte">a) systemd pour lancer un process par carte</a></li>
<li><a href="#toc-b-dbus-pour-relier-les-processus-entre-eux">b) dbus pour relier les processus entre eux</a></li>
</ul>
</li>
<li><a href="#toc-3-exposer-un-capteur-sur-dbus">3) Exposer un capteur sur DBus</a></li>
<li><a href="#toc-4-communiquer-avec-nos-capteurs">4) Communiquer avec nos capteurs</a></li>
<li><a href="#toc-5-comparaison-rapide">5) Comparaison rapide</a></li>
</ul>
<p>Bonjour tout le monde</p>
<p>Mon activité professionnelle quotidienne m'ayant éloigné du développement (je suis devenu DBA parce que le développement logiciel en entreprise me paraissait de plus en plus ridicule, mais libre à vous de me convaincre du contraire), je travaille en auto-entrepreneur sur les projets intéressants que l'on pourrait me présenter. Depuis quelques temps, je travaille sur un système embarqué, où une carte centrale (Raspberry Pi hélas, faute de mieux sur le plan prix/fiabilité d'apprivisionnement notamment) avec une interface graphique en C++/Qt qui communique avec un ensemble de capteurs basés sur des cartes Nucleo reliés en USB.<br>
J'ai récemment fait une grosse altération de l'architecture de cette application : codé initialement sous forme d'un unique processus, il y a désormais un ensemble de processus communiquant entre eux par DBus. Bien que le code ne soit pas libre (mais promis aucune licence libre n'a été maltraitée dans l'opération), comme ce système exploite différents composants standards de nos systèmes, parfois dans des façons qui sont peu connues ou assez mal documentées, je me suis dit qu'il serait intéressant de rédiger ici un journal expliquant cette architecture, les avantages et inconvénients que l'on peut y voir.</p>
<h2 id="toc-1-larchitecture-monolithique-précédente">1) L'architecture monolithique précédente</h2>
<p>La version précédente du logiciel était constituée d'un seul processus (bon, à l'exception des processus de la saleté de moteur Web qu'il faut sortir pour certains affichage), utilisant les fonctionnalités de signaux/slots de Qt pour faire de l'asynchrone et gérer l'ensemble du matériel en un seul thread. L'ensemble fonctionnait plutôt bien jusqu'à ce qu'il se prenne la brique de la réalité dans la face : le matériel ne fonctionne pas toujours aussi bien qu'on l'espère. En l'occurence, les capteurs reliés en USB se sont révélés dans certains cas instables, avec des communications qui finissent par échouer, voire qui plantent le contrôleur USB de la Pi. L'ajout de la gestion du branchement à chaud du matériel est devenu nécessaire et "relativement" facile (il suffit après tout de surveiller /dev, ou de parler sur le line netlink d'udev, mais c'est plus compliqué), mais la coupure en pleine communication est bien plus compliquée à gérer.</p>
<p>Par ailleurs, d'autres phénomènes rigolos apparaissent : malgré le mécanisme de signal/slot de Qt, le code reste plus simple en synchrone. Et en cas de communication intense, si on ne découpe pas massivement le code, on se retrouve avec des gels de l'interface (j'espère sincèrement que le système de coroutines du C++20 éliminera ces problématiques). De plus, quand on utilise des bibliothèques externes, on ne gère pas vraiment la durée des traitements.</p>
<p>La solution classique dans un tel cas est l'utilisation de threads. Avec les threads, un même espace mémoire est utilisé par plusieurs fils d'exécution… mais si un fil d'exécution plante, il entraîne généralement avec lui l'ensemble du processus. De plus, si on utilise des bibliothèques tierces, sont-elles bien compatibles avec l'usage dans plusieurs threads ? Enfin, une telle évolution est contraignante sur notre propre code (même si Qt sait lever une partie des problèmes liés aux threads en permettant plus facilement d'isoler les objets au sein de chaque thread en les faisant communiquer par signaux/slots).</p>
<p>Du coup, il m'est venu l'idée d'une architecture en plusieurs processus…</p>
<h2 id="toc-2-la-nouvelle-architecture">2) La nouvelle architecture</h2>
<h3 id="toc-a-systemd-pour-lancer-un-process-par-carte">a) systemd pour lancer un process par carte</h3>
<p>On veut qu'un processus soit lancé par carte USB branchée. Le processus doit être tué quand le capteur est débranché. Il doit être démarré au boot par contre…<br>
Je sais que cela déplaira à certains, mais une excellente solution pour ça est systemd. Ne s'agit-il pas quasiment d'un cas d'usage mis en avant lors de sa conception initiale ?<br>
Pour cela, il y a deux étapes. Tout d'abord, il faut identifier avec udev le matériel et le marquer de sorte que systemd lance le service. Cela se fait avec un fichier de règle d'une ligne, à déposer dans <code>/etc/udev/rules.d/</code> :</p>
<p><code>KERNEL=="ttyACM[0-9]+", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", TAG+="systemd" ENV{SYSTEMD_ALIAS}+="/sys/class/tty/%k" ENV{SYSTEMD_WANTS}="captor@%k.service"</code></p>
<p>Ces règles udev vont déclencher différentes actions au niveau de systemd.</p>
<p>Essayons déjà cette partie.</p>
<p>Branchons, avant d'avoir défini la règle udev, un périphérique nucleo. Il apparait sous /dev/ttyACM0. Demandons à systemd ce qu'il pense de ce périphérique :</p>
<pre><code>root@peanuts2:~# systemctl status sys-class-tty-ttyACM0.device
● sys-class-tty-ttyACM0.device - /sys/class/tty/ttyACM0
Loaded: loaded
Active: inactive (dead)
</code></pre>
<p>Le périphérique n'est pas reconnu.<br>
Ajoutons maintenant la règle udev, rechargeons udev (systemctl reload udev) et rebranchons le périphérique :</p>
<pre><code>root@peanuts2:~# systemctl status sys-class-tty-ttyACM0.device
● sys-class-tty-ttyACM0.device - ST-LINK/V2.1
Loaded: loaded
Active: active (plugged) since Tue 2019-12-17 12:01:19 CET; 21min ago
Device: /sys/devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.2/tty/ttyACM0
</code></pre>
<p>Il est désormais reconnu par systemd, pour être pris en compte dans les calculs des dépendances.<br>
Il ne nous reste donc plus qu'à ajouter le fichier <a href="mailto:captor@.service">captor@.service</a> pour qu'il instancie proprement nos services au branchement du périphérique. J'installe pour cela le fichier suivant dans <code>/lib/systemd/system</code> :</p>
<pre><code>[Service]
ExecStart=/usr/bin/nucleo-captor %i
User=pi
[Unit]
BindsTo=sys-class-tty-%i.device
After=sys-class-tty-%i.device
</code></pre>
<p>Et il suffit ensuite de faire un <code>systemctl daemon-reload</code> pour que notre service soit démarré et arrêté au branchement du périphérique… Ce fut simple, non ?</p>
<h3 id="toc-b-dbus-pour-relier-les-processus-entre-eux">b) dbus pour relier les processus entre eux</h3>
<p>Bon. Le problème de dbus c'est le manque de main d'œuvre pour maintenir une documentation complète à jour, pour améliorer l'outillage dans l'ensemble des projets qui fournissent des bindings… Du coup ce qui suit contient des éléments que je n'ai trouvé que dans le code source du démon ou des projets l'utilisant, et pas dans la documentation. quoi…</p>
<p>Premier élément, le bus… C'est là où se connectent les différents processus pour communiquer entre eux. Par défaut sous Linux, avec un utilisateur connecté, il y a deux bus, le bus système et un bus propre à chaque session. Dans notre cas, nous allons utiliser le bus système, démarré par systemd au boot. Par contre, par contre… Ce bus est restreint. Il serait malvenu qu'un processus malveillant puisse s'y inscrire et fournisse de fausses réponses à d'autres éléments du système, n'est-ce-pas ? Il y a donc pour cela des fichiers de configuration listant les accès autorisés au bus système pour chaque utilisateur…<br>
Ci dessous, le fichier que j'utilise pour mon système embarqué, dans <code>/etc/dbus-1/system.d/fr.corp.conf</code> :</p>
<pre><code class="xml"><span class="cp"><!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"</span>
<span class="cp"> "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"></span>
<span class="nt"><busconfig></span>
<span class="c"><!-- ../system.conf have denied everything, so we just punch some holes --></span>
<span class="nt"><policy</span> <span class="na">user=</span><span class="s">"pi"</span><span class="nt">></span>
<span class="nt"><allow</span> <span class="na">own_prefix=</span><span class="s">"fr.corp"</span><span class="nt">/></span>
<span class="nt"><allow</span> <span class="na">send_destination=</span><span class="s">"fr.corp.core"</span><span class="nt">/></span>
<span class="nt"><allow</span> <span class="na">send_interface=</span><span class="s">"fr.corp.CaptorInterface"</span><span class="nt">/></span>
<span class="nt"><allow</span> <span class="na">send_interface=</span><span class="s">"org.freedesktop.DBus.ObjectManager"</span><span class="nt">/></span>
<span class="nt"><allow</span> <span class="na">send_interface=</span><span class="s">"org.freedesktop.DBus.Properties"</span><span class="nt">/></span>
<span class="nt"><allow</span> <span class="na">send_interface=</span><span class="s">"org.freedesktop.DBus.Introspectable"</span><span class="nt">/></span>
<span class="nt"></policy></span>
<span class="nt"></busconfig></span></code></pre>
<p>Beaucoup de allow (ne vous endormez pas, un sleepy allow finit toujours mal) dans ce fichier… Donc, on s'en doute en le lisant, DBus a été conçu dans les années 2000 (pourquoi utiliser du XML sinon…)<br>
La politique de droits décrite dans ce fichier s'applique à l'utilisateur pi (dans un prochain épisode, on parlera peut-être du passage à buildroot, mais là nous sommes encore en phase de développement, ne nous dispersons pas). Elle autorise l'utilisateur pi à:<br>
- déclarer sur le bus des services dont le nom commence par fr.corp.<br>
- envoyer des messages à fr.corp.core (on en parlera après, il s'agit de l'appli qui fournit l'interface graphique)<br>
- envoyer des messages sur l'interface des capteurs, nommée fr.corp.CaptorInterface (on en parle juste après), et à quelques interfaces système fort utiles pour explorer le bus.</p>
<h2 id="toc-3-exposer-un-capteur-sur-dbus">3) Exposer un capteur sur DBus</h2>
<p>Pour exposer un service sur DBus, comme mentionné précédemment, il faut qu'il expose une interface. Dans la grande tradition des systèmes de communication, DBus définit un langage pour décrire les interfaces. Évidemment, années 2000 obligent, c'est en XML.<br>
Décrivons donc notre capteur. On va le faire un peu évolué pour que l'exemple soit complet:</p>
<pre><code class="xml"><span class="cp"><!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"</span>
<span class="cp"> "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"></span>
<span class="nt"><node</span> <span class="na">name=</span><span class="s">"/captor"</span><span class="nt">></span>
<span class="nt"><interface</span> <span class="na">name=</span><span class="s">"fr.corp.CaptorInterface"</span><span class="nt">></span>
<span class="nt"><method</span> <span class="na">name=</span><span class="s">"readValue"</span><span class="nt">></span>
<span class="nt"><arg</span> <span class="na">name=</span><span class="s">"result"</span> <span class="na">type=</span><span class="s">"i"</span> <span class="na">direction=</span><span class="s">"out"</span><span class="nt">/></span>
<span class="nt"></method></span>
<span class="nt"><method</span> <span class="na">name=</span><span class="s">"lightUp"</span><span class="nt">/></span>
<span class="nt"><method</span> <span class="na">name=</span><span class="s">"lightDown"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"serial"</span> <span class="na">type=</span><span class="s">"s"</span> <span class="na">access=</span><span class="s">"read"</span><span class="nt">/></span>
<span class="nt"><signal</span> <span class="na">name=</span><span class="s">"overValue"</span><span class="nt">/></span>
<span class="nt"><signal</span> <span class="na">name=</span><span class="s">"buttonPressed"</span><span class="nt">></span>
<span class="nt"><arg</span> <span class="na">name=</span><span class="s">"duration"</span> <span class="na">type=</span><span class="s">"i"</span> <span class="na">direction=</span><span class="s">"out"</span><span class="nt">/></span>
<span class="nt"></signal></span>
<span class="nt"></interface></span>
<span class="nt"></node></span></code></pre>
<p>Rien de très compliqué. Notre service sera exposé à l'adresse /captor sous l'interface CaptorInterface. Il aura une méthode readValue, avec un retour de type entier, deux méthodes lightUp et lightDown, un numéro de série exposé sous forme de propriété en lecture seule, un signal overValue pour exposer une valeur au dessus d'un seuil de mesure, et un signal pour indiquer qu'un bouton a été pressé pendant x secondes.</p>
<p>Du coup, derrière, comment on implémente ça ? Tout dépend du langage et des bibliothèques que vous utilisez.<br>
Dans mon cas, en C++/Qt, je déclare DBUS_ADAPTORS += captor.xml et j'obtiens un objet me permettant d'exposer correctement mon service tout en respectant ce contrat. Je suis pas entièrement fan de ce système, il a ses arguments mais je le trouve trop laxiste et donc dans un sens risqué, mais de sérieux tests suffisent à valider le bon fonctionnement tout de même.<br>
Après avoir déclaré cet «adaptor», il suffit dans le code de l'utiliser, ce qui donne en C++ ceci :</p>
<pre><code class="cpp"> <span class="c1">// Ce devName vient de systemd du coup, allez lire plus haut si vous avez oublié</span>
<span class="k">auto</span> <span class="n">devName</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="n">positionalArguments</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
<span class="k">auto</span> <span class="n">device</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Device</span><span class="p">(</span><span class="n">devName</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">adaptor</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CaptorInterfaceAdaptor</span><span class="p">(</span><span class="n">captor</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">QDBusConnection</span><span class="o">::</span><span class="n">systemBus</span><span class="p">().</span><span class="n">registerService</span><span class="p">(</span><span class="n">QString</span><span class="p">(</span><span class="s">"fr.corp.captor.%1"</span><span class="p">).</span><span class="n">arg</span><span class="p">(</span><span class="n">devName</span><span class="p">)))</span>
<span class="n">qFatal</span><span class="p">(</span><span class="s">"Failed to register service on system bus"</span><span class="p">);</span>
<span class="n">QDBusConnection</span><span class="o">::</span><span class="n">systemBus</span><span class="p">().</span><span class="n">registerObject</span><span class="p">(</span><span class="s">"/captor"</span><span class="p">,</span> <span class="n">device</span><span class="p">);</span></code></pre>
<p>Notez que l'utilisation d'un qFatal permet d'avoir une sortie en erreur et donc systemd qui sait que quelque chose de mal s'est produit… Vous monitorez bien la sortie de <code>systemctl list-units --failed</code>, n'est-ce-pas ?<br>
Ce journal devenant bien long, je laisse en exercice aux lecteurs l'implémentation d'un capteur en se conformant à cette interface. Notez que, et c'est fort pratique, le vocable DBus se prête bien au vocable de Qt et ses signaux, slots et propriétés…</p>
<h2 id="toc-4-communiquer-avec-nos-capteurs">4) Communiquer avec nos capteurs</h2>
<p>Deux éléments importent ici : savoir lister, dynamiquement, les capteurs, et savoir parler avec eux.<br>
Commençons par les lister. Le bus nous notifie de l'arrivée de congénères, c'est parfait. Il existe des classes dans Qt pour écouter ces événements, donc il suffit de se brancher sur l'une d'elles : serviceOwnerChanged. Ok, j'explique…<br>
Lorsqu'un service arrive, il arrive sans propriétaires ni données, et change ensuite d'owner. Nous attendons donc tout simplement ce changement pour capturer l'information.<br>
Pour simplifier, voici ce à quoi ressemble un tel code :</p>
<pre><code class="cpp"> <span class="k">auto</span> <span class="n">bus</span> <span class="o">=</span> <span class="n">QDBusConnection</span><span class="o">::</span><span class="n">systemBus</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">bus</span><span class="p">.</span><span class="n">isConnected</span><span class="p">())</span>
<span class="n">qFatal</span><span class="p">(</span><span class="s">"BAD"</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">iface</span> <span class="o">=</span> <span class="n">bus</span><span class="p">.</span><span class="n">interface</span><span class="p">();</span>
<span class="c1">// Connection is queued to make sure DBus will be available and working when processing the following events.</span>
<span class="c1">// This way, we don't have to push queues everywhere and limit the unsafety here.</span>
<span class="n">connect</span><span class="p">(</span><span class="n">iface</span><span class="p">,</span> <span class="o">&</span><span class="n">QDBusConnectionInterface</span><span class="o">::</span><span class="n">serviceOwnerChanged</span><span class="p">,</span> <span class="p">[]</span> <span class="p">(</span><span class="k">const</span> <span class="n">QString</span> <span class="o">&</span><span class="n">service</span><span class="p">,</span> <span class="k">const</span> <span class="n">QString</span> <span class="o">&</span><span class="n">oldOwner</span><span class="p">,</span> <span class="k">const</span> <span class="n">QString</span> <span class="o">&</span><span class="n">newOwner</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">service</span><span class="p">.</span><span class="n">startsWith</span><span class="p">(</span><span class="s">"fr.corp.captor."</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">newOwner</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// This is goodbye !</span>
<span class="n">emit</span><span class="p">(</span><span class="n">deviceRemoved</span><span class="p">(</span><span class="n">service</span><span class="p">));</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">oldOwner</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">())</span> <span class="p">{</span>
<span class="n">emit</span><span class="p">(</span><span class="n">deviceAdded</span><span class="p">(</span><span class="n">service</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span></code></pre>
<p>Très simple, non ?</p>
<p>Quant à l'utilisation…<br>
J'ai dans mon projet l'équivalent «utilisateur» de DBUS_ADAPTORS, à savoir DBUS_INTERFACES. Cela me génère une classe complète qui encapsule en C++/Qt toute l'interface définie dans mon service DBus.<br>
Du coup… quand mon énumérateur m'envoie un signale deviceAdded avec un nom de service, il me suffit de faire ceci:</p>
<pre><code class="cpp"> <span class="k">auto</span> <span class="n">device</span> <span class="o">=</span> <span class="k">new</span> <span class="n">fr</span><span class="o">::</span><span class="n">corp</span><span class="o">::</span><span class="n">CaptorInterface</span><span class="p">(</span><span class="n">service</span><span class="p">,</span> <span class="s">"/captor"</span><span class="p">,</span> <span class="n">QDBusConnection</span><span class="o">::</span><span class="n">systemBus</span><span class="p">());</span></code></pre>
<p>Et j'ai alors un objet device répondant intégralement au cahier des charges précédent !</p>
<pre><code class="cpp"><span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="n">device</span><span class="o">-></span><span class="n">serial</span><span class="p">();</span>
<span class="n">device</span><span class="o">-></span><span class="n">lightUp</span><span class="p">();</span>
<span class="n">device</span><span class="o">-></span><span class="n">lightDown</span><span class="p">();</span></code></pre>
<p>etc, etc…<br>
Bien sûr, grand défaut : il est tentant d'appeler de manière synchrone ces fonctions. C'est un piège, dont on se sort heureusement facilement :</p>
<pre><code class="cpp"><span class="n">QDBusPendingReply</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">valueReply</span> <span class="o">=</span> <span class="n">device</span><span class="o">-></span><span class="n">readValue</span><span class="p">();</span>
<span class="k">auto</span> <span class="n">valueWatcher</span> <span class="o">=</span> <span class="k">new</span> <span class="n">QDBusPendingCallWatcher</span><span class="p">(</span><span class="n">valueReply</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span></code></pre>
<p>Et l'on attend le signal QDBusPendingCallWatcher::finished…</p>
<h2 id="toc-5-comparaison-rapide">5) Comparaison rapide</h2>
<p>L'introduction de cette modification a profondément changé l'organisation de mon projet. Il m'a fallu déplacer beaucoup de code de part et d'autre, mais j'ai pu :<br>
- me débarasser de la gestion du matériel, gestion assez compliquée mine de rien s'agissant d'USB sur une Pi (je ne sais toujours pas comment, avec du code tournant en espace utilisateur, j'ai bien pu planter la puce USB de la Pi… mais c'était horrible à essayer de debugger, pour sûr)<br>
- réduire le risque de plantage visible de mon application en cas de soucis dans la communication USB<br>
- (m')imposer de bien être en asynchrone pour les communications avec les périphériques, sans pour autant avoir à écrire une gestion asynchrone des écritures sur le port USB<br>
- ouvrir la voie à une réécriture de morceaux de l'application dans d'autres langages si je le souhaite, tant que l'on respecte le 'protocole' (Python, Rust, Go, le choix est libre)<br>
- proposer d'avoir des centaines de capteurs centralisés sur un système, en passant les services DBus par le réseau (j'en parlerai si j'en venais à mettre ceci en production, pour le moment mon prototype a fonctionné, pour mon plus grand plaisir, mais je n'ai pas encore réfléchi à comment le rendre propre)<br>
- et le plus amusant, j'ai pu coder en quelques lignes une interface graphique me permettant de faire un capteur virtuel, sous forme d'une application graphique…</p>
<p>À l'avenir, je pourrais commencer à tester les pires scenarios en simulant dans un langage de script des capteurs et tester l'interaction de l'application avec ces derniers, ou à l'inverse tester le bon comportement de mes capteurs sans sortir tout le code de l'application.<br>
Et ces derniers points m'ont déjà servi à moultes reprises en libérant tout simplement mon bureau d'une quantité de cables et périphériques…</p>
<p>Merci à tous pour votre patience et pour avoir lu ce pavé.</p>
<p>Bonnes fêtes de fin d'année à tous !</p>
<div><a href="https://linuxfr.org/users/pied/journaux/raspberrypi-capteurs-usb-dbus-et-systemd-utiliser-des-briques-linux-desktop-pour-une-architect.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/118966/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/raspberrypi-capteurs-usb-dbus-et-systemd-utiliser-des-briques-linux-desktop-pour-une-architect#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/118966/comments.atomtag:linuxfr.org,2005:Diary/388112019-11-30T10:37:56+01:002019-11-30T10:37:56+01:00QtWebEngine sous raspbian, la croix et la galère…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-lenvironnement-h%C3%B4te">L'environnement hôte</a></li>
<li><a href="#toc-parce-que-cest-pas-encore-assez-fun-les-pilotes-graphiques-de-raspberry-pi">Parce que c'est pas encore assez fun, les pilotes graphiques de Raspberry Pi</a></li>
<li><a href="#toc-compilation-de-qt">Compilation de Qt…</a></li>
<li><a href="#toc-bande-de-zapot%C3%A8ques">Bande de zapotèques !</a></li>
<li><a href="#toc-suppl%C3%A9ment-de-mensonges">Supplément de mensonges</a></li>
</ul>
<p>Salut à tous</p>
<p>Après avoir passé… «plusieurs heures» pour obtenir un QtWebEngine qui fonctionne sous raspbian, je me suis dit qu'il serait peut-être intéressant de partager l'expérience ici. À la fois pour le côté scientifique, documentaire de la chose, mais aussi tout simplement pour vider mon sac, parce qu'on en a gros. Puis ça fait longtemps que j'ai pas fait de journal, ça me manque…</p>
<p>Tout d'abord, petite observation : sur les dépôts raspbian, on trouve beaucoup de paquets… normal, c'est un dérivé de debian me direz-vous ? Non non, même des vieilleries genre Qt 3 sont encore là, sorti 8 ans avant la première Raspberry Pi…<br>
Mais par contre des fois y'a des trous. Genre quand on veut afficher une page web, la techno officielle de Qt est WebEngine, dérivé de Chromium (suite à la direction prise par Apple dans Webkit à l'époque de la version '2.0'). Et là, ben on n'a pas grand chose : on a la doc, les données, mais pas la lib.<br>
Du coup, petite histoire de cross-compilation (compiler blink sur une raspberry pi me semble être une très mauvaise idée) et de souffrance, accrochez vous.</p>
<h2 id="toc-lenvironnement-hôte">L'environnement hôte</h2>
<p>Alors, j'utilise une debian pour cela. Attention : il faut une debian inférieure ou égale à la version raspbian ciblée pour utiliser le compilateur packagé par Debian. Je m'explique. Quand on va compiler sur mettons une debian sid, même si on réalise une cross-compilation vers raspbian, certains composants vont venir du compilateur lui-même, et donc être liées à la version de la libc de debian.</p>
<p>Démonstration:</p>
<pre><code>snoopy@peanuts2:/tmp$ cat math.cpp
#include <cmath>
int test(int a, int b) {
return pow(a, b);
}
snoopy@peanuts2:/tmp$ arm-linux-gnueabihf-g++ math.cpp -shared -o math.so
snoopy@peanuts2:/tmp$ arm-linux-gnueabihf-nm math.so | grep pow
U pow@@GLIBC_2.29
</code></pre>
<p>Donc déjà vous avez perdu 5 points de santé mentale en ayant configuré toute votre machine pour la cross-compilation, tout installé, vérifié et tout… pour au final vous rendre compte que votre binaire ne pourra pas marcher. (Évidemment je m'en suis rendu compte avec mon Qt complet, pas avec un exemple d'une ligne)</p>
<p>Du coup, on reprend, l'environnement hôte… Il suffit d'installer les paquets <code>gcc-arm-linux-gnueabihf</code> et <code>g++-arm-linux-gnueabihf</code> et votre debian buster devrait normalement être à même de produire des binaires compatibles avec raspbian buster.<br>
Ensuite, deuxième étape : il vous faut une raspbian sur votre debian. Pas exécutable (encore que, avec qemu… bref), mais juste l'arborescence pour avoir les fichiers d'en-tête, les bibliothèques dans les bonnes versions…<br>
Du coup, il faut préparer une raspbian. Je vous invite à suivre <a href="https://wiki.qt.io/RaspberryPi2EGLFS">https://wiki.qt.io/RaspberryPi2EGLFS</a> mais pas trop. Jusqu'à l'étape 9, tout va presque bien…</p>
<h2 id="toc-parce-que-cest-pas-encore-assez-fun-les-pilotes-graphiques-de-raspberry-pi">Parce que c'est pas encore assez fun, les pilotes graphiques de Raspberry Pi</h2>
<p>La Pi <4 dispose, pour sa puce graphique VC4, de deux pilotes: le pilote libre vc4, intégré au noyau, et le pilote propriétaire brcm. N'utilisez pas le pilote propriétaire pour cet usage. Il impose un vieux GCC qui n'est pas compatible avec les webengine récents… (j'ai pleuré du sang sur cette blague là)<br>
Du coup, on suit pas exactement le tuto. Dans raspi-config, dans le choix du pilote GL, il faut utiliser le «OpenGL desktop driver» (à vous le choix fake KMS vs full KMS, ça dépend de votre écran et de comment il est connecté… 3 points de santé mentale en moins)<br>
Ensuite seulement, on peut reprendre la documentation de Qt pour obtenir un sysroot viable.</p>
<h2 id="toc-compilation-de-qt">Compilation de Qt…</h2>
<p>Je vous invite à prendre directement les archives complètes plutôt que les modules individuels depuis git. C'est bien plus simple, surtout quand ça inclut un monstre comme WebEngine (au moins 30 minutes de git clone chez moi). Du coup je suis parti de <a href="https://download.qt.io/official_releases/qt/5.12/5.12.6/single/qt-everywhere-src-5.12.6.tar.xz.mirrorlist">https://download.qt.io/official_releases/qt/5.12/5.12.6/single/qt-everywhere-src-5.12.6.tar.xz.mirrorlist</a></p>
<p>Tout d'abord, le configure…</p>
<pre><code>./configure -release -opengl es2 -device linux-rasp-pi3-vc4-g++ -device-option CROSS_COMPILE=/usr/bin/arm-linux-gnueabihf- -sysroot /opt/raspi/sysroot.vc4 -opensource -confirm-license -make libs -prefix /usr/local/qt5pi -extprefix /opt/qt5pi/5.12.6/build -hostprefix /opt/qt5pi/5.12.6/tools -no-xcb -no-use-gold-linker -v -nomake examples -nomake tests -libinput -libudev -webengine-proprietary-codecs
</code></pre>
<p>Ce sont mes options, libre à vous d'en changer évidemment. Décomposons…</p>
<p>-release : pas de debug (sinon c'est beaucoup plus lourd)<br>
-device linux-rasp-pi3-vc4-g++ -device-option CROSS_COMPILE=/usr/bin/arm-linux-gnueabihf- -sysroot /opt/raspi/sysroot.vc4 : on vise une raspberry pi 3, pilote vc4, et on utilise le compilateur de notre debian<br>
-opensource -confirm-license : on est sur la version libre, et oui on a lu la licence<br>
-prefix /usr/local/qt5pi -extprefix /opt/qt5pi/5.12.6/build -hostprefix /opt/qt5pi/5.12.6/tools : on installera dans /usr/local/qt5pi sur la Pi, et dans /opt/qt5pi/5.12.6 sur la machine hôte (j'ai pris là où j'avais de la place)<br>
-no-xcb : je vise de l'embarqué, donc pas de X11<br>
-nomake examples -nomake tests : on va économiser quelques minutes de compilation quand même<br>
-no-use-gold-linker : parce que j'ai pleuré sans (encore des points de santé mentale en moins)<br>
-libinput -libudev -webengine-proprietary-codecs : des options dont j'ai besoin pour mon usage</p>
<p>Et on compile tout ça, avec un <code>make -j 42</code> (remplacez 42 par le nombre de CPU×2+âge du capitaine-5)… on va prendre moultes thés, cafés, cachets d'opium… et on revient : hoo, il a compilé !<br>
Tout fièrement, on fait <code>make install</code>… on regarde… Hooo, il n'a pas compilé QtWebEngine, le petit cachotier…</p>
<h2 id="toc-bande-de-zapotèques">Bande de zapotèques !</h2>
<p>Nous arrivons à la blague ultime. Vraiment balaise.</p>
<p>Tout d'abord, le configure de Qt n'est pas très précis et ne dit pas clairement que non non, le QtWebEngine ne passe pas.<br>
Et ce qui m'a le plus bloqué et rendu fou n'est pas lié à Qt mais à WebEngine. Pour compiler blink en ciblant de l'ARM 32 bits, si on est sur une machine x86 64 bits, il <em>faut</em> avoir le nécessaire pour compiler du x86 32 bits.</p>
<p>Oui oui. Pour compiler pour l'architecture A, si on est sur une architecture B, il faut aussi pouvoir compiler pour l'architecture C. J'étais fou de rage sur celle là.<br>
Ha oui, il utilise aussi son propre système de build lancé depuis make. Parce que c'est plus fun. Et puis des fois il se plante et veut voir les libs sur le système hôte au lieu de chercher dans le système cible.</p>
<p>Du coup, j'ai installé les paquets suivants sur mon hôte pour que ça passe:<br>
<code>apt install ninja-build lib32stdc++-8-dev libnss3-dev</code></p>
<p>Et là, après environ 4 heures de compilation, là c'est passé.</p>
<h2 id="toc-supplément-de-mensonges">Supplément de mensonges</h2>
<p>Y'a une petite blague dans ce que j'ai dit au dessus, que je placerais à 10 sur l'échelle de la puputerie : <em>il utilise aussi son propre système de build lancé depuis make</em>.<br>
Ils ont choisi d'utiliser ninja… Soit, ok, si vous voulez. Mais ninja est vachement fort, il peut lancer autant de jobs en parallèle qu'il y a de CPU… Bonne idée direz-vous ?<br>
J'ai fait un OOM avant d'aller patcher les Makefile pour forcer ninja à utiliser moins de taches de compilation en parallèle. Blink est un monstre à compiler, les fichiers demandent rapidement des GB de RAM pour compiler. Donc un conseil : activez de la swap, ou utilisez une machine avec moulte espace (j'ai que 16GB sur mon PC perso, et pas mal d'outils qui tournent déjà et réduisent à moins de 10GB la RAM dispo)</p>
<p>Merci à vous d'avoir lu mes heures de souffrance. Vous pouvez maintenant faire un navigateur embarqué sur votre Pi, sans utiliser X11, directement sur le framebuffer. Bon, je vous conseille quand même de faire les interfaces en QML, c'est quand même largement plus performant vu que c'est fait pour (y'a quand même des boites qui développent des moteurs HTML conçus exprès pour ce genre d'usage, genre <a href="https://www.ekioh.com/flow-browser/">https://www.ekioh.com/flow-browser/</a> mais c'est pas libre et y'a même pas une démo téléchargeable)</p>
<div><a href="https://linuxfr.org/users/pied/journaux/qtwebengine-sous-raspbian-la-croix-et-la-galere.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/118779/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/qtwebengine-sous-raspbian-la-croix-et-la-galere#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/118779/comments.atomtag:linuxfr.org,2005:Diary/385942019-07-07T10:26:56+02:002019-07-07T10:26:56+02:00Aujourd'hui, je euggubed un programme dans GDB...Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-0-le-reverse-debug-mais-kesako">0) le reverse-debug, mais kesako ?</a></li>
<li><a href="#toc-1-la-joie-du-dentiste--limpact-entre-le-mur-de-la-r%C3%A9alit%C3%A9-et-tes-dents">1) La joie du dentiste : l'impact entre le mur de la réalité et tes dents</a></li>
<li><a href="#toc-2-le-choix-dynamique-dimpl%C3%A9mentation">2) Le choix dynamique d'implémentation</a></li>
<li><a href="#toc-3-jouer">3) Jouer…</a></li>
</ul>
<p>Bonjour bonjour !</p>
<p>En ce moment, pour beaucoup de fun, je tente de bidouiller une grammaire générée avec flex et bison, en mode un peu "boite noire" (interdiction de modifier la grammaire d'origine, et à vrai dire je sais même pas quelle est la tronche exacte du fichier source, je joue avec libpg_query pour ceux que ça intéresse).<br>
Mais quand on tombe sur une erreur, la backtrace est fort peu instructive :</p>
<pre><code>#0 base_yyerror (base_yylloc=0x7fffffffc124, msg=0x5555555f5104 "syntax error", yyscanner=0x0) at src/postgres/src_backend_parser_gram.c:44051
#1 base_yyparse (yyscanner=yyscanner@entry=0x55555584b3a8) at src/postgres/src_backend_parser_gram.c:43856
#2 0x00005555555727bc in raw_parser (str=str@entry=0x5555555a6008 "SELECT 1, ") at src/postgres/src_backend_parser_parser.c:61
</code></pre>
<p>Toutes les erreurs sont émises depuis la même ligne de base_yyparse, et il n'y a pas de sous-fonctions : cette fonction fait 18000 lignes…<br>
En effet, bison génère du goto en masse, et qui dit goto dit pas de frame dans la stack. Qui dit pas de frame dans la stack dit pas d'entrée dans la backtrace. Qui dit pas d'entrée dans la backtrace dit pas content.<br>
Alors pour plus de fun, on va sortir une petite perle introduite il y a deux ans de cela dans GDB : le reverse-debug.</p>
<h2 id="toc-0-le-reverse-debug-mais-kesako">0) le reverse-debug, mais kesako ?</h2>
<p>Comme montré dans le cas d'une grammaire générée par notre ami m. bison, qui n'est pas si méchant que ça, poser des breakpoints et réaliser du debug à base de backtrace a des limites. On aimerait bien disposer de plus dans notre debuggueur que d'un bouton pause et de boutons ligne/function/instruction suivante.<br>
On a donc vu apparaitre ces dernières années le reverse debugging, mis en avant notamment par mozilla et son projet rr.<br>
Le problème, c'est que le matériel ne le permet pas. Il est impossible pour le processeur d'enregistrer toutes ses variations d'état, ainsi que toutes les variations d'état de la RAM. La solution mise en place dans GDB est donc simple : si le processeur ne sait pas faire, faisons-le à sa place. Donc gdb émule un processeur dans un tel cas, ce qui évidemment se ressent sur les performances…</p>
<p>Assez parlé, petit exemple:</p>
<pre><code>#include <stdio.h>
void boo() {
printf("PERDU\n");
}
int main (int argc, char ** argv) {
if (argc != 2)
return -1;
int v = atoi(argv[1]);
if (v < 42)
goto lose;
if (v > 42)
goto lose;
return 0;
lose:
boo();
return -1;
}
</code></pre>
<p>On compile et on va debugger ça…</p>
<pre><code>gcc -g test-rev.c -o test-rev
gdb ./test-rev
</code></pre>
<p>On s'intéresse à la fonction boo et on veut savoir pourquoi elle a été appelée…</p>
<pre><code>> b boo
> r 73
> bt
#0 boo () at test-rev.c:4
#1 0x00005555555551b0 in main (argc=2, argv=0x7fffffffe088) at test-rev.c:19
</code></pre>
<p>Comme dans mon cas avec bison, ce n'est pas très très pratique : impossible de savoir quelle branche de if a bien pu déclencher le goto, à moins bien sûr de poser des breakpoints partout (ou de réfléchir, vu la taille du programme c'est pas bien dur, mais c'est pas le but de l'exercice).</p>
<pre><code>(gdb) b main
Breakpoint 1 at 0x1167: file test-rev.c, line 8.
(gdb) b boo
Breakpoint 2 at 0x1149: file test-rev.c, line 4.
(gdb) r 73
Starting program: /tmp/test-rev 73
Breakpoint 1, main (argc=2, argv=0x7fffffffe088) at test-rev.c:8
8 if (argc != 2)
(gdb) target record-full
(gdb) c
Continuing.
Breakpoint 2, boo () at test-rev.c:4
4 printf("PERDU\n");
(gdb) rs
main (argc=2, argv=0x7fffffffe088) at test-rev.c:19
19 boo();
(gdb)
15 goto lose;
</code></pre>
<p>Et voilà ! Nous avons pris le goto de la ligne 15, à savoir donc v > 42.<br>
Nous avons pour cela activé le mode reverse (target record-full) et effecturé un reverse-step pour remonter dans le temps.<br>
Simple, non ?</p>
<p>Du coup, on peut retourner à notre grammaire…</p>
<h2 id="toc-1-la-joie-du-dentiste--limpact-entre-le-mur-de-la-réalité-et-tes-dents">1) La joie du dentiste : l'impact entre le mur de la réalité et tes dents</h2>
<p>Alors, faisons simple…</p>
<pre><code>(gdb) b base_yyparse
Breakpoint 1 at 0x27d30: file src/postgres/src_backend_parser_gram.c, line 26257.
(gdb) b base_yyerror
Breakpoint 2 at 0x3f6e0: base_yyerror. (2 locations)
(gdb) r
Starting program: /home/pierre/projects/pglast/libpg_query/examples/simple
Breakpoint 1, base_yyparse (yyscanner=yyscanner@entry=0x5555556863a8) at src/postgres/src_backend_parser_gram.c:26257
26257 yytype_int16 *yyss = yyssa;
(gdb) target record-full
(gdb) set variable base_yydebug = 1
(gdb) c
Continuing.
Starting parse
Process record does not support instruction 0xc5 at address 0x7ffff7f26db2.
</code></pre>
<p>Ha.<br>
C'est pas vraiment ce à quoi je m'attendais…<br>
Que s'est-il passé ici ? Pourquoi notre exemple simple marchait et que dans la vraie vie on se prend un mur ?<br>
Pour pouvoir implémenter un enregistrement complet des traces d'exécution pour pouvoir remonter dans le temps, il est nécessaire d'avoir au sein de GDB un émulateur pour le processeur. Mais ce dernier n'est pas complet : il ne gère pas notamment les instructions AVX des processeurs modernes.<br>
Et c'est bien ce qu'il a rencontré ici :</p>
<pre><code> 0x00007ffff7f26db2 <+2>: vmovd %esi,%xmm0
</code></pre>
<p>Il s'agit d'une instruction de l'implémentation en AVX2 de la fonction strhrnul. Comme de nombreuses fonctions de manipulation de chaines de caractères, elles ont été écrites en plusieurs versions, optimisée selon les processeurs.</p>
<h2 id="toc-2-le-choix-dynamique-dimplémentation">2) Le choix dynamique d'implémentation</h2>
<p>Vous souvenez-vous de l'époque où nous avions des paquets libc optimisés selon le type de processeur ? Ce n'est plus le cas sur nos distributions, comment se fait-il qu'un paquet générique soit optimisé ainsi ?<br>
Au démarrage d'un binaire au format ELF, un interpréteur est lancé qui va récupérer les différents binaires, les mettre en place en RAM, et mettre les bons appels de fonctions aux bons endroits. C'est cet interpréteur qui va appeler la méthode cpuid pour obtenir du processeur les différentes fonctionnalités qu'il supporte et donc choisir les versions les plus optimales des fonctions dont il dispose.<br>
Il faut donc que nous fassions comprendre à ce trublion que non, même si notre processeur dispose des derniers raffinements en vigueur, il ne faut pas les activer. Pour cela… quoi de plus simple que de le patcher, directement ? (ouais, c'est bourrin mais j'ai vraiment pas plus simple)<br>
L'interpréteur ELF par défaut sur amd64 est /lib64/ld-linux-x86-64.so.2:</p>
<pre><code>$ file /bin/bash
/bin/bash: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=ffe165dc81a64aea2b05beda07aeda8ad71f1e7c, stripped
</code></pre>
<p>Il est "préférable" de ne pas toucher directement à ce fichier (sans blague), aussi allons-nous procéder sur une copie du fichier.</p>
<p>Le code à patcher initial est:</p>
<pre><code>__cpuid (0, cpu_features->max_cpuid, ebx, ecx, edx);
</code></pre>
<p>(libc, sysdeps/x86/cpu-features.c)</p>
<p>Cela correspond à mettre 0 dans le registre EAX, puis appeler la fonction cpuid.</p>
<p>Nous devons donc trouver dans ld-linux.so l'assembleur suivant:</p>
<pre><code>31 c0 xor eax, eax // Le moyen le plus court pour mettre 0 dans EAX
0f a2 cpuid
</code></pre>
<p>Si on cherche dans le binaire, on ne trouve pas cette séquence. Il est en effet possible d'avoir quelques instructions intermédiaires ou de padding, selon le compilateur et le sens du vent…<br>
Du coup, il faut chercher avec éventuellement quelques octets entre ces deux instructions.</p>
<p>Faisons simple : le one-liner Perl suivant devrait résoudre le problème :)</p>
<pre><code>perl -pe 's/\x{31}\x{c0}.{0,32}\K\x{0f}\x{a2}/\x{66}\x{90}/' < /lib64/ld-linux-x86-64.so.2 > ld-linux-x86-64.so.2.nocpu
</code></pre>
<p>On génère donc un nouveau fichier ld-linux.so qui ne va pas aller méchamment lire les infos du CPU pour tenter d'être intelligent. non mais…</p>
<p>Il suffit maintenant d'appeler patchelf pour que notre exécutable utilise notre interpréteur patché.</p>
<pre><code>patchelf --set-interpreter `readlink -f ld-linux-x86-64.so.2.nocpu` ./examples/simple
</code></pre>
<p>Et voilà !</p>
<h2 id="toc-3-jouer">3) Jouer…</h2>
<p>Je peux maintenant faire du reverse-debug dans ma grammaire, et donc retracer "aisément" les variations dans la machine à états.</p>
<pre><code>44051 parser_yyerror(msg);
(gdb) rs
26461 if (yyn == 0)
(gdb) rs
26460 yyn = yydefact[yystate];
(gdb) rs
26424 if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
(gdb) p yyn
$1 = 28679
</code></pre>
<p>Le reste sera entre moi et mon psy.</p>
<p>Bonne fin de week-end à tous !</p>
<div><a href="https://linuxfr.org/users/pied/journaux/aujourd-hui-je-euggubed-un-programme-dans-gdb.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117634/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/aujourd-hui-je-euggubed-un-programme-dans-gdb#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/117634/comments.atomtag:linuxfr.org,2005:Diary/382812018-12-16T22:38:42+01:002018-12-16T22:38:42+01:00Les ricains nous ont tout chouravé…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Salut les moules</p>
<p>Cette fois, la situation va mal. Mon employeur a été racheté il y a quelques mois de cela par une entreprise américaine… Ce sont des choses qui arrivent. L'entreprise en question nous a tous invités à aller sur place pour que nous découvrions leur culture.<br>
Et voici ce que j'ai trouvé dans le sac à matériel promotionnel qu'ils nous ont donné :</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f686f73742e70696e617261662e696e666f2f7e70696e617261662f706f756472652d7665727465322e6a7067/poudre-verte2.jpg" alt="Poudre verte !" title="Source : http://host.pinaraf.info/~pinaraf/poudre-verte2.jpg"></p>
<p>Ils nous ont piqué la <a href="http://poudreverte.org">poudre verte</a> !<br>
C'est pour ça qu'elle est en rupture de stock depuis si longtemps, ils ont tout pris…</p>
<p>Salauds !</p>
<div><a href="https://linuxfr.org/users/pied/journaux/les-ricains-nous-ont-tout-chourave.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/115999/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/les-ricains-nous-ont-tout-chourave#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/115999/comments.atomtag:linuxfr.org,2005:Diary/380102018-07-05T12:48:24+02:002018-07-05T12:48:24+02:00Directive sur le droit d'auteur : le parlement européen reprend les choses en mainLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Salut tout le monde</p>
<p>Depuis quelques semaines, beaucoup d'inquiétude apparaît autour de la directive sur le droit d'auteur en cours d'élaboration au niveau européen. Deux articles en particulier, le 11 et le 13, introduisent une notion de censure automatique obligatoire pour les hébergeurs de contenu envoyé par les utilisateurs, et une forme de taxe sur les liens.<br>
Aujourd'hui avait lieu un vote crucial à ce sujet au parlement européen : l'approbation du mandat de négociations de la commission des affaires juridiques.</p>
<p>Le vote a eu lieu et le résultat est un grand soulagement pour tout le monde : le mandat n'a pas été approuvé (313 contre 278, avec 31 abstentions), et la directive sera donc discutée par le parlement européen en septembre, avec donc la possibilité pour nos députés européens d'amender le texte.</p>
<p>Ouf.</p>
<div><a href="https://linuxfr.org/users/pied/journaux/directive-sur-le-droit-d-auteur-le-parlement-europeen-reprend-les-choses-en-main.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/114841/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/pied/journaux/directive-sur-le-droit-d-auteur-le-parlement-europeen-reprend-les-choses-en-main#comments">ouvrir dans le navigateur</a>
</p>
Pinarafhttps://linuxfr.org/nodes/114841/comments.atom