Почему задержка IRQ в моем обработчике прерываний ARM всегда одинакова, независимо от прерываемой инструкции?

#c #arm

#c #arm

Вопрос:

Я пытаюсь применить тип атаки по побочному каналу, о которой я читал в этой статье, которая пытается определить состояние выполнения по различиям в задержках IRQ на MCU с процессором cortex M4. Атака тщательно прерывает инструкции, которые выполняются сразу после перехода, и измеряет задержку прерывания. Когда разные ветви имеют инструкции разной длины, вы можете посмотреть на задержку прерывания, чтобы определить, в какой из этих ветвей произошло прерывание и произошла утечка части состояния программы.

Я написал простую функцию, которую я хочу атаковать описанным выше способом. Я использую системный таймер для генерации прерывания в правильный момент времени. Чтобы получить начальное хорошее значение для таймера прерывания, я использовал GDB, чтобы остановить программу на целевой строке, чтобы увидеть значение системы в то время.

Я реализовал очень простой обработчик прерываний, который

  1. загружает значение системного таймера из памяти
  2. вычитает это значение из значения перезагрузки, чтобы получить время, прошедшее с момента прерывания (т.е. задержку IRQ).
  3. очищает прерывание и
 void __attribute__((interrupt("IRQ"))) SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
    SysTick->CTRL amp;= 0xfffffffe;                                // disable SysTick (~SysTick_CTRL_ENABLE_Msk)
    *timer_value = SysTick->VAL;                                // capture counter value (as quickly as possible)
    *timer_value = SysTick->LOAD - *timer_value;                    // subtract it from reload value to get IRQ latency
    SysTick->VAL = 0;                                           // reset initial value
}   
 

Однако я обнаружил, что я всегда получаю одинаковую задержку IRQ, независимо от прерванной инструкции. Я ожидаю, что задержка прерывания будет больше, когда прерывается более длинная инструкция.

Это функция, которую я написал для тестирования атаки

 extern uint32_t *timer_value;
int sample_function(int *a, int *b){
    /*
     * function description -- store the smallest of the two value in a, if MEASURE_CYCLESS defined return the number
     * of clock cycles that have been elapsed since the timer has been started
     * r0 contains pointer to a
     * r1 contains pointer to b
     */

    __asm volatile(
        /*  push working registers */
        "PUSH {r4-r8} n"
        /* move counter into r8 */
        "MOV r8, #10 n"
        /* begin loop */
        "begin_loop: n"
        /* decrement counter variable*/
        "SUB r8, r8, #1 n"
        /* if counter variable not equal to 0, jump back to start of loop */
        "CMP r8, #0 n"
        /* if r8 not equal to 0, jump back to begin of loop*/
        "BNE begin_loop n"
        /* load a into r2 */
        "LDR r2, [r0] n"
        /* load b into r3 */
        "LDR r3, [r1] n"
        /*  store a-b in r4, setting status flags -- if result is 0 Z flag is set */
        "SUBS r4, r2, r3 n"
        /* if a-b positive, a is larger  otherwise, b is larger (assuming a not equal to b)  */
        "BPL a_larger n"
#ifdef SPY
        /* load address of (*timer_value) into r4 -- use of LDR pseudo-instruction places constant in a literal pool*/
        "LDR r4, =timer_value n"
        /* Load (*timer_value) into r4 */
        "LDR r4, [r4] n"
        /* load address of Systick VAL into r5 */
        "LDR r5, =0xe000e018 n"
        /* Load value at address stored in R5 (= Systick Val) */
        "LDR r5, [r5] n"
        /* Move Systick Val into adress stored at r4 (= *timer_value = address of timer_value)*/
        "STR r5, [r4] n"
#endif
        "NOP n"
        /*instruction that gets interrupted -- swap value*/
        "STR r2, [r1] n"
        /* load value at this address into r0 (return value) */
        "STR r3, [r0] n"
        "B end n"
        "a_larger: n"
        "MOV r0, #0 n"              // instruction that gets interrupted
        "end: POP    {r4-r8}"
            );     // pop working registers
}

 

Обратите внимание, что раздел кода в #define блоке используется для автоматического определения правильного значения перезагрузки таймера (вместо использования GDB), но в настоящее время я не использую значение, полученное таким образом.
У меня также есть пустой цикл, чтобы немного задержать инструкцию, которая должна быть прервана.

Прерываемая инструкция — это инструкция сразу после #define блока. Когда я удаляю NOP инструкцию, я все равно получаю ту же задержку прерывания. Когда я увеличиваю или уменьшаю значение таймера (чтобы прервать некоторые циклы раньше или позже) Я также по-прежнему получаю ту же задержку IRQ.

