Nouveau

FreeRTOS Thread Debugging with Eclipse and OpenOCD

Si vous utilisez FreeRTOS et que votre IDE est Eclipse et que vous vous connectez à votre carte grâce à OpenOCD, ceci devrait vous intéresser !

MCU on Eclipse

FreeRTOS is probably the number one RTOS used, and Eclipse is likely the most popular IDE I can think of. But debugging FreeRTOS applications with Eclipse and GDB is somewhat limited? What I would like to get at the minimum is this: ability to see all the different threads in the Eclipse debug view like this:

FreeRTOS Threads in Eclipse with OpenOCD FreeRTOS Threads in Eclipse with OpenOCD

As you might guess from that screenshot: this post is about how to make FreeRTOS tread debugging possible with Eclipse and GDB :-).

View original post 594 mots de plus

Publicités

Formater du texte en C++ avec {fmt}

Formater du texte en C++, c’est pas ultra fun… Il y a bien les « bons » vieux reliquats du C avec les fonctions genre std::printf() et les méthodes un peu plus modernes comme std::ostringstream. C’est pas génial comparer à string.format() de Python En regardant le code de spdlog( une bibliothèque de logging qui semble très bien mais qui malheureusement n’était pas compatible avec mes contraintes), j’ai découvert la bibliothèque {fmt}. C’est elle qui sert de back-end à spdlog pour formater les messages. Si vous voulez essayer {fmt}, voici le mode opératoire !

Builder la bibliothèque

Tout le code est sur GitHub, est placé sous licence BSD et se builde avec CMake. Le mode opératoire est donc très classique :

$ git clone https://github.com/fmtlib/fmt.git
$ cd fmt
p$ mkdir build
$ cd build
$ cmake ..
[...]
$ make all
Scanning dependencies of target fmt
[  2%] Building CXX object CMakeFiles/fmt.dir/src/format.cc.o
[  4%] Building CXX object CMakeFiles/fmt.dir/src/posix.cc.o
[  7%] Linking CXX static library libfmt.a
[  7%] Built target fmt
Scanning dependencies of target gmock
[  9%] Building CXX object test/CMakeFiles/gmock.dir/gmock-gtest-all.cc.o
[ 12%] Linking CXX static library libgmock.a
[ 12%] Built target gmock
Scanning dependencies of target test-main
[ 14%] Building CXX object test/CMakeFiles/test-main.dir/test-main.cc.o
[ 17%] Building CXX object test/CMakeFiles/test-main.dir/gtest-extra.cc.o
[ 19%] Building CXX object test/CMakeFiles/test-main.dir/util.cc.o
[ 21%] Linking CXX static library libtest-main.a
[ 21%] Built target test-main
Scanning dependencies of target time-test
[ 24%] Building CXX object test/CMakeFiles/time-test.dir/time-test.cc.o
[ 26%] Linking CXX executable ../bin/time-test
[...]
[100%] Linking CXX executable ../bin/util-test
[100%] Built target util-test

$ make test
Running tests...
Test project /home/pgradot/Documents/GitHub/fmt/build
      Start  1: assert-test
 1/11 Test  #1: assert-test ......................   Passed    0.02 sec
      Start  2: gtest-extra-test
 2/11 Test  #2: gtest-extra-test .................   Passed    0.03 sec
      Start  3: format-test
[...]
      Start 10: posix-mock-test
10/11 Test #10: posix-mock-test ..................   Passed    0.20 sec
      Start 11: posix-test
11/11 Test #11: posix-test .......................   Passed    4.85 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =   5.84 sec


