Explorer la qualité du code du système d'exploitation Zephyr

PVS-Studio et Zephyr

Nous avons récemment déclaré que l'analyseur de code PVS-Studio avait commencé à s'intégrer à PlatformIO. Naturellement, l'équipe de développement de PVS-Studio a communiqué avec l'équipe de PlatformIO et ils ont suggéré, par souci d'intérêt, de vérifier le code du système d'exploitation en temps réel Zephyr. Pourquoi pas, nous avons pensé, et voici un article sur une telle étude.

PlatformIO


Avant de commencer la partie principale de l'article, je voudrais recommander le projet PlatformIO aux développeurs de systèmes embarqués , ce qui peut leur faciliter la vie. Il s'agit d'un outil de programmation de microcontrôleur multiplateforme. Le cœur de PlatformIO est un outil en ligne de commande, mais il est recommandé de l'utiliser comme plug-in pour Visual Studio Code. Un grand nombre de puces et de cartes mères modernes basées sur celles-ci sont prises en charge. Il est capable de télécharger automatiquement les systèmes d'assemblage appropriés et le site possède une grande collection de bibliothèques pour gérer les composants électroniques enfichables.

PVS-Studio


PVS-Studio est encore peu connu dans le monde des systèmes embarqués, donc juste au cas où, je ferai une introduction pour les nouveaux lecteurs qui ne connaissent pas encore cet outil. Nos lecteurs réguliers peuvent passer directement à la section suivante.

PVS-Studio est un analyseur de code statique qui permet de détecter les erreurs et les vulnérabilités potentielles dans le code des programmes écrits en C, C ++, C # et Java. Si nous ne parlons que de C et C ++, les compilateurs suivants sont pris en charge:

  • les fenêtres Visual Studio 2010-2019 C, C ++, C ++ / CLI, C ++ / CX (WinRT)
  • les fenêtres IAR Embedded Workbench, compilateur C / C ++ pour ARM C, C ++
  • les fenêtres QNX Momentics, QCC C, C ++
  • Windows / Linux Keil µVision, DS-MDK, ARM Compiler 5/6 C, C ++
  • Windows / Linux Texas Instruments Code Composer Studio, Outils de génération de code ARM C, C ++
  • Windows/Linux/macOS. GNU Arm Embedded Toolchain, Arm Embedded GCC compiler, C, C++
  • Windows/Linux/macOS. Clang C, C++
  • Linux/macOS. GCC C, C++
  • Windows. MinGW C, C++

L'analyseur possède son propre système de classification des avertissements , mais si nécessaire, vous pouvez activer l'affichage des alertes selon les normes de codage CWE , SEI CERT , MISRA .

Vous pouvez rapidement commencer à utiliser régulièrement PVS-Studio, même dans un grand projet hérité. Un mécanisme spécial de suppression massive des avertissements est prévu à cet effet . Tous les avertissements actuels sont considérés comme des dettes techniques et sont masqués, ce qui vous permet de vous concentrer sur les avertissements qui s'appliquent uniquement au code nouveau ou modifié. Cela permet à l'équipe de commencer immédiatement à utiliser l'analyseur quotidiennement dans son travail, et vous pouvez de temps en temps revenir au devoir technique et améliorer le code.

Il existe de nombreux autres scénarios d'utilisation de PVS-Studio. Par exemple, vous pouvez l'utiliser comme plugin pour SonarQube. L'intégration avec des systèmes tels que Travis CI, CircleCI, GitLab CI / CD, etc. est possible. Une description plus détaillée de PVS-Studio dépasse le cadre de cet article. Par conséquent, je propose de vous familiariser avec l'article, qui contient de nombreux liens utiles, et dans lequel des réponses à de nombreuses questions sont données: " Raisons pour introduire l'analyseur de code statique PVS-Studio dans le processus de développement ."

Zéphyr


Travaillant sur l' intégration de PVS-Studio dans PlatformIO , nos équipes se sont entretenues et ont été invitées à découvrir un projet du monde embarqué, à savoir Zephyr. Nous avons aimé l'idée, ce qui a motivé la rédaction de cet article.

