Improving Embedded Systems Code Quality Using Static Code Analysis

Thread Starter

julprogrammer

Joined Feb 12, 2020
1
It’s not a secret that the code quality standards for embedded systems software are much higher than those for games, photo editors and others. The requirements have increased after cases when hardware having got out of control caused a lot of tragic situations and, at best, great deal of money was spent for nothing. The article tells about the ways of improving code quality.
 

402DF855

Joined Feb 9, 2013
194
I'm not much in favor of MISRA, and static analyzers can be useful. But the key to software quality, embedded or otherwise, is testing:

"The erroneous module of the fifth model wasn’t tested properly."
 

ApacheKid

Joined Jan 12, 2015
157
I've discovered over the past few years that it is often imperative languages themselves that cause poor quality/reliability. Believe it or not functional languages can have a huge impact and often totally eliminate problems that plague imperative code.

There's a common saying in the functional world - if it compiles it works - this isn't too much of an exaggeration either.

Functional languages either don't support or strongly discourage:

  1. Variables
  2. Loops
  3. Null
  4. If/then/else

This might at first seem pretentious but it isn't, the removal of these mechanisms really does avoid a huge number of scenarios that are difficult to identify or debug.

In fact functional compilers do more powerful static analysis which is further aided by the lack of these concepts.

Unfortunately there are no functional languages well suited to microcontroller development, C and the loathesome C++ dominate this genre of software.
 

nsaspook

Joined Aug 27, 2009
7,179
I've discovered over the past few years that it is often imperative languages themselves that cause poor quality/reliability. Believe it or not functional languages can have a huge impact and often totally eliminate problems that plague imperative code.

There's a common saying in the functional world - if it compiles it works - this isn't too much of an exaggeration either.

Functional languages either don't support or strongly discourage:

  1. Variables
  2. Loops
  3. Null
  4. If/then/else

This might at first seem pretentious but it isn't, the removal of these mechanisms really does avoid a huge number of scenarios that are difficult to identify or debug.

In fact functional compilers do more powerful static analysis which is further aided by the lack of these concepts.

Unfortunately there are no functional languages well suited to microcontroller development, C and the loathesome C++ dominate this genre of software.
The root problem is that most applications for microcontrollers must use unsafe code at some point to interact with real-world events that are not static (non-arithmetic,dynamically changing state interfaces) or have functions that can't be easily decomposed into the functional programming paradigm without muddying the low-level bit banging harsh performance constraint mental picture IMO. SO if you want to sacrifice raw performance and also lose precise control over what and when should happen, pure functional programing is the cure.
Side-effects like specific I/O sequences in a specific order must be written in an imperative style (impure functions) even in functional languages.
https://en.wikibooks.org/wiki/Haskell/Understanding_monads/IO

Languages like Forth sort of fit into the functional language arena but if you've ever used Forth you can see why it's called a 'write only' language as the concatenative programming paradigm can be bewildering.
 
Last edited:

nsaspook

Joined Aug 27, 2009
7,179
A critical factor in embedded systems is co-design. This is where new hardware and software are created together. You need software languages with very few constraints to design precise hardware testing hacks when isolating possible hardware issues.

Specification: A chicken and egg problem. This is why people stay with the same product line and controller series for long periods of time, you can start with a hatchling and buildup from that.

Partitioning: The division (hardware/software solutions of problems) of labor problem. This is why people change product lines and controller series. Former software problems are solved in new hardware (DMA vs software peek/pokes).

Modelling and design: Connecting the dots. Design a possible prototype, define all needed interconnects, signal names and types in a CAD program while transferring those names and types into a software framework while changing algorithms frequently to be correct with the hardware reality.