Я что-то здесь упускаю? Есть ли какое-то поведение, о котором я не знаю? Кроме того, важно ли использовать атрибут __attribute__((interrupt("IRQ")) для обработчика прерываний?

Комментарии:

1. Я не могу себе представить, как GDB может получить точное считывание таймера. Похоже, тонна кода делает все, что вы пытаетесь сделать. Выполните тест на языке ассемблера, как на переднем плане, так и на прерывании, таким образом, вам не нужно нажимать какие-либо регистры, вы можете просто иметь несколько строк isr, ldr rd,[rn]; bx lr, где ldr считывает системный таймер. все для предварительной настройки теста.

2. каково ваше определение длинной инструкции. задача переднего плана может представлять собой цикл таких вещей, как nop, а затем такие вещи, как хранилища и такие вещи, как чтение, и тому подобное.

3. несомненно, будут различия в задержках, поскольку шина (ses) может быть загружена по-разному, Что влияет на время выборки isr или выполнение isr и т. Д. (ну, для systick это обработчик исключений, а не обработчик прерываний). но можете ли вы добраться туда, где вы можете это увидеть.

4. нет смысла пытаться установить значение тайм-аута, чтобы попытаться настроить таргетинг на инструкцию, если это подразумевалось здесь, просто выберите периодическое прерывание. период. задача переднего плана может сказать, проверить регистр, возможно, вы увеличиваете значение в обработчике, затем разветвляетесь и распечатываете один или два элемента, собранные в обработчике. затем вернитесь к циклу, который в основном представляет собой инструкции, которые вы надеетесь прервать. поэтому период должен быть достаточно длинным, чтобы это произошло. например, один раз в секунду.

5. вы определенно не хотите создавать для отладки и не видите, как здесь помогает отладчик, но можете видеть, как это мешает.

Ответ №1:

Это то, о чем я думал и комментировал.

бутстрэп

 .thumb_func
reset:
    bl notmain
    ldr r4,=0xE000E018
    ldr r0,=0xE000E010
    mov r1,#7
    str r1,[r0]
    b hang
.thumb_func
hang:   
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    b hang
 

настройка uart и системного интерфейса

 void notmain ( void )
{
    uart_init();
    hexstring(0x12345678);
    
    PUT32(STK_CSR,4);
    PUT32(STK_RVR,0xF40000);
    PUT32(STK_CVR,0x00000000);
    //PUT32(STK_CSR,7);
}
 

обработчик событий

 .thumb_func
.globl systick_handler
systick_handler:
    ldr r0,[r4]
    ldr r5,[sp,#0x18]
    push {r0,lr}
    bl hexstrings
    mov r0,r5
    bl hexstring
    pop {r0,pc}
 

возьмите таймер и адрес прерванной инструкции и распечатайте их.

 00F3FFF4 08000054 
00F3FFF4 08000056 
00F3FFF4 08000058 
00F3FFF4 0800005A 
00F3FFF4 0800005C 
00F3FFF4 0800005E 
00F3FFF4 08000054 
00F3FFF4 08000056 
00F3FFF4 08000058 
00F3FFF4 0800005A 
00F3FFF4 08000050 


08000050 <hang>:
 8000050:   bf00        nop
 8000052:   bf00        nop
 8000054:   bf00        nop
 8000056:   bf00        nop
 8000058:   bf00        nop
 800005a:   bf00        nop
 800005c:   bf00        nop
 800005e:   e7f7        b.n 8000050 <hang>
 

Из документации ARM.

Задержка прерывания

Существует максимальная задержка в двенадцать циклов от утверждения прерывания до выполнения первой инструкции ISR, когда к памяти, к которой осуществляется доступ, не применяются состояния ожидания. Когда реализована опция FPU и активен контекст с плавающей запятой, а отложенное стекирование не включено, эта максимальная задержка увеличивается до двадцати девяти циклов. Первые инструкции, которые должны быть выполнены, извлекаются параллельно с выталкиванием стека.

И эту последнюю строку мы, возможно, видим здесь. Вы можете попробовать различные инструкции, но эта архитектура имеет возможность перезапускать длительные инструкции (чтение и нажатие / нажатие, умножение и тому подобное). Я думаю, чтобы увидеть большую разницу в задержке, вам может потребоваться создать конфликт шины или общих ресурсов (против инструкций)

Кроме того, системный вызов является исключением, а не прерыванием, поэтому могут быть некоторые различия в отношении задержки.