Zephyr est un système d'exploitation léger en temps réel conçu pour fonctionner sur des appareils avec des ressources limitées de diverses architectures. Le code est distribué sous la licence open source Apache 2.0. Fonctionne sur les plateformes suivantes: ARM (Cortex-M0, Cortex-M3, Cortex-M4, Cortex-M23, Cortex-M33, Cortex-R4, Cortex-R5, Cortex-A53), x86, x86-64, ARC, RISC- V, Nios II, Xtensa.

Certaines fonctionnalités:

  • Espace d'adressage unifié. Le code d'application spécifique en combinaison avec le noyau personnalisé crée une image monolithique qui est exécutée sur l'appareil.
  • . , .
  • . .
  • . . .
  • : , , , , .

Parmi les moments intéressants pour nous, Synopsys est impliqué dans le développement du système d'exploitation . En 2014, Synopsys a acquis Coverity, qui a produit l'analyseur de code statique du même nom.

Il est naturel que dès le début, le développeur Zephyr utilise l'analyseur Coverity . L'analyseur est un leader du marché et cela ne peut qu'améliorer la qualité du code du système d'exploitation.

Qualité du code Zephyr


À mon avis, le code du système d'exploitation Zephyr est de qualité. Voici ce qui me donne raison de le penser:

  • PVS-Studio 122 High 367 Medium. , , 560 C/C++ . . 7810 C/C++ 10075 . , . , , .
  • . «» , .
  • L'utilitaire SourceMonitor , après avoir analysé le code source, a fourni des statistiques selon lesquelles 48% du code sont des commentaires. C'est beaucoup et d'après mon expérience, cela indique une grande préoccupation pour la qualité du code, sa compréhensibilité pour les autres développeurs.
  • Lors du développement d'un projet, un analyseur de code statique Coverity est utilisé. Très probablement, de ce fait, l'analyseur PVS-Studio, bien qu'il ait trouvé des erreurs dans le projet, ne pouvait pas se montrer clairement, comme cela arrive parfois lors de l'analyse d'autres projets.

Sur cette base, je pense que les auteurs du projet se soucient de la qualité et de la fiabilité du code. Voyons maintenant quelques avertissements émis par l'analyseur PVS-Studio (version 7.06).

Semi-faux avertissements


Le code du projet, en raison de son faible niveau, est écrit de manière assez spécifique et avec beaucoup de compilation conditionnelle (#ifdef). Cela génère un grand nombre d'avertissements qui n'indiquent pas une véritable erreur, mais ils ne peuvent pas être appelés simplement faux. Il sera plus facile de clarifier cela avec quelques exemples.

Un exemple d'actionnement «semi-faux» N1

static struct char_framebuffer char_fb;

int cfb_framebuffer_invert(struct device *dev)
{
  struct char_framebuffer *fb = &char_fb;

  if (!fb || !fb->buf) {
    return -1;
  }

  fb->inverted = !fb->inverted;

  return 0;
}

PVS-Studio Warning: V560 Une partie de l'expression conditionnelle est toujours fausse :! Fb . cfb.c 188

Lors de la prise de l'adresse d'une variable statique, un pointeur non nul est toujours obtenu. Par conséquent, le pointeur fb est toujours non nul et sa vérification n'a pas de sens.

Cependant, il est clair que ce n'est pas du tout une erreur, mais simplement un contrôle excessif, qui ne fait pas de mal. De plus, lors de la construction de la version Release, le compilateur la jettera, donc cela ne causera même pas de ralentissement.

Un cas similaire, à ma connaissance, relève du concept de fonctionnement de l'analyseur «semi-faux». Formellement, l'analyseur a absolument raison. Et il est préférable de supprimer la vérification inutile supplémentaire du code. Cependant, tout cela est mesquin et de tels avertissements ne sont même pas intéressants à considérer dans le cadre de l'article.

Un exemple d'actionnement «semi-faux» N2

int hex2char(u8_t x, char *c)
{
  if (x <= 9) {
    *c = x + '0';
  } else if (x >= 10 && x <= 15) {
    *c = x - 10 + 'a';
  } else {
    return -EINVAL;
  }
  return 0;
}

Avertissement PVS-Studio: V560 Une partie de l'expression conditionnelle est toujours vraie: x> = 10. hex.c 31

L'analyseur a de nouveau formellement raison d'affirmer qu'une partie de la condition est toujours vraie. Si la variable x n'est pas inférieure / égale à 9, alors il s'avère qu'elle est toujours supérieure / égale à 10. Et le code peut être simplifié:

} else if (x <= 15) {

Encore une fois, il est clair qu'il n'y a pas vraiment d'erreur nuisible ici, et la comparaison supplémentaire est écrite juste pour la beauté du code.

Voyons maintenant un exemple plus complexe de N3. Voyons d'abord

comment la macro CHECKIF peut être implémentée .

#if defined(CONFIG_ASSERT_ON_ERRORS)
#define CHECKIF(expr) \
  __ASSERT_NO_MSG(!(expr));   \
  if (0)
#elif defined(CONFIG_NO_RUNTIME_CHECKS)
#define CHECKIF(...) \
  if (0)
#else
#define CHECKIF(expr) \
  if (expr)
#endif

Selon le mode de compilation du projet, la vérification peut être effectuée ou ignorée. Dans notre cas, lors de la vérification du code à l'aide de PVS-Studio, cette implémentation de macro a été sélectionnée:

#define CHECKIF(expr) \
  if (expr)

Voyons maintenant à quoi cela mène.

int k_queue_append_list(struct k_queue *queue, void *head, void *tail)
{
  CHECKIF(head == NULL || tail == NULL) {
    return -EINVAL;
  }

  k_spinlock_key_t key = k_spin_lock(&queue->lock);
  struct k_thread *thread = NULL;
  if (head != NULL) {
    thread = z_unpend_first_thread(&queue->wait_q);
  }
  ....
}

Avertissement PVS-Studio: V547 [CWE-571] L'expression 'head! = NULL' est toujours vraie. queue.c 244

L'analyseur considère que la vérification (head! = NULL) donne toujours true. Et c'est effectivement le cas. Si le pointeur de tête était NULL, alors la fonction cesserait de fonctionner en raison d'une vérification au début de la fonction:

CHECKIF(head == NULL || tail == NULL) {
  return -EINVAL;
}

Rappelons qu'ici la macro se développe comme suit:

if (head == NULL || tail == NULL) {
  return -EINVAL;
}

Ainsi, l'analyseur PVS-Studio est juste de son point de vue et émet un avertissement correct. Cependant, cette vérification ne peut pas être supprimée. Elle est nécessaire. Dans un autre scénario, la macro s'ouvrira comme ceci:

if (0) {
  return -EINVAL;
}

Et puis revérifier le pointeur est nécessaire. Bien sûr, l'analyseur ne générera pas d'avertissement dans cette version de compilation de code. Cependant, il donne un avertissement pour la version de débogage de la compilation.

J'espère que maintenant, les lecteurs savent clairement d'où viennent les avertissements «semi-faux». Cependant, il n'y a rien de mal avec eux. L'analyseur PVS-Studio fournit divers mécanismes pour supprimer les fausses alertes, qui peuvent être trouvés dans la documentation.

Avertissements de cas


Mais avez-vous trouvé quelque chose d'intéressant après tout? Ce fut un succès, et maintenant nous allons examiner diverses erreurs. En même temps, je tiens à noter immédiatement deux points:

  1. . : , , , Coverity. , PVS-Studio - , .
  2. . , «» . , . GitHub, PVS-Studio.

N1,

static void gen_prov_ack(struct prov_rx *rx, struct net_buf_simple *buf)
{
  ....
  if (link.tx.cb && link.tx.cb) {
    link.tx.cb(0, link.tx.cb_data);
  }
  ....
}

Avertissement PVS-Studio: V501 [CWE-571] Il existe des sous-expressions identiques à gauche et à droite de l'opérateur '&&': link.tx.cb && link.tx.cb pb_adv.c 377

L'une est vérifiée deux fois la même variable link.tx.cb . Apparemment, c'est une faute de frappe, et la deuxième variable à vérifier devrait être link.tx.cb_data .

Fragment N2, débordement du tampon

Considérons la fonction net_hostname_get , qui sera utilisée plus tard.

#if defined(CONFIG_NET_HOSTNAME_ENABLE)
const char *net_hostname_get(void);
#else
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}
#endif

Dans mon cas, lors du prétraitement, l'option liée à la branche #else a été sélectionnée . Autrement dit, dans le fichier prétraité, la fonction est implémentée comme ceci:
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}