$ sudo make install
[  7%] Built target fmt
[...]
[100%] Built target format-impl-test
Install the project...
-- Install configuration: "Release"
-- Installing: /usr/local/lib/cmake/fmt/fmt-config.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-config-version.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-targets.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-targets-release.cmake
-- Installing: /usr/local/lib/libfmt.a
-- Installing: /usr/local/include/fmt/core.h
-- Installing: /usr/local/include/fmt/format.h
-- Installing: /usr/local/include/fmt/format-inl.h
-- Installing: /usr/local/include/fmt/locale.h
-- Installing: /usr/local/include/fmt/ostream.h
-- Installing: /usr/local/include/fmt/printf.h
-- Installing: /usr/local/include/fmt/time.h
-- Installing: /usr/local/include/fmt/posix.h

J’ai volontairement raccourci la sortie de plusieurs commandes (vous avez sans doute vu les […]). Vous n’êtes pas obligé de faire make test mais c’est sympa de vérifier que notre bibliothèque s’est correctement compilée. On voit que l’installation a copié dans des dossiers classiques la bibliothèque statique libfmt.a ainsi que les nombreux headers dont nous aurons besoin pour l’utiliser dans notre code. Vous pourriez ne pas l’installer et récupérer les fichiers pour les mettre dans votre projet ou encore utiliser directement le CMake de {fmt} comme un sous-CMake de votre projet.

Petits essais

J’ai repris quelques lignes données dans le README du GitHub et j’ai écrit une petite fonction log() puisque c’est un peu pour ça que je me suis intéressé à {fmt} :

#include 
#include 
#include 

namespace pgt
{
template 
void log(const char* format, Args&& ... args)
{
	try
	{
		auto now = std::time(nullptr);
		auto timestamp = std::string(std::ctime(&now));
		std::replace(timestamp.begin(), timestamp.end(), '\n', '\0');

		auto message = fmt::format(format, args...);
		fmt::print("[{}] {}\n", timestamp, message);
	}
	catch(const fmt::format_error& e)
	{
		fmt::print("Invalid log message: {}\n", e.what());
	}
}
}

int main()
{
	// Exemples de GitHub
	fmt::print("Hello, {}!\n", "world");
	fmt::printf("Hello, %s!\n", "world");
	std::string s = fmt::format("{0}{1}{0}\n", "abra", "cad");
	fmt::print(s);

	// Log
	int speed = 1200;
	pgt::log("Speed = {} rpm - Temperature = {}°C", speed, 42);
	pgt::log("Setpoint = {}");
}

On compile et on lance le programme :

$ g++ -Wall -Wextra -std=c++11 main.cpp -lfmt && ./a.out
Hello, world!
Hello, world!
abracadabra
[Fri May 11 11:59:13 2018] Speed = 1200 rpm - Temperature = 42°C
Invalid log message: argument index out of range

Notes :

  1. C’est pas une super bonne idée de faire une fonction qui s’appelle log dans le namespace global, elle risque d’entrer en conflit avec std::log, qui dans mon cas était disponible (le test a été fait avec un gcc 5.4). Ainsi, un appel à log(42) compilait sans erreur alors que pgt::log(42) génère bien un message d’erreur.
  2. Il y a moyen de faire encore mieux pour formater le timestamp des logs mais mon vieux compilateur semblait perdu…
  3. Si on intervertit main.cpp et -lfmt dans la ligne de commande, on se mange une palanquée d’erreurs de link.
  4. Oui, un formatage peut rater et lancer des exceptions !

Personnellement, je trouve ça bien sympa !

Pour aller plus loin, vous pouvez regarder la documentation de l’API ou approfondir sur la syntaxe du formatage,

C’est tout pour aujourd’hui 🙂

C++17 et fold expression

Il y a quelques jours, j’ai découvert les fold expressions, une nouveauté de C++17. J’ai trouvé ça cool. Je me suis surtout demandé à quoi ça pourrait me servir. Et puis j’ai pensé à un code que j’avais écrit il y a quelques semaines et et je me suis dit que ça aurait sans doute été plus simple avec ça. Je ne vais pas modifier le vrai code puisqu’il marche et est déjà utilisé dans un logiciel professionnel mais j’ai fait un code d’essai pour voir. Et effectivement, c’est bien mieux ! Je vais vous montrer ça.

