Почти все, что вы хотели знать про плавающую точку в ARM, но боялись спросить

    Привет, Хабр! В этой статье я хочу рассказать про работу с плавающей точкой для процессоров с архитектурой ARM. Думаю, эта статья будет полезна прежде всего тем, кто портирует свою ОС на ARM-архитектуру и при этом им нужна поддержка аппаратной плавающей точки (что мы и делали для Embox, в котором до этого использовалась программная реализация операций с плавающей точкой).

    Итак, приступим.

    Флаги компилятора


    Для поддержки плавающей точки необходимо передавать правильные флаги компилятору. Беглое гугление нас приводит к мысли, что особенно важны две опции: -mfloat-abi и -mfpu. Опция -mfloat-abi задает ABI для работы с плавающей точкой и может иметь одно из трех значений: ‘soft’, ‘softfp’ и ‘hard’. Вариант ‘soft’, как и следует из названия, говорит компилятору использовать встроенные вызовы функций, для программной работы с плавающей точкой (этот вариант и использовался раньше). Остальные два ‘softfp’ и ‘hard’ рассмотрим немного позже, после рассмотрения опции -mfpu.

    Флаг -mfpu и версия VFP


    Опция -mfpu, как написано в онлайн-документации gcc, позволяет задать тип аппаратуры и может принимать следующие варианты:
    ‘auto’, ‘vfpv2’, ‘vfpv3’, ‘vfpv3-fp16’, ‘vfpv3-d16’, ‘vfpv3-d16-fp16’, ‘vfpv3xd’, ‘vfpv3xd-fp16’, ‘neon-vfpv3’, ‘neon-fp16’, ‘vfpv4’, ‘vfpv4-d16’, ‘fpv4-sp-d16’, ‘neon-vfpv4’, ‘fpv5-d16’, ‘fpv5-sp-d16’, ‘fp-armv8’, ‘neon-fp-armv8’ and ‘crypto-neon-fp-armv8’. Причем ‘neon’ это тоже самое что и ‘neon-vfpv3’, а ‘vfp’ это ‘vfpv2’.
    Мой компилятор (arm-none-eabi-gcc (15:5.4.1+svn241155-1) 5.4.1 20160919) выдаёт немного другой список, но сути дела это не меняет. Нам в любом случае нужно понять, как влияет тот или иной флаг на работу компилятора, ну и конечно, какой флаг когда следует использовать.

    Я начинал разбираться с платформы на основе процессора imx6, но отложим и ее ненадолго, поскольку сопроцессор neon имеет особенности о которых я расскажу позже, а начнем с более простого случая — с платформы integrator/cp,
    Самой платы у меня нет, поэтому отладка производилась на эмуляторе qemu. В qemu платформа Interator/cp основана на процессоре ARM926EJ-S, который в свою очередь поддерживает сопроцессор VFP9-S. Данный сопроцессор соответствует стандарту Vector Floating-point Architecture version 2 (VFPv2). Соответственно, нужно поставить -mfpu=vfpv2, но в списке опций моего компилятора, такого варианта не оказалось. На просторах интернета я встретил вариант компиляции с флагами -mcpu=arm926ej-s -mfpu=vfpv3-d16, поставил, и у меня все скомпилилось. При запуске я получил исключение undefined instruction, что было предсказуемо, ведь сопроцессор был выключен.

    Для того чтобы, разрешить работу сопроцессора нужно установить бит EN [30] в регистре FPEXC. Делается это с помощью команды VMSR

        /* Enable FPU extensions */                                    
            asm volatile ("VMSR FPEXC, %0" : : "r" (1 << 30);

    Вообще-то команда VMSR обрабатывается сопроцессором и вызывает исключение, если не включен сопроцессор, но обращение к этому регистру его не вызывает. Правда, в отличие от остальных, обращение к этому регистру возможно только в привилегированном режиме.

    После разрешения работы сопроцессора стали проходить наши тесты на математические функции. Но когда я включил оптимизацию (-O2), стало возникать уже упомянутое ранее исключение undefined instruction. Причем возникало оно на инструкции vmov которая вызывалась в коде раньше, но исполнялась успешно (без возникновения исключения). Наконец, я обнаружил в конце приведенной страницы фразу “The instructions that copy immediate constants are available in VFPv3” (т.е. операции с константами поддерживаются начиная с VFPv3). И решил проверить, какая же версия релизована в моем эмуляторе. Версия записана в регистре FPSID. Из документации следует, что значение регистра должно быть 0x41011090. Это соответствует 1 в поле architecture [19..16] то есть VFPv2. Собственно, сделав распечатку при старте, я это и получил

        unit: initializing embox.arch.arm.fpu.vfp9_s:
                VPF info:
                 Hardware FP support
                 Implementer =        0x41 (ARM)
                 Subarch:             VFPv2
                 Part number =        0x10
                 Variant     =        0x09
                 Revision    =        0x00

    Прочитав внимательно, что ‘vfp’ это alias ‘vfpv2’, я выставил правильный флаг, все заработало. Возвращаясь к странице, где я увидел сочетания флагов -mcpu=arm926ej-s -mfpu=vfpv3-d16 отмечу, что был недостаточно внимателен, потому что в списке флагов фигурирует -mfloat-abi=soft. То есть, никакой аппаратной поддержки в этом случае нет. Точнее, -mfpu имеет значение только если установлено отличное от ‘soft’ значение для -mfloat-abi.

    Ассемблер


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

    Регистры


    Начнем с описание регистров. VFP позволяет совершать операции с 32-битными (s0..s31) и 64-битными (d0..d15) числами с плавающей точкой, Соответствие между этими регистрами показано на картинке ниже.



    Q0-Q15 — это 128-битные регистры из более старших версий для работы с SIMD, о них чуть позже.

    Система команд


    Конечно, чаще всего работу с VFP-регистрами стоит отдать компилятору, но как минимум переключение контекста придётся написать вручную. Если у вас уже есть примерное понимание синтаксиса команд ассемблера для работы с регистрами общего назначения, разобраться с новыми командами не должно составить большого труда. Чаще всего просто добавляется приставка “v”.

    vmov d0, r0, r1 /* Указывается r0 и r1, т.к. в d0 64 бита, а в r0-1 только 32 */
    vmov r0, r1, d0
    vadd d0, d1, d2
    vldr d0, r0
    vstm r0!, {d0-d15}
    vldm r0!, {d0-d15}

    И так далее. Полный список команд можно посмотреть на сайте ARM.

    Ну и конечно не стоит забывать о версии VFP, чтобы не возникло ситуаций вроде той, что описана выше.

    Флаг -mfloat-abi ‘softfp’ и ‘hard’


    Вернемся к -mfloat-abi. Если почитать документацию, то увидим:
    ‘softfp’ allows the generation of code using hardware floating-point instructions, but still uses the soft-float calling conventions. ‘hard’ allows generation of floating-point instructions and uses FPU-specific calling conventions.
    То есть, речь идет о передаче аргументов в функцию. Но, по крайней мере, мне было не очень понятно, в чем разница между “soft-float” и “FPU-specific” calling conventions. Предположив, что в случае hard используются регистры с плавающей точкой, а случае softfp используются целочисленные регистры, я нашел подтверждение этому на вики debian. И хотя это для сопроцессоров NEON, но это не имеет принципиального значения. Еще один интересный момент, что при варианте softfp компилятор может, но не обязан использовать аппаратную поддержку:
    “Compiler can make smart choices about when and if it generates emulated or real FPU instructions depending on chosen FPU type (-mfpu=) “
    Для лучшей ясности я решил поэкспериментировать, и очень удивился, поскольку при выключенной оптимизации -O0 разница была очень незначительная и касалась не тех мест, где реально использовалась плавающая точка. Догадавшись, что компилятор просто все укладывает на стек, а не использует регистры, я включил оптимизацию -O2 и опять удивился, поскольку с оптимизацией компилятор начинал использовать аппаратные регистры с плавающей точкой, как для варианта hard, так и для sotffp, и разница, как и в случае с -O0 была очень незначительная. В итоге для себя я объяснил это тем что компилятор решает проблему, связанную с тем, что если копировать данные между регистрами с плавающей точкой и целочисленными, существенно падает производительность. И компилятор при оптимизации начинает использовать все имеющиеся в его распоряжении ресурсы.

    На вопрос, какой же флаг использовать ‘softfp’ или ‘hard’, я ответил для себя следующим образом: везде где нет уже скомпилированных с флагом ‘softfp’ частей, следует использовать ‘hard’. Если же такие есть, то необходимо использовать ‘softfp’.

    Переключение контекста


    Поскольку Embox поддерживает вытесняющую многозадачность, для корректной работы в рантайме, естественно, нужна была реализация переключения контекста. Для этого необходимо сохранять регистры сопроцессора. Тут есть пара нюансов. Первый: оказалось что команды операций со стеком для плавающих точек (vstm/vldm) поддерживают не все режимы. Второй: эти операции не поддерживают работу более, чем с шестнадцатью 64-битных регистров. Если за раз нужно загрузить/сохранить больше регистров, нужно использовать две инструкции.

    Еще приведу одну небольшую оптимизацию. На самом деле, каждый раз сохранять и восстанавливать по 256 байт VFP-регистров совсем не обязательно (регистры общего назначения занимают всего 64 байта, так что разница существенная). Очевидной оптимизацией будет совершать эти операции только если процесс этими регистрами в принципе пользуется.

    Как я уже упоминал, при выключенном сопроцессоре VFP попытка исполнить соответствующую инструкцию будет приводить к исключению “Undefined Instruction”. В обработчике этого исключения нужно проверить, чем исключение вызвано, и если дело в использовании VPF-сопроцессора, то процесс помечается как использующий VFP-сопроцессор.

    В итоге уже написанное сохранение/восстановление контекста дополнилось макросами

    #define ARM_FPU_CONTEXT_SAVE_INC(tmp, stack) \
        vmrs      tmp, FPEXC ; \
        stmia     stack!, {tmp}; \
        ands      tmp, tmp, #1<<30; \
        beq       fpu_out_save_inc; \
        vstmia    stack!, {d0-d15}; \
    fpu_out_save_inc:
    
    #define ARM_FPU_CONTEXT_LOAD_INC(tmp, stack) \
        ldmia     stack!, {tmp}; \
        vmsr      FPEXC, tmp; \
        ands      tmp, tmp, #1<<30; \
        beq       fpu_out_load_inc; \
        vldmia    stack!, {d0-d15}; \
    fpu_out_load_inc:

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

    EMBOX_TEST_SUITE("FPU context consistency test. Must be compiled with -02");
    
    #define TICK_COUNT    10
    
    static float res_out[2][TICK_COUNT];
    
    static void *fpu_context_thr1_hnd(void *arg) {
        float res = 1.0f;
        int i;
    
        for (i = 0; i < TICK_COUNT; ) {
            res_out[0][i] = res;
    
            if (i == 0 || res_out[1][i - 1] > 0) {
                i++;
            }
    
            if (res > 0.000001f) {
                res /= 1.01f;
            }
    
            sleep(0);
        }
    
        return NULL;
    }
    
    static void *fpu_context_thr2_hnd(void *arg) {
        float res = 1.0f;
        int i = 0;
    
        for (i = 0; i < TICK_COUNT; ) {
            res_out[1][i] = res;
    
            if (res_out[0][i] != 0) {
                i++;
            }
    
            if (res < 1000000.f) {
                res *= 1.01f;
            }
    
            sleep(0);
        }
    
        return NULL;
    }
    
    TEST_CASE("Test FPU context consistency") {
        pthread_t threads[2];
        pthread_t tid = 0;
        int status;
    
        status = pthread_create(&threads[0], NULL, fpu_context_thr1_hnd, &tid);
        if (status != 0) {
            test_assert(0);
        }
    
        status = pthread_create(&threads[1], NULL, fpu_context_thr2_hnd, &tid);
        if (status != 0) {
            test_assert(0);
        }
    
        pthread_join(threads[0], (void**)&status);
        pthread_join(threads[1], (void**)&status);
    
        test_assert(res_out[0][0] != 0 && res_out[1][0] != 0);
    
        for (int i = 1; i < TICK_COUNT; i++) {
            test_assert(res_out[0][i] < res_out[0][i - 1]);
            test_assert(res_out[1][i] > res_out[1][i - 1]);
        }
    }

    Тест успешно проходил при выключенной оптимизации поэтому мы и указали в описании теста, что он должен быть скомпилирован с оптимизацией, EMBOX_TEST_SUITE(«FPU context consistency test. Must be compiled with -02»); хотя знаем, что тесты не должны на это полагаться.

    Сопроцессор NEON и SIMD


    Пришло время рассказать, почему я отложил рассказ про imx6. Дело в том что он основан на ядре Cortex-A9 и содержит более продвинутый сопроцессор NEON (https://developer.arm.com/technologies/neon ). NEON не только является VFPv3, но это еще и сопроцессор SIMD. VFP и NEON используют одни и те же регистры. VFP использует для работы 32- и 64-разрядные регистры, а NEON — 64- и 128-битные, последние как раз и были обозначены Q0-Q16. Помимо целых значений и чисел с плавающей точкой NEON также умеет работать с кольцом многочленов 16-й или 8-й степени по модулю 2.

    Режим vfp для NEON почти ничем не отличается от разобранного сопроцессора vfp9-s. Конечно, лучше указывать для -mfpu варианты vfpv3 или vfpv3-d32 для лучшей оптимизации, поскольку он имеет 32 64-битных регистра. И для включения сопроцессора необходимо дать доступ к сопроцессорам c10 и c11. это делается с помощью команд

    /* Allow access to c10 & c11 coprocessors */
        asm volatile ("mrc p15, 0, %0, c1, c0, 2" : "=r" (val) :);
        val |= 0xf << 20;
        asm volatile ("mcr p15, 0, %0, c1, c0, 2" : : "r" (val));

    но других принципиальных отличий нет.

    Другое дело если указывать -mfpu=neon, в этом случае компилятор может использовать SIMD-инструкции.

    Использование SIMD в C


    Для того, чтобы <<рассовывать>> значения по регистрам вручную, можно заинклудить “arm_neon.h” и использовать соответствующие типы данных:
    float32x4_t для четырёх 32-битных флоатов в одном регистре, uint8x8_t для восьми 8-битных целых чисел и так далее. Для обращения к одному значению обращаемся как к массиву, сложение, умножение, присвоение и т.д. как для обычных переменных, например:

    uint32x4_t a = {1, 2, 3, 4}, b = {5, 6, 7, 8};
    uint32x4_t c = a * b;
    printf(“Result=[%d, %d, %d, %d]\n”, c[0], c[1], c[2], c[3]);

    Само собой, использовать автоматическую векторизацию проще. Для автоматической векторизации добавляем в GCC флаг -ftree-vectorize.

    void simd_test() {
        int a[LEN], b[LEN], c[LEN];
    
        for (int i = 0; i < LEN; i++) {
            a[i] = i;
            b[i] = LEN - i;
        }
    
        for (int i = 0; i < LEN; i++) {
            c[i] = a[i] + b[i];
        }
    
        for (int i = 0; i < LEN; i++) {
            printf("c[i] = %d\n", c[i]);
        }
    }

    Цикл со сложениями генерирует следующий код:

    600059a0:       f4610adf        vld1.64 {d16-d17}, [r1 :64]               
    600059a4:       e2833010        add     r3, r3, #16                       
    600059a8:       e28d0a03        add     r0, sp, #12288  ; 0x3000          
    600059ac:       e2811010        add     r1, r1, #16                       
    600059b0:       f4622adf        vld1.64 {d18-d19}, [r2 :64]               
    600059b4:       e2822010        add     r2, r2, #16                       
    600059b8:       f26008e2        vadd.i32        q8, q8, q9                
    600059bc:       ed430b04        vstr    d16, [r3, #-16]                   
    600059c0:       ed431b02        vstr    d17, [r3, #-8]                    
    600059c4:       e1530000        cmp     r3, r0                            
    600059c8:       1afffff4        bne     600059a0 <foo+0x58>               
    600059cc:       e28d5dbf        add     r5, sp, #12224  ; 0x2fc0          
    600059d0:       e2444004        sub     r4, r4, #4                        
    600059d4:       e285503c        add     r5, r5, #60     ; 0x3c  

    Проведя тесты на распараллеленный код, получили, что простое сложение в цикле, при условии независимости переменных дает ускорение аж в 7 раз. Кроме того, мы решили посмотреть, насколько влияет распараллеливание на реальных задачах, взяли MESA3d с его программной эмуляцией и померили количество fps с разными флагами, получился выигрыш в 2 кадра в секунду (15 против 13), то есть, ускорение около 15-20%.

    Приведу еще один пример ускорения с помощью команд NEON, не нашего, а от ARM-а.

    Копирование памяти ускоряется почти на 50 процентов по сравнению с обычным. Правда примеры там на ассемблере.

    Обычный цикл копирования:

    WordCopy
          LDR r3, [r1], #4
          STR r3, [r0], #4
          SUBS r2, r2, #4
          BGE WordCopy

    цикл с командами и регистрами neon:

    NEONCopyPLD
          PLD [r1, #0xC0]
          VLDM r1!,{d0-d7}
          VSTM r0!,{d0-d7}
          SUBS r2,r2,#0x40
      BGE NEONCopyPLD

    Понятно, что копировать по 64 байта быстрее чем по 4 и такое копирование даст прирост на 10%, но остальные 40% похоже дает работа сопроцессора.

    Cortex-M


    Работа с FPU в Cortex-M мало чем отличается от описанного выше. Например, вот так выглядит приведенный выше макрос для сохранения fpu-шного контекста

    #define ARM_FPU_CONTEXT_SAVE_INC(tmp, stack) \
        ldr       tmp, =CPACR; \
        ldr       tmp, [tmp]; \
        tst       tmp, #0xF00000; \
        beq       fpu_out_save_inc; \
        vstmia    stack!, {s0-s31};
    fpu_out_save_inc:

    Также команда vstmia использует только регистры s0-s31 и по-другому происходит обращение к управляющим регистрам. Поэтому не буду сильно вдаваться в подробности, объясню только diff. Итак, мы сделали поддержку для STM32F7discovery c cortex-m7 для него, соответственно, нужно поставить флаг -mfpu=fpv5-sp-d16. Обратите внимание, что в мобильных версиях, нужно еще более внимательно смотреть версию сопроцессора, поскольку могут быть разные варианты у одного и того же cortex-m. Так, если у вас вариант не с двойной точностью, а с одинарной, то может не быть регистров D0-D16, как у нас в stm32f4discovery, именно поэтому и используются вариант с регистрами S0-S31. Для этого контроллера мы используем -mfpu=fpv4-sp-d16.

    Основным же отличием является доступ к управляющим регистрам контроллера они расположены прямо в адресном пространстве основного ядра для причем для разных типов они разные cortex-m4 для cortex-m7.

    Заключение


    На этом я закончу свой краткий рассказ про плавающую точку для ARM. Отмечу, что современные микроконтроллеры очень мощные и подходят не только для управления, но и для обработки сигналов или различного рода мультимедиа-информации. Для того, чтобы эффективно использовать всю эту мощь, нужно понимать как она устроена. Надеюсь данная статья помогла в этом чуть лучше разобраться.
    Embox
    Открытая и свободная ОС для встроенных систем

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

      +1

      Это все, безусловно, интересно. Указанные выше особенности пишутся один раз, или берутся готовыми вместе с RTOS/библиотекой/компилятором.


      Думаю, что разработчику интереснее куда более простые, но насущные вопросы, а именно:


      1. Количество тактов на арифметические операции (о ужас, деление в несколько раз медленнее умножения!)
      2. Наличие аппаратных функций вычисления квадратного корня (vsqrtf.32), причем компилятор может и не захотеть его нативно использовать, отдав предпочтение программной реализации
      3. Время/инструкции на перегонку значений между FPU и ALU
      4. Возможность работы FPU параллельно с CPU

      Так-то VFPv3 вполне хороший сопроцессор.


      Кстати по поводу NEON — если я не ошибаюсь, он заявлен как несовместимый с IEEE-754. Не потому, что у него float другой, а просто он не отрабатывает все эти пограничные значения с бесконечностями и NaN как этого требует IEEE-754-совместимый FPU.


      И размеры векторных регистров у A8 и A9 разные, если не ошибаюсь. Потому что у меня в коде был только float32x2

        +2
        Указанные выше особенности пишутся один раз, или берутся готовыми вместе с RTOS/библиотекой/компилятором.

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


        Количество тактов на арифметические операции (о ужас, деление в несколько раз медленнее умножения!)

        не уверен, что разработчику более интересно знать количество тактов, а не то как объяснить компилятору правильно использовать сопроцессор. То есть, это безусловно важно, но это из общей части работы с плавающей точкой.

        Наличие аппаратных функций вычисления квадратного корня (vsqrtf.32), причем компилятор может и не захотеть его нативно использовать, отдав предпочтение программной реализации

        Если Вы внимательно читали статью, то видели, как на примере ошибочного флага компилятор сгенерил неподдерживающуюся данным сопроцессором инструкцию, и потом было прямо указано, что для разных версий поддерживается разный набор инструкций. Думаю выдача таблицы для версий сопроцессора не очень соотвествует формату хабра.

        Время/инструкции на перегонку значений между FPU и ALU

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

        Возможность работы FPU параллельно с CPU

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

        Так-то VFPv3 вполне хороший сопроцессор.

        Правильнее говорить, что это стандарт, а реализации могут быть разными
        VFPv3 — An optional extension to the Arm, Thumb, and ThumbEE instruction sets in the Armv7-A and Armv7-R profiles. VFPv3 can be implemented with either 32 or 16 doubleword registers. VFPv3U is a variant of VFPv3 that supports the trapping of floating-point exceptions to support code.


        Кстати по поводу NEON — если я не ошибаюсь, он заявлен как несовместимый с IEEE-754.
        Вы совершенно правы!

        И размеры векторных регистров у A8 и A9 разные

        конкретно с cortex-a8 и cortex-a9 не сталкивался, но как написано выше, зависит от реализации. в статье тоже есть ссылки, где при одной версии vfp разные реализации по регистрам.

        +1
        Дурацкий вопрос: а если мы не сохраняем флоат-регистры в софте, то там же предыдущие значения от другой программы? Т.е. одна программа может получить доступ к float-регистрам другой программы. Кажется, как раз недавно пролетала CVE'шка подобного типа…

          +1

          У ARM Cortex MxF действительно есть возможность ленивого сохранения контекста FPU. Там это оправдано по нескольким причинам:


          • Обычно 100% софта на микроконтроллере — доверенный код. Он записан во FLASH и так просто не меняется. Более того, МК обычно слишком маленькие, чтобы делать полноценную изоляцию окружений
          • Сохранение каждого регистра на стек — это, как минимум, еще один такт процессора, потраченный на переключение контекста. Посчитайте количество регистров в VFP и заметьте, насколько дольше у вас сразу станет обработка прерываний. К слову, математику в прерываниях обычно не делают
          • При включении Lazy FPU Storage при попытке доступа к FPU будет сгенерировано прерывание, которое попросит программный код осуществить выгрузку контекста FPU

          А так по умолчанию контекст FPU сохраняется автоматически вместе с остальным состоянием процессора. Альтернативой этому является подход в других МК, к примеру, TI C2000, где автоматически сохраняются только самые нужные (вроде SP, PC), а за всеми остальными должен следить разработчик. Полезно это там, где больше одного-двух регистров прерыванию не нужны, а вот время реакции нужно сократить всеми возможными способами (вплоть до выделения отдельного банка регистров для обработчиков прерываний)

            0
            Процессор один на всех, контекст у каждого процесса свой. При переключении надо сохранять/восстанавливать контекст. Теоретически можно гонять не весь контекст, а только то, что реально надо процессу, но с одной стороны это утечки данных между процессами, да, но с другой — производительность.
              +1
              Вопрос вовсе не дурацкий! Вы совершенно правы, потенциально это возможно. Мы хотели рассказать и об этом, но решили, что очень уж раздувается статья и мы можем утонуть в деталях.
              Dima_Sharihin в принципе ответил на этот вопрос:
              • Назначение технологии не полного сохранения/востановления регистров — улучшение производительности. Но при этом происходит ухудшение безопасности.
              • Так же написано, что софт на микроконтроллере — доверенный код. Так вот уточню, что для Embox это касается не только кода микроконтроллеров. Предполагается, что использовать Embox целесообразно, если у вас есть система с заранее известной функциональностью. А принципы сборки и запуска ПО, гарантируют отсуствие стороннего (вредоносного) кода или потенциально небезопасного.
                0
                Я понимаю, что это не нужно однопользовательским системам, где весь софт доверенный и нет пользователей. Но если у нас есть привилегированный режим, значит, есть разделение прав, а значит, это уязвимость.
                  +1
                  Дело не в множестве пользователей и не в разделении на уровни доступа, а в том, что весь код (все приложения, службы, библиотеки, драйвера) могут появиться в системе только на момент ее проектирования. То есть, нет возможности запустить код, который воспользуется этой уязвимостью. Даже если используются внешние скрипты обработка которых запрещена по умолчанию, но код самого интерпретатора есть на момент сборки и следовательно может быть просмотрен и проанализирован на предмет уязвимостей подобного рода.
                  То есть, если нет возможности установить новое ПО и запустить его на исполнение, то уровень угрозы сильно снижается.

                  Собственно в микроконтроллерах обычно стоит какая то прошивка, а не универсальная ОС, поэтому весь код там доверенный, невзирая на то в каком режиме он выполняется.
                    0
                    Убрав возможность исполнять чужой код вы убрали один класс проблем, но не все. Одно дело уязвимость в на уровне nobody, и на этом всё. Другое дело если он при этом (будучи nobody) может сифонить криптоключи или ещё что-то с привилегированных процессов.
                      0
                      Убрав возможность исполнять чужой код вы убрали один класс проблем, но не все.

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

                      Другое дело, что сильно снизить уровень угрозы, причем достаточно простыми методами.

                      Давайте попробуем разобрать описанную Вами ситуацию, возможно я не неправ и уровень угрозы действительно серьезный.

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

                      Если речь идет о том, что современные системы очень сложны по функциональности и невозможно просмотреть весь код, то я отвечу, что принцип переиспользования кода, позволяет существенно снизить эту проблему. Если добавить к этому принцип ограничения функциональности, то есть, не добавлять всю потенциально требующуюся функциональность, а только минимально необходимую, то это еще уменьшит количество нужной для анализа информации, а следовательно и понизит уровень угрозы.
                        0
                        Обычная уязвимость в ПО, и вроде бы непривилегированный процесс от nobody уже читает регистры криптобиблиотеки соседнего приложения.
                          0
                          Процесс это слишком громко сказано для микроконтроллера)
                          В лучшем случае поток, для процессов уже требуется вирутализация памяти.
                          Но что бы этот поток создать, вам надо залезть в саму прошивку. Но с другой стороны если вы уже там то зачем такие выкрутасы?
                            0
                            Все таки уточню, что Embox с успехом может запускаться и на более мощьных платформах, в том числе и с виртуальной памятью. Но подход, когда делается единая прошивка, и нельзя обновить или установить одно приложение, сохраняется.
                            Это востребованно, например, берем роутер, или какое нибудь другое устройство с четко описанной функциональностью. В нем вам не нужно ставить приложения, но нужна довольно мощьная функциональность, которую микроконтроллер может не потянуть.
                            +1
                            не важно, что он от nobody! Как процесс попал в систему? По нашей модели, его мог только разработчик добавить. То есть, это либо закладка, либо ошибка разработчика. Уровень угрозы определяет насколько тщательно выгребаются эти ошибки. Плюс, приложение которое используется в системе и содержит подобного рода ошибку, скорее всего заимствовано. Но тогда оно (приложение) должно знать в каком окружении его будут использовать, ну например, что передача управления поризошла от интерисующего процесса. А у нас это не возможно, у нас на этапе конфигурации определяются даже системные вызовы, все отстальные параметры также могут меняться.
                          +1

                          Гм, кто считает криптографию на FPU? Вы же понимаете, что для просто перегонки данных из-в-FPU есть отдельные ассемблерные команды и вся целочисленная арифметика считается на других регистрах, сохранение контекста которых — обязательно на уровне архитектуры?


                          А если вы говорите про аппаратную изоляцию "секурных" и "несекурных" процессов, то за этим идите к ARM Cortex M23/M33, все, что было "до" предполагает, что все есть доверенное.
                          Ну или сконфигурируйте MPU на запрет записи конфигурации FPU для обычных процессов, запретите ленивую выгрузку контекста и проблема исчезнет сама собой.

                            +1
                            Ну по идее для криптографии может использоваться SIMD, то есть тот же NEON, а он использует те же регистры.
                              0

                              Справедливо, да. В моем понимании FPU — это в первую очередь VFP, про NEON я забыл (потому что работаю чаще с М-серией, а не А-).

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое