The Embedded New Testament

The "Holy Bible" for embedded engineers


Project maintained by theEmbeddedGeorge Hosted on GitHub Pages — Theme by mattgraham

Static Analysis for Embedded Systems

Leveraging static analysis tools and techniques to detect defects, ensure code quality, and prevent runtime failures in embedded software

📋 Table of Contents

🎯 Overview

Static analysis examines source code without executing it, identifying potential defects, security vulnerabilities, and code quality issues. In embedded systems, static analysis is crucial for catching problems early in the development cycle, especially in safety-critical applications where runtime failures can have severe consequences.

Why Static Analysis is Critical in Embedded Systems

🔑 Key Concepts

Static Analysis Categories

┌─────────────────────────────────────────────────────────────┐
│                Static Analysis Categories                   │
├─────────────────────────────────────────────────────────────┤
│ Syntax Analysis      │ Grammar and language rule checking  │
│ Semantic Analysis    │ Meaning and logic verification      │
│ Data Flow Analysis   │ Variable usage and data tracking    │
│ Control Flow Analysis│ Execution path and logic flow       │
│ Type Checking        │ Data type safety and compatibility  │
│ Security Analysis    │ Vulnerability and threat detection   │
└─────────────────────────────────────────────────────────────┘

Analysis Techniques

🧠 Core Concepts

Data Flow Analysis Fundamentals

Data flow analysis tracks how data moves through your program:

// Example: Potential null pointer dereference
void process_data(uint8_t *data, uint32_t length) {
    if (data == NULL) {
        return;  // Early return
    }
    
    // Data flow analysis should recognize 'data' is non-null here
    for (uint32_t i = 0; i < length; i++) {
        data[i] = process_byte(data[i]);  // Safe access
    }
}

Control Flow Analysis

Control flow analysis examines the execution paths through your code:

// Example: Unreachable code detection
void hardware_control(uint8_t command) {
    if (command == CMD_START) {
        start_hardware();
    } else if (command == CMD_STOP) {
        stop_hardware();
    } else if (command == CMD_RESET) {
        reset_hardware();
    } else {
        // This path should be unreachable if command validation is proper
        handle_invalid_command(command);
    }
}

Type Safety Analysis

Type safety analysis ensures data types are used correctly:

// Example: Type mismatch detection
typedef struct {
    uint32_t id;
    uint16_t value;
} sensor_data_t;

void process_sensor_data(sensor_data_t *sensor) {
    // Static analysis should flag this type mismatch
    uint8_t temp = sensor->value;  // uint16_t to uint8_t conversion
}

🛠️ Implementation

Basic Static Analysis Framework

// Simple static analysis rule structure
typedef struct {
    uint32_t rule_id;
    const char *rule_name;
    const char *description;
    uint32_t severity;  // 1=Low, 2=Medium, 3=High, 4=Critical
    bool (*check_function)(const char *code, uint32_t line);
} static_analysis_rule_t;

// Rule checking function type
typedef bool (*rule_checker_t)(const char *code, uint32_t line);

// Analysis result structure
typedef struct {
    uint32_t line_number;
    uint32_t rule_id;
    uint32_t severity;
    const char *message;
    const char *suggestion;
} analysis_result_t;

#define MAX_RESULTS 100
#define MAX_RULES 50

static_analysis_rule_t rules[MAX_RULES];
analysis_result_t results[MAX_RESULTS];
uint32_t rule_count = 0;
uint32_t result_count = 0;

Rule Implementation Examples

// Check for potential null pointer dereference
bool check_null_pointer_deref(const char *code, uint32_t line) {
    // Simple pattern matching for demonstration
    if (strstr(code, "->") && strstr(code, "NULL")) {
        return true;  // Potential issue found
    }
    return false;
}

// Check for uninitialized variables
bool check_uninitialized_vars(const char *code, uint32_t line) {
    // Look for variable declarations without initialization
    if (strstr(code, "uint32_t") || strstr(code, "int") || strstr(code, "char")) {
        if (!strstr(code, "=") && strstr(code, ";")) {
            return true;  // Potential uninitialized variable
        }
    }
    return false;
}

// Check for magic numbers
bool check_magic_numbers(const char *code, uint32_t line) {
    // Look for hardcoded numbers that might be magic numbers
    if (strstr(code, " 0x") || strstr(code, " 0b")) {
        return true;  // Potential magic number
    }
    return false;
}

Analysis Engine

// Register a new analysis rule
uint32_t register_analysis_rule(const char *name, const char *desc, 
                               uint32_t sev, rule_checker_t checker) {
    if (rule_count >= MAX_RULES) {
        return UINT32_MAX; // Error
    }
    
    rules[rule_count].rule_id = rule_count;
    rules[rule_count].rule_name = name;
    rules[rule_count].description = desc;
    rules[rule_count].severity = sev;
    rules[rule_count].check_function = checker;
    
    return rule_count++;
}

// Analyze a single line of code
void analyze_code_line(const char *code, uint32_t line_number) {
    for (uint32_t i = 0; i < rule_count; i++) {
        if (rules[i].check_function(code, line_number)) {
            // Issue found, add to results
            if (result_count < MAX_RESULTS) {
                results[result_count].line_number = line_number;
                results[result_count].rule_id = i;
                results[result_count].severity = rules[i].severity;
                results[result_count].message = rules[i].description;
                results[result_count].suggestion = "Review and fix the issue";
                result_count++;
            }
        }
    }
}