Le but est de monitorer des variables pendant l’exécution du programme. J’ai pour cela une classe Monitor à laquelle on peut ajouter des variables à surveiller. Voici à quoi ressemble le code :

#include <iostream>

class Monitor {
public:
    void watch(short& s [[maybe_unused]]) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    void watch(int& i [[maybe_unused]]) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    void watch(long& l [[maybe_unused]]) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main(void) {
    Monitor m;

    short s = 0;
    m.watch(s);

    int i = 0;
    m.watch(i);

    long l = 42;
    m.watch(l);
}

J’en profite au passage pour vous montrer l’extensions GNU __PRETTY_FUNCTION__ qui permet d’afficher joliment le nom de la fonction et une utilisation de l’attribut maybe_unused (une autre nouveauté de C++17) pour à éviter le classique (void) paramètre_qui_ne_sert_pas dans le corps des fonctions. On a ainsi un code qui compile sans warning et qui produit la sortie suivante :

void Monitor::watch(short int&)
void Monitor::watch(int&)
void Monitor::watch(long int&)

À l’utilisation, il s’avère qu’il est pratique de pouvoir ajouter plusieurs variables d’un coup, par exemple en écrivant m.watch(s, i, l);. L’idée est de créer une fonction template prenant un nombre variable de paramètres, chacun pouvant être d’un type différent des autres. C++11 a apporté cette possibilité, grâce aux variadic templates et aux parameter packsUne telle fonction ressemblerait à ça :

template<typename...Targs>
void function(Targs... args) {
}

Ça se corse quand on souhaite itérer sur les éléments du pack… La technique classique consiste à fait faire des appels récursifs à la fonction pour réduire le pack. Pour la classe Monitor, il convient donc de rajouter deux fonctions :

class Monitor {
public:
    // idem

    template<typename T, typename...Targs>
    void watchSeveral_recursive(T& t, Targs&...args) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        watch(t);
        watchSeveral_recursive(args...);
    }

private:
    template<typename T>
    void watchSeveral_recursive(T& t) {
        std::cout << __PRETTY_FUNCTION__ << " (final) " << std::endl;
        watch(t);
    }
};

La fonction publique est effectivement récursive. L’astuce est d’avoir un paramètre simple en plus du pack. Chaque appel imbriqué va ainsi « réduire le pack », puisque le premier élément du pack va devenir le premier argument et que le reste du pack va créer un nouveau pack en deuxième argument. Comme toute récursion, il faut un cas d’arrêt : c’est le rôle de la fonction privée. Elle est appelée quand le pack ne contient plus qu’un élément puisque le compilateur va préférer cette surcharge privée à la surcharge publique dans un tel cas. Ça donne ça à l’utilisation :

int main(void) {
    Monitor m;

    short s = 0;
    int i = 0;
    long l = 42;

    m.watchSeveral_recursive(s, i, l);
}
void Monitor::watchSeveral_recursive(T&, Targs& ...) [with T = short int; Targs = {int, long int}]
void Monitor::watch(short int&)
void Monitor::watchSeveral_recursive(T&, Targs& ...) [with T = int; Targs = {long int}]
void Monitor::watch(int&)
void Monitor::watchSeveral_recursive(T&) [with T = long int] (final) 
void Monitor::watch(long int&)

Sympa, hein ?

Voici maintenant la version avec une fold expression :

class Monitor {
public:
    // idem

    template<typename...Ts>
    void watchSeveral_withFoldExpr(Ts&...args) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        (watch(args), ...);
        // Notez l'utilisation de parentheses !
    }
};

Elle s’utilise évidemment de la même manière que précédemment :

int main(void) {
    Monitor m;

    short s = 0;
    int i = 0;
    long l = 42;

    m.watchSeveral_withFoldExpr(s, i, l);
}

La sortie est bien sûr ce qu’on s’attend à obtenir :