La fonction retourne un pointeur sur un tableau de 7 octets (on prend en compte le terminal zéro à la fin de la ligne).

Considérez maintenant le code qui mène à la sortie de la limite du tableau.

static int do_net_init(void)
{
  ....
  (void)memcpy(hostname, net_hostname_get(), MAX_HOSTNAME_LEN);
  ....
}

Avertissement PVS-Studio: V512 [CWE-119] Un appel de la fonction 'memcpy' entraînera la sortie du buffer 'net_hostname_get ()'. log_backend_net.c 114

Après le prétraitement, MAX_HOSTNAME_LEN est développé comme suit:

(void)memcpy(hostname, net_hostname_get(),
    sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"));

Par conséquent, lors de la copie de données, un littéral de chaîne dépasse la frontière. Il est difficile de prédire comment cela affectera l'exécution du programme, car cela conduit à un comportement indéfini.

Fragment N3, dépassement potentiel du tampon

int do_write_op_json(struct lwm2m_message *msg)
{
  u8_t value[TOKEN_BUF_LEN];
  u8_t base_name[MAX_RESOURCE_LEN];
  u8_t full_name[MAX_RESOURCE_LEN];
  ....
  /* combine base_name + name */
  snprintf(full_name, TOKEN_BUF_LEN, "%s%s", base_name, value);
  ....
}

Avertissement PVS-Studio: V512 [CWE-119] Un appel de la fonction 'snprintf' entraînera un débordement du tampon 'full_name'. lwm2m_rw_json.c 826

Si nous substituons des valeurs de macro, l'image de ce qui se passe ressemble à ceci:

u8_t value[64];
u8_t base_name[20];
u8_t full_name[20];
....
snprintf(full_name, 64, "%s%s", base_name, value);

Seuls 20 octets sont alloués sous le tampon full_name dans lequel la chaîne est formée. Dans ce cas, les parties à partir desquelles la chaîne est formée sont stockées dans des tampons de 20 et 64 octets. De plus, la constante 64, passée à la fonction snprintf et conçue pour empêcher la baie d'aller à l'étranger, est évidemment trop grosse!

Ce code ne conduit pas nécessairement à un débordement de tampon. Peut-être toujours chanceux et les sous-chaînes sont toujours très petites. Cependant, en général, ce code n'est en aucun cas protégé contre le débordement et contient la faille de sécurité classique CWE-119 .

Fragment N4, l'expression est toujours vraie

static int keys_set(const char *name, size_t len_rd, settings_read_cb read_cb,
                    void *cb_arg)
{
  ....
  size_t len;
  ....
  len = read_cb(cb_arg, val, sizeof(val));
  if (len < 0) {
    BT_ERR("Failed to read value (err %zu)", len);
    return -EINVAL;
  }
  ....
}

Avertissement PVS-Studio: V547 [CWE-570] L'expression 'len <0' est toujours fausse. La valeur du type non signé n'est jamais <0. keys.c 312 La

variable len a un type non signé et, par conséquent, ne peut pas être inférieure à 0. Par conséquent, le statut d'erreur n'est en aucun cas traité. Dans d'autres endroits , le type int ou ssize_t est utilisé pour stocker le résultat de la fonction read_cb . Exemple:


static inline int mesh_x_set(....)
{
 ssize_t len;
 len = read_cb(cb_arg, out, read_len);
 if (len < 0) {
 ....
}

Remarque. Tout semble aller mal avec la fonction read_cb . Le fait est qu'il est déclaré comme ceci:

static u8_t read_cb(const struct bt_gatt_attr *attr, void *user_data)

Le type u8_t est un caractère non signé.

La fonction ne renvoie toujours que des nombres positifs de type caractère non signé . Si vous mettez cette valeur dans une variable signée de type int ou ssize_t , la valeur sera toujours positive. Par conséquent, dans d'autres endroits, la vérification de l'état d'erreur ne fonctionne pas non plus. Mais je ne me suis pas plongé dans l'étude de cette question.

Fragment N5, quelque chose de très étrange

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}

Avertissement PVS-Studio: V575 [CWE-628] La fonction 'memcpy' ne copie pas la chaîne entière. Utilisez la fonction 'strcpy / strcpy_s' pour conserver la valeur null du terminal. shell.c 427

Code étrange

Quelqu'un a essayé de créer un analogue de la fonction strdup , mais il n'a pas réussi.

Commençons par l'avertissement de l'analyseur. Il signale que la fonction memcpy copie la ligne, mais ne copie pas le terminal zéro, ce qui est très suspect.

Il semble que ce terminal 0 soit copié ici:

((u8_t *)mntpt)[strlen(mntpt)] = '\0';

Mais non! Voici une faute de frappe, à cause de laquelle le terminal zéro est copié sur lui-même! Notez que l'écriture dans le tableau mntpt , pas cpy_mntpt . Par conséquent, la fonction mntpt_prepare renvoie une chaîne incomplète avec un terminal zéro.

En fait, le programmeur voulait écrire comme ceci:

((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';

Cependant, on ne sait toujours pas pourquoi c'était si compliqué! Ce code peut être simplifié avec l'option suivante:

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}

Fragment N6, déréférencer un pointeur avant validation

int bt_mesh_model_publish(struct bt_mesh_model *model)
{
  ....
  struct bt_mesh_model_pub *pub = model->pub;
  ....
  struct bt_mesh_msg_ctx ctx = {
    .send_rel = pub->send_rel,
  };
  ....
  if (!pub) {
    return -ENOTSUP;
  }
  ....
}

Avertissement PVS-Studio: V595 [CWE-476] Le pointeur "pub" a été utilisé avant d'être vérifié par rapport à nullptr. Vérifiez les lignes: 708, 719. access.c 708

Un modèle d'erreur très courant . Tout d'abord, le pointeur est déréférencé pour initialiser un membre de la structure:

.send_rel = pub->send_rel,

Et ce n'est qu'ensuite que l'on vérifie que ce pointeur peut être nul.

Fragment N7-N9, déréférencement de pointeur avant validation

int net_tcp_accept(struct net_context *context, net_tcp_accept_cb_t cb,
                   void *user_data)
{
  ....
  struct tcp *conn = context->tcp;
  ....
  conn->accept_cb = cb;

  if (!conn || conn->state != TCP_LISTEN) {
    return -EINVAL;
  }
  ....
}

Avertissement PVS-Studio: V595 [CWE-476] Le pointeur 'conn' a été utilisé avant d'être vérifié par rapport à nullptr. Vérifiez les lignes: 1071, 1073. tcp2.c 1071 Identique

au cas précédent. Aucune explication n'est requise ici.

Deux autres erreurs de ce type peuvent être vues ici:

  • V595 [CWE-476] Le pointeur 'context-> tcp' a été utilisé avant d'être vérifié par rapport à nullptr. Vérifiez les lignes: 1512, 1518. tcp.c 1512
  • V595 [CWE-476] Le pointeur 'fsm' a été utilisé avant d'être vérifié par rapport à nullptr. Vérifiez les lignes: 365, 382. fsm.c 365

Fragment N10, vérification erronée

static int x509_get_subject_alt_name( unsigned char **p,
                                      const unsigned char *end,
                                      mbedtls_x509_sequence *subject_alt_name)
{
  ....
    while( *p < end )
    {
        if( ( end - *p ) < 1 )
            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
                    MBEDTLS_ERR_ASN1_OUT_OF_DATA );
    ....
  }
  ....
}

Avertissement PVS-Studio: V547 [CWE-570] L'expression '(fin - * p) <1' ​​est toujours fausse. x509_crt.c 635

Examinez de près les conditions:

  • * p <fin
  • (fin - * p) <1

Ils se contredisent.

Si (* p <end), alors (end - * p) donnera toujours une valeur de 1 ou plus. En général, il y a quelque chose qui ne va pas ici, mais je ne sais pas comment l'épeler correctement.

Fragment N11, code inaccessible

uint32_t lv_disp_get_inactive_time(const lv_disp_t * disp)
{
    if(!disp) disp = lv_disp_get_default();
    if(!disp) {
        LV_LOG_WARN("lv_disp_get_inactive_time: no display registered");
        return 0;
    }

    if(disp) return lv_tick_elaps(disp->last_activity_time);

    lv_disp_t * d;
    uint32_t t = UINT32_MAX;
    d          = lv_disp_get_next(NULL);
    while(d) {
        t = LV_MATH_MIN(t, lv_tick_elaps(d->last_activity_time));
        d = lv_disp_get_next(d);
    }

    return t;
}

Avertissement PVS-Studio: V547 [CWE-571] L'expression 'disp' est toujours vraie. lv_disp.c 148

La fonction se termine si disp est un pointeur nul. Ensuite, au contraire, on vérifie que le pointeur disp n'est pas nul (et c'est toujours le cas), et la fonction termine à nouveau son travail.

