This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Setting a watchpoint dynamically

Hello,

I am trying to spot a bug in which some memory corruption is happening. I don't know in advance at which address the corruption will happen, as it happens in some circular buffer. Maybe something is wrongly reading an address referring to this buffer and then writing there. I don't know.

Anyhow, I have found some way to store in some global variable mem_corruption_address the  data memory address at which the corruption before it happens, just in case it would happen, and what I want to do it call some watch_corruption_enable function at some point before code section in which the corruption may happen and watch_corruption_disable after this code section.

So here is the code which I have written : 

extern uint32_t const volatile* volatile mem_corruption_address;
uint32_t const volatile* volatile mem_corruption_address = NULL;

nrf_atomic_flag_t watch_corruption;

static void watch_corruption_set_watchpoint(void)
{
    if (watch_corruption) {
        DWT->COMP0     = (uintptr_t)mem_corruption_address;
        DWT->MASK0     = 0; // range = 1 × 32bit word.
        DWT->FUNCTION0 = (6<<0) // generate watch point debug event on WO match
            | (0<<7) // FUNCTION0.CYCMATCH == 0 for address comparison
            | (0<<8) // FUNCTIONn.DATAVMATCH == 0 for address comparison
            | (1<<5) // FUNCTIONn.EMITRANGE == 1 for trace packet generation
            ;
    }
}

static void watch_corruption_reset_watchpoint(void)
{
    DWT->FUNCTION0 = 0;
}

void watch_corruption_enable(void)
{
    nrf_atomic_flag_set(&watch_corruption);
}

void watch_corruption_disable(void)
{
    if (nrf_atomic_flag_clear_fetch(&watch_corruption)) {
        watch_corruption_reset_watchpoint();
    }
}

The watch_corruption_set_watchpoint is called after each valid writing to the potentially corrupted memory, so that a valid write won't trigger the watchpoint, and I am also sure that no subsequent valid writing can occur before watch_corruption_disable is called.