// Generate analysis report
void generate_analysis_report(void) {
    printf("=== Static Analysis Report ===\n");
    printf("Total Issues Found: %u\n", result_count);
    
    uint32_t critical_count = 0;
    uint32_t high_count = 0;
    uint32_t medium_count = 0;
    uint32_t low_count = 0;
    
    for (uint32_t i = 0; i < result_count; i++) {
        switch (results[i].severity) {
            case 4: critical_count++; break;
            case 3: high_count++; break;
            case 2: medium_count++; break;
            case 1: low_count++; break;
        }
    }
    
    printf("Critical: %u, High: %u, Medium: %u, Low: %u\n",
           critical_count, high_count, medium_count, low_count);
    
    // Print detailed results
    for (uint32_t i = 0; i < result_count; i++) {
        printf("Line %u [%s]: %s\n", 
               results[i].line_number,
               get_severity_string(results[i].severity),
               results[i].message);
    }
}

🚀 Advanced Techniques

Custom Rule Development

// Advanced rule for checking interrupt safety
bool check_interrupt_safety(const char *code, uint32_t line) {
    // Check for potential issues in interrupt context
    if (strstr(code, "malloc") || strstr(code, "free")) {
        return true;  // Dynamic allocation in interrupt context
    }
    
    if (strstr(code, "printf") || strstr(code, "sprintf")) {
        return true;  // I/O operations in interrupt context
    }
    
    return false;
}

// Rule for checking hardware register access patterns
bool check_register_access_patterns(const char *code, uint32_t line) {
    // Look for proper register access patterns
    if (strstr(code, "0x") && strstr(code, "=")) {
        // Check if it's a volatile pointer access
        if (!strstr(code, "volatile")) {
            return true;  // Missing volatile qualifier
        }
    }
    
    return false;
}

Integration with Build System

// CMake integration example
void integrate_with_cmake(void) {
    printf("Static analysis integration with CMake:\n");
    printf("1. Add cppcheck target\n");
    printf("2. Configure analysis rules\n");
    printf("3. Set up pre-commit hooks\n");
    printf("4. Integrate with CI/CD pipeline\n");
}

// Pre-commit hook integration
void setup_pre_commit_hooks(void) {
    printf("Setting up pre-commit hooks:\n");
    printf("1. Install pre-commit framework\n");
    printf("2. Configure static analysis tools\n");
    printf("3. Set up rule violations as warnings/errors\n");
    printf("4. Configure automatic fixes where possible\n");
}

Advanced Pattern Recognition

// Check for resource leak patterns
bool check_resource_leaks(const char *code, uint32_t line) {
    // Look for file handles, memory allocations, etc.
    if (strstr(code, "fopen") && !strstr(code, "fclose")) {
        return true;  // Potential file handle leak
    }
    
    if (strstr(code, "malloc") && !strstr(code, "free")) {
        return true;  // Potential memory leak
    }
    
    return false;
}

// Check for race condition patterns
bool check_race_conditions(const char *code, uint32_t line) {
    // Look for shared variable access without protection
    if (strstr(code, "global_") && strstr(code, "=")) {
        if (!strstr(code, "mutex") && !strstr(code, "lock")) {
            return true;  // Potential race condition
        }
    }
    
    return false;
}

⚠️ Common Pitfalls

False Positives

Performance Impact

Tool Limitations

✅ Best Practices

Tool Selection

  1. Multiple Tools: Use complementary tools for comprehensive coverage
  2. Rule Customization: Adapt rules to your specific embedded domain
  3. Integration: Integrate analysis into your development workflow
  4. Automation: Automate analysis in CI/CD pipelines

Rule Configuration

  1. Severity Levels: Set appropriate severity levels for different rule types
  2. False Positive Management: Document and suppress legitimate false positives
  3. Rule Evolution: Continuously improve rules based on project experience
  4. Team Training: Educate team members on analysis results

Integration Strategy

  1. Early Integration: Start static analysis early in development
  2. Incremental Adoption: Gradually increase analysis coverage
  3. Quality Gates: Use analysis results as quality gates
  4. Continuous Improvement: Regularly review and update analysis rules

💡 Interview Questions

Basic Questions

Q: What is the difference between static and dynamic analysis? A: Static analysis examines code without execution, identifying potential issues through code inspection. Dynamic analysis runs the code and monitors its behavior at runtime. Static analysis catches issues early but may have false positives, while dynamic analysis finds actual runtime issues but requires execution.

Q: What are the main benefits of static analysis in embedded systems? A: Early defect detection, improved code quality, compliance with safety standards, reduced testing costs, identification of security vulnerabilities, and prevention of runtime failures that could be catastrophic in safety-critical systems.

Intermediate Questions

Q: How would you handle false positives in static analysis? A: Document legitimate false positives, configure rules appropriately, use suppressions for known cases, continuously improve rule quality, and involve the team in rule refinement to reduce false positive rates over time.

Q: What types of issues can static analysis detect in embedded C code? A: Null pointer dereferences, uninitialized variables, memory leaks, buffer overflows, type mismatches, unreachable code, resource leaks, race conditions, and violations of coding standards and best practices.

Advanced Questions

Q: How would you integrate static analysis into a continuous integration pipeline for embedded systems? A: Configure analysis tools to run automatically on code commits, set up quality gates based on analysis results, integrate with build systems like CMake, use pre-commit hooks for immediate feedback, and configure reporting and notification systems for the team.

Q: How do you balance static analysis thoroughness with build performance? A: Use incremental analysis where possible, configure tools to analyze only changed files, run comprehensive analysis during nightly builds, use parallel analysis on multiple cores, and configure analysis depth based on build type (debug vs. release).


Next Steps: Explore Dynamic Analysis for runtime behavior analysis or Code Coverage for testing completeness assessment.