void Monitor::watchSeveral_withFoldExpr(Ts& ...) [with Ts = {short int, int, long int}]
void Monitor::watch(short int&)
void Monitor::watch(int&)
void Monitor::watch(long int&)

Voilà, voilà… J’avais dit que c’était mieux ^^ C’est tellement simple que je n’ai pas besoin de vous expliquer le code. Il suffit que de savoir que c’est une fold expression pour le comprendre. C’est une fonctionnalité toute nouvelle, peu de gens la connaissent, mais vous oui après la lecture de cet article 🙂

Laisse faire ton compilateur, il sait ce qu’il fait :)

Ce qui est bien avec les compilateurs modernes, c’est qu’ils sont très bons pour optimiser le code à notre place. Un bon code est un code efficace mais simple, que le compilateur comprendra et analysera facilement. Le développeur pourra décider d’écrire un code « plus optimisé » si et seulement si le compilateur n’a pas réussi à produire quelque chose de satisfaisant. Mais il devrait quand même d’abord essayer de clarifier son code 😉

J’en ai encore fait l’expérience ces jours-ci en me demandant si un ranged-loop for (apparu en C++11) sur un tableau avait un overhead par rapport à un parcours classique avec un index de 0 à taille-1. Je suis donc aller sur Compiler Explorer et j’ai écrit ce petit code :

void f(int v);

constexpr int SIZE = 128;
int array[SIZE];

void g() {
    for(auto& e : array) {
        f(e);
    }
}

void h() {
    for(int i = 0; i < SIZE; ++i) {
        auto& e = array[i];
        f(e);
    }
}

Je l'ai ensuite compilé avec GCC 7.2.1 pour ARM, puisque c'est la toolchain que j’utilise sur mon projet actuel. Avec l’optimisation à O2 ou O3, les codes assembleurs générés sont les mêmes. En revanche, une des versions est un poil meilleure en O1…. et ce n’est pas la version classique mais la version mode du ranged-loop for. Sans doute parce qu’il n’y a aucune ambiguïté sur le code : il s’agit bien de parcourir le tableau élément par élément. La différence est minime toutefois 😉

Voici ce que ça donne (les couleurs montrent la correspondance code source / code assembleur) :

 

Conclusion : laissez faire votre compilateur, il sait ce qu’il fait…. et sans doute bien mieux que vous !

Outils Markdown pour Notepad++

Il y a très longtemps, j’avais parlé des outils XML pour Notepad++. Parlons aujourd’hui des outils Markdown !

Coloration syntaxique

Il n’y a pas de coloration syntaxique pour ce langage par défaut…

npp md avant

Avant…

On va commencer par remédier à ce défaut ! Notepad++ permet de définir des langages utilisateurs pour permettre la coloration syntaxique des langages non supportés nativement. On parle de « User Defined Language« .  Le dépôt GitHub markdown-plus-plus fournit plusieurs fichiers XML contenant des définitions de « User Defined Languages » pour le markdown. La section Usage du README explique comment importer ces fichiers.

Il est ensuite possible de sélectionner ce nouveau langage depuis le menu Langage. Il est sélectionné automatiquement si votre fichier porte l’une des extensions indiquer dans la configuration du langage importée. Il est possible d’en rajouter si jamais vous le souhaitez.

npp md apres

À mi-chemin

Visualisation

Maintenant que le texte est correctement coloré, il ne reste plus qu’avoir une visualisation du rendu ! Il existe un autre dépôt GitHub avec tout ce qu’il faut : MarkdownViewerPlusPlus ! Comme précédemment, le README vous explique tout ce que vous devez savoir.

Une fois le plugin installé et Notepad++ redémarré, un petit bouton avec un M blanc sur fond noir apparaît dans la barre d’outils. Il permet d’ouvrir la fenêtre de visualisation.

npp md apercu

Fini !

Aide-mémoire

En bonus, n’oublie pas de mettre ce Markdown Cheatsheet dans vos marque-pages !