Par conséquent, une partie du code d'une fonction ne sera jamais contrôlée du tout.

Fragment N12, étrange valeur de retour

static size_t put_end_tlv(struct lwm2m_output_context *out, u16_t mark_pos,
        u8_t *writer_flags, u8_t writer_flag,
        int tlv_type, int tlv_id)
{
  struct tlv_out_formatter_data *fd;
  struct oma_tlv tlv;
  u32_t len = 0U;

  fd = engine_get_out_user_data(out);
  if (!fd) {
    return 0;
  }

  *writer_flags &= ~writer_flag;

  len = out->out_cpkt->offset - mark_pos;

  /* use stored location */
  fd->mark_pos = mark_pos;

  /* set instance length */
  tlv_setup(&tlv, tlv_type, tlv_id, len);
  len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
  return 0;
}

PVS-Studio Warning: V1001 La variable 'len' est affectée mais n'est pas utilisée à la fin de la fonction. lwm2m_rw_oma_tlv.c 338

La fonction contient deux instructions de retour qui renvoient toutes les deux 0. Il est étrange que la fonction renvoie toujours 0. Il est également étrange que la variable len ne soit plus utilisée après l'affectation. J'ai un grand soupçon qu'il devrait en fait être écrit comme ceci:

  len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
  return len;
}

