#c #arm #volatile #armcc
#c #arm #volatile #armcc
Вопрос:
Рассмотрим следующий код:
volatile int status;
status = process_package_header(amp;pack_header, PACK_INFO_CONST);
if ((((status) == (SUCCESS_CONST)) ? ((random_delay() amp;amp; ((SUCCESS_CONST) == (status))) ? 0 : side_channel_sttack_detected()) : 1))
{
...
}
Который генерирует этот машинный код (созданный с помощью objdump цепочки инструментов):
60: f7ff fffe bl 0 <process_package_header>
64: 9000 str r0, [sp, #0] /* <- storing to memory as status is volatile */
66: 42a0 cmp r0, r4 /* <- where is the load before compare? status is volatile, it could have change between the last store instruction (above line) and now */
68: d164 bne.n 134 <func 0x134>
6a: f7ff fffe bl 0 <random_delay>
Теперь, поскольку status
является volatile, он должен был быть считан из памяти при достижении if
инструкции. Я ожидал бы увидеть некоторую команду загрузки, прежде чем сравнивать ее ( cmp
) с SUCCESS_CONST
, независимо от того факта, что ей было присвоено возвращаемое значение из функции process_package_header()
и сохранено в памяти, поскольку status
она изменчива и могла быть изменена между str
инструкцией и cmp
инструкцией.
Пожалуйста, постарайтесь игнорировать мотивацию if
условия, его цель — попытаться обнаружить физическую атаку на процессор, при которой флаги условий и регистры могут быть изменены извне физическим оборудованием.
Набор инструментов ARM DS-5_v5.27.0 компилятор arm: ARMCompiler5.06u5 (armcc)
Целью является ARM CortexM0 CPU
Комментарии:
1.
status
похоже, это автоматическая переменная. Изменится ли поведение, если определено как статическое? Я думаю, что нет способа изменить такую переменную способом, «неизвестным реализации», который не вызовет неопределенного поведения (ну, может быть, есть, но я не могу придумать ни одного).2. Даже если у вас была команда load, она могла измениться сразу после команды load. Поэтому, возможно, у него должно быть две команды загрузки на случай, если он изменился сразу после первой. Теперь ему нужны три команды загрузки на случай, если он изменился после второй. Вы не можете выиграть. Поэтому простое использование значения, которое было сохранено предыдущей инструкцией, кажется не хуже, чем использование команды load.
3. вы использовали слишком много ненужных круглых скобок, таких как
(status) == (SUCCESS_CONST)
4. @IanAbbott:
volatile
определено не как означающее, что значение, содержащееся в объекте, обновляется с момента его использования, а скорее как то, что каждый доступ в модели вычислений C реализуется с фактическим доступом. Поэтому достаточно одной загрузки.5. Вопрос: Должен ли энергозависимый объект с автоматической продолжительностью хранения иметь фактическое местоположение в памяти, или компилятору разрешено реализовать его в каком-либо неадресуемом местоположении, таком как регистр?
Ответ №1:
Основное правило, регулирующее volatile
объекты, заключается в следующем, начиная с C11 6.7.3 / 7:
любое выражение, ссылающееся на такой объект, должно оцениваться строго в соответствии с правилами абстрактной машины, как описано в 5.1.2.3. Более того, в каждой точке последовательности значение, последним сохраненное в объекте, должно соответствовать значению, предписанному абстрактной машиной, за исключением случаев, когда оно изменено неизвестными факторами, упомянутыми ранее.
И далее говорится, что
То, что представляет собой доступ к объекту, который имеет тип, определяемый volatile, определяется реализацией.
, который применяется к тому, как должны интерпретироваться другие правила (например, в 5.1.2.3). В руководстве пользователя вашего компилятора обсуждаются детали изменчивых обращений, но, похоже, в этом нет ничего удивительного. В самом разделе 5.1.2.3 в основном говорится о правилах последовательности; правила для вычисления выражений находятся в другом месте (но все равно должны соблюдаться, как указано в отношении доступа к вашему volatile объекту).
Вот соответствующие детали поведения абстрактной машины:
-
операция присваивания имеет побочный эффект сохранения значения в объекте, идентифицируемом
status
. В конце этого оператора есть точка последовательности, поэтому- побочный эффект применяется до выполнения любых вычислений, появляющихся в последующих операторах, и
- поскольку
status
он является volatile, назначение, выраженное этой строкой, является последней записью,status
выполняемой программой перед точкой последовательности.
-
следующим вычисляется условное выражение в
if
инструкции с- подвыражение
(status) == (SUCCESS_CONST)
вычисляется первым, перед любым из других подвыражений. - Оценка
status
происходит перед оценкой==
операции, и - принимает форму преобразования этого идентификатора в значение, хранящееся в объекте, который он идентифицирует (преобразование lvalue, согласно пункту 6.3.2.1/ 2).
- Чтобы что-либо сделать со значением, хранящимся в
status
в то время, это значение должно быть сначала прочитано.
- подвыражение
Стандарт не требует, чтобы объект volatile находился в адресуемом хранилище, поэтому в принципе ваша автоматическая переменная volatile могла быть назначена исключительно регистру. В этом случае, пока машинные инструкции, использующие этот объект, либо считывают его значение непосредственно из его регистра, либо вносят обновления непосредственно в его регистр, никаких отдельных загрузок или хранилищ не потребуется для достижения надлежащей семантики volatile. Однако ваш конкретный объект, похоже, не попадает в эту категорию, поскольку инструкция сохранения в вашей сгенерированной сборке, похоже, указывает, что она действительно связана с местоположением в памяти.
Более того, если программа правильно реализовала семантику volatile для объекта, назначенного регистру, тогда этот регистр должен был бы быть r0. Я не знаком со спецификой этого языка ассемблера и процессора, на котором выполняется код, но, безусловно, не похоже, что r0 является жизнеспособным местом для такого хранилища.
В этом случае я согласен с тем, что status
должно было быть прочитано из памяти, и его следует снова прочитать из памяти, если необходимо оценить его второе появление в условном выражении. Это поведение абстрактной машины, которое соответствующие реализации демонстрируют в отношении всех изменчивых обращений. Таким образом, мой анализ заключается в том, что ваша реализация не соответствует требованиям в этом отношении, и я был бы склонен сообщить об этом как об ошибке.
Что касается обходного пути, я думаю, что вам лучше всего записать важные биты в сборку — встроенную сборку, если ваша реализация поддерживает это, или в виде полной функции, реализованной в сборке, если это необходимо.
Комментарии:
1. Возможно, это отдельный вопрос, но что, по вашему мнению, должно произойти, если переменная объявлена как
register volatile int status;
, но компилятор помещает ее в адресуемое хранилище, а не в регистр? Нужно ли генерировать код для чтения ячейки памяти каждый раз, когда этоstatus
вычисляется (как значение rvalue)?2. @IanAbbott, для
volatile
объекта, я думаю, все дело в том, куда реализация фактически помещает объект, независимо от того, куда она могла его поместить. Я думаю, что это прямое следствие следования правилам, установленным для абстрактной машины. Использованиеregister
ключевого слова не имеет значения в этом отношении.
Ответ №2:
Описанное поведение не соответствует стандарту C, за исключением нетрадиционных интерпретаций. Если предполагается, что компилятор соответствует в этом отношении, об этом следует сообщить как об ошибке.