Вместо введения. Чем ARM процессоры необычны. (Часть 1)

Младшие версии ARM процессоров имеют 32 разрядный набор команд, ориентированный преимущественно на обработку 32-х разрядных данных.

 

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

Для придания современным версиям ARM процессоров большей универсальности, им добавили второй набор, уже 2 байтных команд, гораздо более эффективный по размеру кода. Набор укороченных команд называется Thumb, а набор 'стандартных' команд- ARM наборами. Несмотря на то, что 'внутри' процессора команды Thumb преобразуются перед исполнением в ARM инструкции, с точки зрения программиста эти наборы настолько отличаются, что вполне могли бы принадлежать разным процессорам. Переход от одного набора к другому может выполняться в любом месте, практически любой алгоритм можно написать либо на ARM либо на Thumb, по усмотрению программиста. Но, поскольку в Thumb наборе нет команд, требуемых для обработки исключительных ситуаций (к примеру, сброса или прерываний), любая программа требует включения фрагментов ARM. После сброса или наступления исключительной ситуации процессор принудительно переключается к исполнению команд ARM набора.

Набор Thumb, имея меньшую разрядность, оптимизирован, в его состав ввели только команды, реализующие наиболее важные операции, и он несколько ограничивает доступ к ресурсам процессора. Например, с 16 до 8 меняется число полностью доступных регистров. Некоторые команды из набора Thumb предоставляют ограниченный доступ к регистрам R8..R15, называемых в этом режиме 'верхними', или 'старшими регистрами' (high registers). Указатель стека SP (R13), регистр адреса возврата LR (R14), счетчик команд PC (R15) доступны в Thumb через специальные команды, прямой доступ к словам состояния CPRS и SPRS не предоставляется.

Программная смена набора команд происходит при выполнении двух специальных команд - "BX" 'перехода на адрес с изменением' (Branch and eXchange) и вызова подпрограммы "BLX", (Branch, Link and eXchange). Если при исполнении этих команд младший разряд адреса перехода равен 1, процессор переключится в Thumb режим, если равен 0, то в ARM режим. Последующие команды, на которые осуществляется переход, будут рассматриваться как принадлежащие выбранному набору. Эти две команды присутствуют и в Thumb и в ARM, конечно для каждого набора свои версии. 

Команды ARM режима длиной 4 байта, хранятся по адресам, кратным 4-м (0, 4, 8, 0xC и т.д.), а двухбайтные команды THUMB режима по адресам, кратным 2 (0, 2, 4...). Нетрудно заметить, что в обоих случаях последний разряд адреса равен нулю. Поэтому для реализации перехода в Thumb режим необходимо устанавливать этот бит 'принудительно', для чего к адресу прибавляют единицу. 

Пример: 

LDR R1,=TProc1 +1; ( * см. далее “Команды, которых нет”)

BX R1; переход на адрес TProc1 с изменением набора команд на Thumb.


 

В сам регистр R15 адрес загружается в любом случае с обнуленным младшим разрядом.

В отличии от команд 'BX' и 'BLX', применение 'обычных' команд 'B' и 'BL' или изменение значения регистра R15 любой командой, к смене набора команд не приводит. Поэтому в Thumb режиме +1 к каждому адресу перехода приписывать не надо, что значительно облегчает жизнь программиста.

Есть еще один способ сменить набор инструкций. Необходимо установить в нужное состояние флаг T в регистре SPSR и выполнить команду восстановления регистра CPSR из SPSR. 

Замечание: в нормальном режиме исполнения программ регистр SPSR недоступен, он есть только в некоторых привилегированных режимах. Непосредственное изменение T флага в регистре CPSR приводит к НЕПРЕДСКАЗУЕМОМУ результату.

 

 

Команды, которых нет.


 

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

К примеру, формы автоматической адресации команды LDR вызывают немало вопросов.

Команда LDR Load Register служит для загрузки в указанный первым операндом регистр 32-х разрядного слова из памяти. Адрес памяти, откуда происходит загрузка, может вычисляться процессором 9-ю различными, порой довольно сложными способами. Подробно способы вычисления адреса будут рассмотрены далее, но сейчас отметим, что все они требуют, чтобы в качестве базового при вычислении адреса использовалось значение, хранящееся в одном из регистров R0:R15.


 

Самая простая форма вычисления адреса памяти для команды LDR такова:


 

LDR Rd, [Rm,#смещение]

- загрузить в Rd значение хранящееся по адресу Rm + смещение. 


 

При нулевом смещении допустима такая упрощенная запись:


 

LDR Rd, [Rm] ; - синоним LDR Rd, [Rm, #0]


 

Получается, что если мы хотим при помощи самой простой формы команды LDR загрузить в регистр R1 число 0x22334455, нам нужно:


 

1. Сохранить это число в памяти (при компиляции, например)

2. Загрузить в любой регистр, скажем, в R2 адрес этого числа

3. Выполнить LDR R1,[R2]


 

А как нам выполнить пункт 2? Как нам загрузить в регистр 32 битный адрес? Выходом служит вычисление адреса данных относительно состояния регистра R15 (счетчик команд, PC), значение которого всегда определенно и на текущий момент известно – оно равно адресу выполняемой команды. Надо только учитывать, что при любом чтении из R15 в режиме ARM возвращается значение равное (адресу_текущей_команды + 8)*. 


 


 

thisAddr: LDR R1,[R15, #SourceData - thisAddr - 8] ; коррекция на 8

...

SourceData: DD 0x22334455 ; недалеко в памяти данные 

; это работающий пример загрузки в регистр R1 числа 0x22334455 


 


 

Конечно, это сложно и неудобно. Для упрощения жизни программистов у ARM ассемблера есть специальные формы автоматической адресации (здесь под ассемблером подразумевается программа, переводящая исходный *.asm текст в объектный код):


 

-------------------------------------------------------------------------------

* Примечание: в Thumb чтение R15 дает значение, равное (адресу_текущей_команды + 4)


 

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


 

LDR R1, SourceData; прочитать значение по адресу SourceData в регистр

STR R1, SourceData; записать из регистра в адрес SourceData


 

SourceData: DD 0x22334455; адрес недалеко в памяти 


 

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


 

LDR R1,=0x22334455 ; здесь R1 := 0x22334455


 

Или, как вариант загрузки произвольного 32 разрядного адреса:

...

Label_1:

LDR R1, =Label_1; здесь R1 := адресу Label_1 (адрес тоже Число) 

...

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


 

LDR Rd, [PC, #offsetDataStore];

...

DataStore: DD 123456789;


 

Это накладывает ограничения на использование автоматической адресации. Во первых, смещение 12 разрядное (для команд LDRD STRD LDRH STRH LDRSH LDRSB даже 8 разрядное), то есть для загружаемых данных должно найтись место в пределах +/- 4095 (+/- 255) адресов от команды. Во вторых, понятно, что при сохранении данных командой STR надо убедится, что мы не пытаемся записывать в ПЗУ, или, при отладке, мы не должны писать в сегмент кода (запись в него запрещена). Но если в KEIL создать для имитации ОЗУ сегмент данных с разрешением записи, попытка автоматической адресации данных за пределы сегмента кода дает ошибку при ассемблировании: 

error A20: INVALID SIMPLE RELOCATABLE EXPRESSION. 


 

В некоторых случаях надо быть готовым к тому, что ассемблер заменит конструкцию LDR Rn,=data на команду MOV Rn, #data. Это произойдет, если загружаемое значение можно представить как любое 8 разрядное число от 0x00 до 0xff, циклически сдвинутое на произвольное четное количество разрядов (0,2,4...30) внутри 32 разрядного регистра (просто, не правда ли? =), в этом случае будет произведена оптимизация, и вместо ожидаемой команды LDR будет вставлена команда 

MOV Rn,#data

где #data - так называемые непосредственные данные. Подробнее о них - дальше, в разделе addressing_mode.


 

IDA умеет 'сворачивать' команды LDR обратно в формы автоматической адресации, но тем не менее не прячет данные, которые участвуют в такой операции:


 

LDR PC, =loc_A000009C; свернутая форма

; -------------------------------------------------

off_A0000004 DCD loc_A000009C ; это те данные, которые грузятся


 

Еще две 'команды', которые встречаются в программах, но которые не умеет исполнять процессор:

ADR Rx, Address ; - загрузить в Rx указанный относительный адрес. Реально это команда превращается ассемблером в команду ADD Rx,PC,#xx или SUB Rx,PC,#xx, вычисляя нужное значение прибавлением или вычитанием смещения от PC. Очевидное ограничение - применимое для написания программ смещение между адресом команды и загружаемым адресом не более +/-255 байт. 

RET – В действительности это команда MOV R15,R14.

Еще один вариант “автоматической” команды – MOVL Rx,#data

В Thumb режиме максимальное число, загружаемое в регистр командой mov Rx,#data равно 255. При необходимости загрузить большее число, применяют команду movl Rx,#data, которая автоматически будет заменена на пару команд: 

mov Rx,#data
lsl Rx,Rx,#shift

или

mov Rx,#data1

add Rx,#data2

в зависимости от того, каким образом можно представить число, которое необходимо загрузить. В отличие от IDA, Keil такую форму команды не понимает 

No comments yet! Why don't you be the first?

Post Comment

Your email address will not be published. Required fields are marked *