Implementation/Integration: Actually build a prototype (or two or three). Transfer design changes into the CAD design and software (if you have hardware emulation that's when you discover how limited it is) as the prototype is debugged.

Testing: Try to kill it with badness only seen in a SCI-FI horror movie. This is where the Heisenberg uncertainty principle comes into effect. Merely observing system internal behaviour is impossible without altering it (single stepping real-time systems). Debug mode code runs perfectly, production mode code acts like a crack addict. Is it hardware, software or a phase of the moon? This is why a DSO that costs more than a Tesla car is on the bench. It allows you to interact lightly (reducing Heisenberg uncertainty) at full system speed.

Maintenance: That's the next poor slob's job. You've moved on to the next project or retired to a beach in a country without the Coronavirus.
 

BobaMosfet

Joined Jul 1, 2009
1,056
It’s not a secret that the code quality standards for embedded systems software are much higher than those for games, photo editors and others. The requirements have increased after cases when hardware having got out of control caused a lot of tragic situations and, at best, great deal of money was spent for nothing. The article tells about the ways of improving code quality.
Nobody can test a piece of code better than the designer. It is a developer's duty to generate flawless code. Impossilble? Bull-puckey! I do it every day. It's called engineering, designing, planning, documenting, and being mindful of constraints and requirements; debugging, testing, using flow-charts, breadcrumbs, logic tables, and analysis to ensure that every case is taken care of. Writing additional code to test snippets to be sure they function correctly and logic proven before integrating into production. Writing code defensively and make code take care of itself.

Bad code exists today because of lazy programmers, and idiot teachers* who impart bad practices and techniques.

If, as a developer, you have to blame anything or anyone other than yourself for bad code- you shouldn't be programming. period. it isn't for you. The plethora of programming languages today (starting with Java) stem from one simple truth: In most cases, they were created by someone who had not the discipline or capability to code well in the first place. They found the responsibilities and requirements of coding well to be too burdensome- they want the compiler to do it all. It can't.
---
*Not all, just those that do.
 
Last edited:

ApacheKid

Joined Jan 12, 2015
157
Bad code exists today because of lazy programmers, and idiot teachers* who impart bad practices and techniques.
I'm afraid I can't agree with that claim.

The "quality" (however we choose to measure this) is directly influenced by deadline pressure, working environment, ability to focus on the task and so on.

If you read Peopleware, you'll see the evidence for this.

In there is a case study where very experienced developers of a known high calibre produced lower quality than relatively novice developers given the same task.

The difference was that in the former test there was office noise, interruptions, frequent urgent emails coming in, unplanned meetings and so on, these meetings were short and few by the way.

So given pretty much the same problem, tools, and time the experienced team created worse code.

So laziness is too simplistic, there are a host of factors and unless these are appreciated by management quality is compromised.

One of my gripes about scrum is that it creates the kind of pressure that can impact quality, like a regular recurring drumbeat, the daily "stand ups" and recurring "sprint planning" sessions week in and week out actually work against quality.

With my own personal work I may hot some issue or problem and then shelve the work for a few days, then magically the solution or better approach will just come to me and I'll get past the issue and improve the design. From the outside it looks like I stopped doing productive work for two days but in reality my mind was working in a low-pressure, subliminal, creative mode that was an essential step.
 

nsaspook

Joined Aug 27, 2009
7,179
I wouldn't call them 'lazy'. There are different types of programmers like there are different types of cars. To expect X top-notch programmer to handle any type of task is like asking a mechanical engineer to design electronics. The ME and the EE both understand good engineering principles but they are specialist in a field. So far in software engineering this specialist distinction is less clear as a professional label for programmers.
 
Last edited:

MrAl

Joined Jun 17, 2014
7,460
Hello there,

I've had very good results adopting a self imposed methodology where you always do things a certain way and do not vary from that. It takes discipline to follow your own rules though because there is always the temptation to sway from the good rules and through in a goto when things get tough.
It's also good to always provide error checks right in the code itself when you know something may not work the way it is usually expected to work such as when opening a file.
I'll show a few of the simpler examples.
When starting a for next loop, type the end part before any body part, same with while loops.
Always make sure loops have an exit point and one point is better than several.
Work up ways to handle things in piecewise format. When opening a file write the close statement before the body. Iin other words, skeleton code to handle things that have endings that must associate with the start of the body, then fill in the body code dont wait until you've typed out all the body code to add the ending part.

Maybe we can start a list of good programming practices.

There are also methods using artificial intelligence but it's been many years since i got into that so i refer you to the National Argonne Laboratory.
 

nsaspook

Joined Aug 27, 2009
7,179
There are times when goto is the best language structure to solve error condition exits. The compiler/linker has no problem using gotos to optimize solutions and programmers should consider using them in a disciplined manner when it clarifies the program structure and readability during resource allocation and failure.
C:
/* 
 * called for each listed spigert device 
 * SO THIS RUNS FIRST, setup basic spi comm parameters here
 * so it defaults to slow speed
 */
static int32_t spigert_spi_probe(struct spi_device * spi)
{
    struct comedi_spigert *pdata;
    int32_t ret;

    pdata = kzalloc(sizeof(struct comedi_spigert), GFP_KERNEL);
    if (!pdata)
        return -ENOMEM;

    spi->dev.platform_data = pdata;
    reinit_completion(&done);
    pdata->ping_pong = false;
    pdata->upper_lower = 0;
    pdata->tx_buff = kzalloc(SPI_BUFF_SIZE, GFP_DMA);
    if (!pdata->tx_buff) {
        ret = -ENOMEM;
        goto kfree_exit;
    }
    pdata->rx_buff = kzalloc(SPI_BUFF_SIZE, GFP_DMA);
    if (!pdata->rx_buff) {
        ret = -ENOMEM;
        goto kfree_tx_exit;
    }

    /*
     * Do only two chip selects for the Gertboard 
     */

    dev_info(&spi->dev,
        "default setup: cd %d: %d Hz: bpw %u, mode 0x%x\n",
        spi->chip_select, spi->max_speed_hz, spi->bits_per_word,
        spi->mode);

    if (spi->chip_select == CSnA) {
        /* 
         * get a copy of the slave device 0 to share with Comedi 
         * we need a device to talk to the ADC 
         * 
         * create entry into the Comedi device list 
         */
        INIT_LIST_HEAD(&pdata->device_entry);
        pdata->slave.spi = spi;
        /* 
         * put entry into the Comedi device list 
         */
        list_add_tail(&pdata->device_entry, &device_list);
        spi->mode = daqgert_devices[defdev0].spi_mode;
        spi->max_speed_hz = daqgert_devices[defdev0].max_speed_hz;
    }
    if (spi->chip_select == CSnB) {
        /* 
         * we need a device to talk to the DAC 
         */
        INIT_LIST_HEAD(&pdata->device_entry);
        pdata->slave.spi = spi;
        list_add_tail(&pdata->device_entry, &device_list);
        spi->mode = daqgert_devices[defdev0].spi_mode;
        spi->max_speed_hz = daqgert_devices[defdev0].max_speed_hz;
    }
    spi->bits_per_word = daqgert_devices[defdev0].spi_bpw;
    spi_setup(spi);

    /* 
     * Check for basic errors 
     */
    ret = spi_w8r8(spi, 0); /* check for spi comm error */
    if (ret < 0) {
        dev_err(&spi->dev, "spi comm error\n");
        ret = -EIO;
        goto kfree_rx_exit;
    }

    /* setup comedi part of driver */
    if (spi->chip_select == CSnA) {
        ret = comedi_driver_register(&daqgert_driver);
        if (ret < 0)
            goto kfree_rx_exit;

        if (gert_autoload)
            ret = comedi_auto_config(&spi->master->dev,
            &daqgert_driver, 0);

        if (ret < 0)
            goto kfree_rx_exit;
    }
    return 0;

kfree_rx_exit:
    kfree(pdata->rx_buff);
kfree_tx_exit:
    kfree(pdata->tx_buff);
kfree_exit:
    kfree(pdata);
    return ret;
}
 
Last edited:

MrChips

Joined Oct 2, 2009
20,859
There are times when goto is the best language structure to solve error condition exits. The compiler/linker has no problem using gotos to optimize solutions and programmers should consider using them in a disciplined manner when it clarifies the program structure and readability during resource allocation and failure.
C:
/*
* called for each listed spigert device
* SO THIS RUNS FIRST, setup basic spi comm parameters here
* so it defaults to slow speed
*/
static int32_t spigert_spi_probe(struct spi_device * spi)
{
    struct comedi_spigert *pdata;
    int32_t ret;

    pdata = kzalloc(sizeof(struct comedi_spigert), GFP_KERNEL);
    if (!pdata)
        return -ENOMEM;

    spi->dev.platform_data = pdata;
    reinit_completion(&done);
    pdata->ping_pong = false;
    pdata->upper_lower = 0;
    pdata->tx_buff = kzalloc(SPI_BUFF_SIZE, GFP_DMA);
    if (!pdata->tx_buff) {
        ret = -ENOMEM;
        goto kfree_exit;
    }
    pdata->rx_buff = kzalloc(SPI_BUFF_SIZE, GFP_DMA);
    if (!pdata->rx_buff) {
        ret = -ENOMEM;
        goto kfree_tx_exit;
    }

    /*
     * Do only two chip selects for the Gertboard
     */

    dev_info(&spi->dev,
        "default setup: cd %d: %d Hz: bpw %u, mode 0x%x\n",
        spi->chip_select, spi->max_speed_hz, spi->bits_per_word,
        spi->mode);

    if (spi->chip_select == CSnA) {
        /*
         * get a copy of the slave device 0 to share with Comedi
         * we need a device to talk to the ADC
         *
         * create entry into the Comedi device list
         */
        INIT_LIST_HEAD(&pdata->device_entry);
        pdata->slave.spi = spi;
        /*
         * put entry into the Comedi device list
         */
        list_add_tail(&pdata->device_entry, &device_list);
        spi->mode = daqgert_devices[defdev0].spi_mode;
        spi->max_speed_hz = daqgert_devices[defdev0].max_speed_hz;
    }
    if (spi->chip_select == CSnB) {
        /*
         * we need a device to talk to the DAC
         */
        INIT_LIST_HEAD(&pdata->device_entry);
        pdata->slave.spi = spi;
        list_add_tail(&pdata->device_entry, &device_list);
        spi->mode = daqgert_devices[defdev0].spi_mode;
        spi->max_speed_hz = daqgert_devices[defdev0].max_speed_hz;
    }
    spi->bits_per_word = daqgert_devices[defdev0].spi_bpw;
    spi_setup(spi);

    /*
     * Check for basic errors
     */
    ret = spi_w8r8(spi, 0); /* check for spi comm error */
    if (ret < 0) {
        dev_err(&spi->dev, "spi comm error\n");
        ret = -EIO;
        goto kfree_rx_exit;
    }

    /* setup comedi part of driver */
    if (spi->chip_select == CSnA) {
        ret = comedi_driver_register(&daqgert_driver);
        if (ret < 0)
            goto kfree_rx_exit;

        if (gert_autoload)
            ret = comedi_auto_config(&spi->master->dev,
            &daqgert_driver, 0);

        if (ret < 0)
            goto kfree_rx_exit;
    }
    return 0;

kfree_rx_exit:
    kfree(pdata->rx_buff);
kfree_tx_exit:
    kfree(pdata->tx_buff);
kfree_exit:
    kfree(pdata);
    return ret;
}
I see a potential glitch.
kfree(pdata) is always executed.
kfree(pdata->tx_buffer) is always executed even when kfree_rx_exit is called.

Is this your intended program flow?
 

nsaspook

Joined Aug 27, 2009
7,179
I see a potential glitch.
kfree(pdata) is always executed.
kfree(pdata->tx_buffer) is always executed even when kfree_rx_exit is called.

Is this your intended program flow?
pdata has a preemptive return code to minimize driver failure execution time.
C:
pdata = kzalloc(sizeof(struct comedi_spigert), GFP_KERNEL);
    if (!pdata)
        return -ENOMEM;
Yes, they are sequentially allocated and deallocated with failures.
 

402DF855

Joined Feb 9, 2013
194
I haven't used lint in years, should dust it off one of these days. The compilers do a good job of emitting warnings which most experts agree should be given heed. I prefer to set the "warning level" to highest and eliminate or justify what gets flagged.
 

MrAl

Joined Jun 17, 2014
7,460
Well some people cant stop using it and it could make maintenance harder.
It has to be part of your methodology and of course that means limiting its use.
Over the years i've had this discussion with many programmers and they all say limiting its use is the key, but i've learned to not use it at all and there is in fact at least one language i know of that does not include a goto by design of its author, a computer scientist in Canada.
 

nsaspook

Joined Aug 27, 2009
7,179
There are plenty of languages without an explicit goto but almost all have a language feature that does the same thing but with usually much less efficiency than a explicit goto. Engineers use goto and write code in languages with goto because we need things that work in actual systems vs some egg-head computer scientist in Canada writing a proof on goto alternatives in a new language.
 
Top