Search code examples
ccorruptionbare-metallpc

Data Corruption when Segregating Functions into Different Files


This issue has me vexed. I am doing some bare-metal programming in C for a custom sensor board based on the LPC15XX series microcontrollers. The platform amounts to a bunch of I2C sensors and a BlueTooth transmitter hooked up to one of the UARTs. This is NOT one of the LPCxpresso (or similar) boards; it is of a custom design.

Until today, everything was working as expected with all test code in one monolithic file. As the code grew, I decided to break the main functional pieces into separate source files to easier management. Unfortunately, as soon as I did this, the program began segfaulting.

The code being moved is a copy + paste of the original monolithic file. I have stepped through using GDB over a SWD link. It appears that one of my static variables used to hit the internal ROM drivers is being NULLified. Dereferencing this later causes the fault.

Setting a watch on the static variable, I can see the line of code blowing it up is in the I2C initialization routines. What is problematic is the fact that init is done using the provided in-ROM driver routines from NXP. Furthermore, this same code worked without issue when it was all in the same file.

Questions

  • What would nuke a file-level static variable like that? To my understanding, the variable shouldn't be on the stack so I don't see a buffer overflow causing this.

  • Why would moving the code between files expose this? Shouldn't it all be compiled and linked into a single namespace anyway?

Replies to Questions in Comments

  • uart_instance is defined once, in usart.c and only ever directly (by name) written to once as part of the call to uart_setup.

  • Likewise, i2c_instance is defined in main.c (I haven't moved the I2C functions yet). Is static and declared identically to uart_instance. I have updated the code below to include all static variables from both files.

  • The pointer is actually NULLed out in the call to pI2CApi->i2c_set_bitrate(hI2C, BASE_CLOCK, I2C_BAUD);, as determined by setting a watch on the memory in question.

  • I cannot directly trace into any of the ROM driver routines. They are provided by the uC and are accessed through a double pointer provided at a fixed memory address. I have checked these pointer objects (hUart and pUartApi) and they retain their correct values; only uart_instance gets clobbered.

  • I created and dumped the map file for this application; it did not contain a reference to uart_instance (or any static variable, for that matter). Strange. I can post the full contents on request (is a bit long).

Code Vomit

A word of warning, this code is extremely prototypical and unoptimized. A good most of it was copied shamelessly from the datasheet.

Definition of static variables (usart.c)

static UARTD_API_T* pUartApi;           // USART API function addr table
static UART_HANDLE_T  uart_instance;    // Raw storage for USART API
static UART_HANDLE_T* hUart;            // Handle to USART API

Definition of static variables (main.c)

static I2CD_API_T* pI2CApi;             // I2C API function addr table
static I2C_HANDLE_T  i2c_instance;      // Raw storage for I2C API
static I2C_HANDLE_T* hI2C;              // Handle to I2C API

Init of static handle (usart.c)

int setupUSART0(int sys_clock, int baud) {

    UART_CONFIG_T config;
    uint32_t frg_val = 0;
    uint32_t size_in_bytes;

    // Enable USART0 clock
    LPC_SYSCON->SYSAHBCLKCTRL1 |= ((1UL << 17));

    // Configure USART clock divider
    LPC_SYSCON->UARTCLKDIV = (uint8_t)USART_PERIPH_PRESCALE;

    // Configure USART0 pins
    LPC_SWM->PINASSIGN0 = 0;
    LPC_SWM->PINASSIGN0 |= ((uint8_t)18) << 0;     // PIO0_18, tx
    LPC_SWM->PINASSIGN0 |= ((uint8_t)13) << 8;     // PIO0_13, rx
    LPC_SWM->PINASSIGN0 |= ((uint8_t)0xFF) << 16;  // Not wired, rts
    LPC_SWM->PINASSIGN0 |= ((uint8_t)0xFF) << 24;  // Not wired, cts

    // Get handle to USART API
    pUartApi = getUartDriver();

    // Initialize memory for UART API
    size_in_bytes = pUartApi->uart_get_mem_size();
    if (10 < (size_in_bytes / 4)) return -1;
    hUart = pUartApi->uart_setup(LPC_USART0_BASE, (uint8_t*)&uart_instance);    // <- uart_instance initialized here

    // Initialize USART API
    config.sys_clk_in_hz = sys_clock / USART_PERIPH_PRESCALE;
    config.baudrate_in_hz = baud;
    config.config = 1;              // 8N1
    config.sync_mod = 0;
    config.error_en = 0;
    frg_val = (pUartApi->uart_init(hUart, &config) << 8) | 0xFF;

    // Configure USART fractional divider
    if (!frg_val) return -1;
    LPC_SYSCON->FRGCTRL     = frg_val;

    // Enable USART0 in NVIC
    NVIC->ISER0 |= ((1UL << 21));

    // Enable UART0 interrupts
    LPC_USART0->INTENSET |= ((1UL << 0));

    return 0;
}

I2C init code that breaks the pointer (main.c)

ErrorCode_t setupI2C() {

    ErrorCode_t err;

    // Enable I2C clock

    LPC_SYSCON->SYSAHBCLKCTRL1 |= ((1UL << 13));
    LPC_I2C0->DIV = 0x0078;    // 120 decimal

    LPC_I2C0->MSTTIME = 0x00;    // SCL high / low = 2 clocks each

    //DEBUG
    LPC_SWM->PINENABLE1 = 0x00;

    // Enable interrupts
    NVIC->ISER0 |= ((1UL << 24));                // ISE_I2C0
    LPC_I2C0->INTENSET |= ((1UL << 0));         // MSTPENDINGEN
    LPC_I2C0->INTENSET |= ((1UL << 8));         // SLVPENDINGEN

    // Get handle to I2C API
    pI2CApi = getI2CDriver();

    // Initialize memory for UART API
    hI2C = pI2CApi->i2c_setup(LPC_I2C0_BASE, (uint32_t*)&i2c_instance);

    // This NULLS uart_instance somehow
    // Set bitrate
    err = pI2CApi->i2c_set_bitrate(hI2C, BASE_CLOCK, I2C_BAUD);

    // Set master mode
    LPC_I2C0->CFG = ((1UL << 0));               // MSTEN

    return err;
}

Solution

  • I see nothing wrong with the code above.

    Generally, an error that can be accidentally introduced by splitting the code into several files is a duplication of static variables. For example, there could be a static UART_HANDLE_T uart_instance; both in main.c and usart.c (or even weirdly in some .h included by both). Everything will compile and link OK, but functions from main.c and usart.c will use unrelated variables, and the program logic will change with potential for data corruption.

    Now, this is a generic remark not directly related to your code. There is some chance it helps... Good luck hunting the bug!