Fragment N13-N16, erreur de synchronisation

static int nvs_startup(struct nvs_fs *fs)
{
  ....
  k_mutex_lock(&fs->nvs_lock, K_FOREVER);
  ....
  if (fs->ate_wra == fs->data_wra && last_ate.len) {
    return -ESPIPE;
  }
  ....
end:
  k_mutex_unlock(&fs->nvs_lock);
  return rc;
}

PVS-Studio Warning: V1020 La fonction s'est terminée sans appeler la fonction 'k_mutex_unlock'. Vérifiez les lignes: 620, 549. nvs.c 620

Il y a une situation où une fonction termine son travail sans déverrouiller le mutex. Si je comprends bien, il serait correct d'écrire comme ceci:

static int nvs_startup(struct nvs_fs *fs)
{
  ....
  k_mutex_lock(&fs->nvs_lock, K_FOREVER);
  ....
  if (fs->ate_wra == fs->data_wra && last_ate.len) {
    rc = -ESPIPE;
    goto end;
  }
  ....
end:
  k_mutex_unlock(&fs->nvs_lock);
  return rc;
}

Trois autres erreurs de ce type:

  • V1020 La fonction s'est terminée sans appeler la fonction 'k_mutex_unlock'. Lignes de contrôle: 574, 549. nvs.c 574
  • V1020 La fonction s'est terminée sans appeler la fonction 'k_mutex_unlock'. Vérifiez les lignes: 908, 890. net_context.c 908
  • V1020 La fonction s'est terminée sans appeler la fonction 'k_mutex_unlock'. Vérifiez les lignes: 1194, 1189. shell.c 1194

Conclusion


J'espère que vous avez aimé. Visitez notre blog pour en savoir plus sur les vérifications d'autres projets et d'autres publications intéressantes.

Utilisez des analyseurs statiques dans votre travail pour réduire le nombre d'erreurs et de vulnérabilités potentielles même au stade de l'écriture de code. La détection précoce des erreurs est particulièrement pertinente pour les systèmes embarqués, la mise à jour de programmes dans lesquels est souvent un processus long et coûteux.

Je suggère également de ne pas reporter et d'essayer de vérifier vos projets à l'aide de l'analyseur PVS-Studio. Voir l'article: Comment voir rapidement les avertissements intéressants générés par l'analyseur PVS-Studio pour le code C et C ++?



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Andrey Karpov. Vérification du code du système d'exploitation Zephyr .

All Articles