Now, I am debugging with the Segger JTracePro with Ozone IDE (so I compiled my FW with ENABLE_TRACE #define'd). My hope was after the watchpoint is triggered I can browse back the instruction trace to find what made the corruption.

So, my problem is the following : instead of what I expected/hoped to get, I get Ozone more or less frozen at some point of time, I cannot stop the debugging session. I need to for the Ozone application stop from system. So, it seems that what I am doing it messing up how Ozone interfaces with the JTracePro.

I would like to know how to do it properly. 

Parents
  • Answering to myself : I did the experiment again, and actually Ozone does not get frozen --- that was probably another issue --- but the debugger does not halt and show the instruction where the watchpoint is hit, although I can see in the RTTlogging that the corruption is still happening by testing the memory in the sequel. 

  • Once again answering to myself. I did the following plain experiment, calling the test_dwt  function below close to the very beginning of my application (but after logger init).

    void test_dwt(void)
    {
        NRF_LOG_WARNING("DWT->CTRL = 0x%08x",DWT->CTRL);
    
        uint32_t dummy;
    
        DWT->COMP0 = &dummy;
        DWT->MASK0 = 0;
        DWT->FUNCTION0 = (6<<0) // generate watchpoint data trace packet for WO access
            | (0<<7) // FUNCTION0.CYCMATCH == 0 for address comparison
            | (0<<8) // FUNCTIONn.DATAVMATCH == 0 for address comparison
            | (0<<5) // FUNCTIONn.EMITRANGE == 0 for trace packet conveying PC value & data value
            ;
    
        *(uint32_t volatile*)&dummy = 0;
    
        NRF_LOG_WARNING("DWT->FUNCTION0 = 0x%08x",DWT->FUNCTION0);
        DWT->FUNCTION0 = 0;
    
        NRF_LOG_FLUSH();
        for(;;) {}
    }

    Here is what I get in the output Cry

    <warning> app: DWT->CTRL = 0x0000978C
    <warning> app: DWT->FUNCTION0 = 0x0000978C
    Transfer rate: 0 KByte/s Bytes written: 105 Byte   

    The 3 MSBits of DWT->CTRL are the DWT_CTRL.NUMCOMP field which in ARM v7M reference manual are documented as giving the number of implemented comparators. According to nRF52840 data sheet there are two comparators, so this should be 2 instead of 0. Then why so ?

    Maybe there is some power domain setting to enable to whole debugging peripheral ?

  • Hello,

    This is something I need to look more into as I don't know the answer. But I suspect there is a conflict between the debugger and code as both try to access DWT.

  •  Thank you for paying attention to my ticket.

    In the latest experiment (that in which I call some test_dwt function) there is no debugger running, just the RTTlogger through a JLink probe.

    BTW I did some mistake in the code comments, but I cannot re-edit my posts to fix them. 

  • To ensure the device is not in debug mode, could you try to do a pin-reset or power-cycle prior to running this test? I've previously tried using DWT as a stack guard to catch stack overflows, and I think I remember some similiar problems.

    Here's the code I used:

    #define STACK_LIMIT 0x2003e000
    
    void DebugMon_Handler(void)
    {
        NRF_LOG_INFO("Debug Monitor interrupt triggered");
        NRF_LOG_INFO("Current SP 0x%x", __get_MSP());
        NRF_LOG_FINAL_FLUSH();
        for(;;);
    }
    
    
    static void dwt_stack_guard_init()
    {
        uint32_t num;
    
        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk | CoreDebug_DEMCR_MON_EN_Msk;
    
        NRF_LOG_INFO("CoreDebug->DEMCR : 0x%x", CoreDebug->DEMCR);
        
        /* Using the highest programmable int. priority which is reserved to the Softdevice, and
           because of that we must configure the priority after ble_stack_init(). This bypasses the
           Softdevice's internal int. priority validation check. */
        NVIC_SetPriority(DebugMonitor_IRQn, _PRIO_SD_HIGH); 
        NVIC_EnableIRQ(DebugMonitor_IRQn);
    
        num = (DWT->CTRL & DWT_CTRL_NUMCOMP_Msk) >> DWT_CTRL_NUMCOMP_Pos;
        NRF_LOG_INFO("Number of DWT comparators: %d", num);
    
        DWT->COMP0 = STACK_LIMIT;
        DWT->MASK0 = 4;
        DWT->FUNCTION0 = (7 << DWT_FUNCTION_FUNCTION_Pos);
         
        NRF_LOG_INFO("DWT->COMP0 : 0x%x", DWT->COMP0);
        NRF_LOG_INFO("DWT->MASK0 : 0x%x", DWT->MASK0);
        NRF_LOG_INFO("DWT->FUNCTION0 : 0x%x", DWT->FUNCTION0);
    
    }

  • OK, here is the new test_dwt function.

    void test_dwt(void)
    {
        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk | CoreDebug_DEMCR_MON_EN_Msk;
    
        NRF_LOG_INFO("CoreDebug->DEMCR : 0x%x", CoreDebug->DEMCR);
    
        NRF_LOG_WARNING("DWT->CTRL = 0x%08x",DWT->CTRL);
    
        uint32_t dummy;
    
        DWT->COMP0 = &dummy;
        DWT->MASK0 = 0;
        DWT->FUNCTION0 = (6<<0) // FUNCTION0.FUNCTION = 6, generate watchpoint debug event for WO access
            | (0<<7) // FUNCTION0.CYCMATCH == 0 for address comparison
            | (0<<8) // FUNCTIONn.DATAVMATCH == 0 for address comparison
            | (0<<5) // FUNCTIONn.EMITRANGE == 0, no meaning when FUNCTION = 6
            ;
    
        *(uint32_t volatile*)&dummy = 0;
    
        NRF_LOG_WARNING("DWT->FUNCTION0 = 0x%08x",DWT->FUNCTION0);
        DWT->FUNCTION0 = 0;
    
        NRF_LOG_FLUSH();
        for(;;) {}
    }
    

    And here is what I get now, it looks better : 

    <info> app: CoreDebug->DEMCR : 0x1010000
    <warning> app: DWT->CTRL = 0x40000001
    <warning> app: DWT->FUNCTION0 = 0x01000006
    


  • Dear ,

    Happy new year 2021, all the best to you !

    One more question : can you remember which logger backend you used with the code which you provided. Was it through the UART, right ?

    I adapted this code to my platform and breakpoint condition, and I tried that with the RTTlogger, and it seems that if I start to play with the DebugMonitor_IRQn and overload the DebugMon_Handler, then when the breakpoint condition occurs, the trace stops, so the final trace made by DebugMon_Handler does not go to the output, the log gets kind of frozen, and nothing more seems to happen.

    I tried to connect the debugger in this situation, and the only thing I can see is that I am stuck in the FreeRTOS calling vPortSuppressTicksAndSleep for the Idle task.

Reply
  • Dear ,

    Happy new year 2021, all the best to you !

    One more question : can you remember which logger backend you used with the code which you provided. Was it through the UART, right ?

    I adapted this code to my platform and breakpoint condition, and I tried that with the RTTlogger, and it seems that if I start to play with the DebugMonitor_IRQn and overload the DebugMon_Handler, then when the breakpoint condition occurs, the trace stops, so the final trace made by DebugMon_Handler does not go to the output, the log gets kind of frozen, and nothing more seems to happen.

    I tried to connect the debugger in this situation, and the only thing I can see is that I am stuck in the FreeRTOS calling vPortSuppressTicksAndSleep for the Idle task.

Children
  • Hi and happy new year! 

    Yes, I only tested with the UART backend so I didn't have to keep the debugger connected. Have you tried testing with and without the NRF_LOG_FINAL_FLUSH()  line in your DebugMon_Handler?

  • Actually, it gets to the same result (everything looking like frozen) even with an empty DebugMon_Handler

    I also tried this : I carried out the procedure to get up to the breakpoint with the RTTlogger running. When the breakpoint occurs then everything gets kind of frozen, so I exit the RTTlogger and connect to the app with gdb through the Segger gdb server. When doing this I can see this backtrace (I just replaced the full path by …) : 

    (gdb) bt
    #0 vPortSuppressTicksAndSleep (xExpectedIdleTime=5120)
    at …/nrf-sdk/external/freertos/portable/CMSIS/nrf52/port_cmsis_systick.c:254
    #1 0x00002616 in prvIdleTask (pvParameters=0x0 <__isr_vector>)
    at …/nrf-sdk/external/freertos/source/tasks.c:3321
    #2 0x00000548 in pxPortInitialiseStack (pxTopOfStack=0x1400 <xQueueSemaphoreTake+204>, pxCode=0xa5a5a5a5,
    pvParameters=0xa5a5a5a5)
    at …/nrf-sdk/external/freertos/portable/CMSIS/nrf52/port_cmsis.c:147
    Backtrace stopped: previous frame identical to this frame (corrupt stack?)

    I also did the experiment with having DebugMon_Handler doing some border effect on some static flag like this : 

    uint32_t titi;
    void DebugMon_Handler(void)
    {
    titi = 1;
    }


    and what happens is that when inspecting titi with the debugger, it is equal to 0, the startup value, so it seems that either DebugMon_Handler is not entered when the interrupt occurs, or the execution goes again to the startup and gets frozen at some point. 

  • Yes, I agree, it looks like maybe the DebugMonitor_IRQn is not being raised, and that the program just stays in the "wait for event" loop. Are you sure you are meeting the conditions to trigger this event? Does the ISR get invoked as expected if you call NVIC_SetPendingIRQ(DebugMonitor_IRQn )?

  • I tried to call NVIC_SetPendingIRQ(DebugMonitor_IRQn), both before starting FreeRTOS, and also after FreeRTOS is started, at the beginning of a thread function.

    In both cases, I cannot see the trace output of DebugMon_Handler happening, and the program goes past the call of NVIC_SetPendingIRQ(DebugMonitor_IRQn) without any effect of it, and continues what is next.

    Please note that in both cases, I had called 

     NVIC_SetPriority(DebugMonitor_IRQn, _PRIO_SD_HIGH);
    NVIC_EnableIRQ(DebugMonitor_IRQn);

    at some point of time before calling NVIC_SetPendingIRQ(DebugMonitor_IRQn ).

  • FYI, I also had a look at the .elf.map file, and I can see that the DebugMon_Handler is there, and it is from the C source file where I typed it.

    So, the problem is not that another implementation of it is called by way of some erroneous linking.

Related