Примеры программ на ассемблере для arm

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

Использование команд, выполняемых по условиям

Применение условий для логического ИЛИ

CMP Rn,#p ; Если Rn=p OR Rm=q то перейти на Label. BEQ Label CMP Rm,#q BEQ Label Эквивалентный код: CMP Rn,#p CMPNE Rm,#q ; Если условие не выполнено, то провести другой тест. BEQ Label
TEQ Rn,#0 ; Проверить знак RSBMI Rn,Rn,#0 ; и, если необходимо, дополнить до 2-х.

Умножение на 4, 5 или 6

MOV Rc,Ra,LSL#2 ; Умножить на 4, CMP Rb,#5 ; проверить значение, ADDCS Rc,Rc,Ra ; закончить умножение на 5, ADDHI Rc,Rc,Ra ; закончить умножение на 6.

Сочетание частичной проверки и проверки диапазона

TEQ Rc,#127 ; Частичная проверка, CMPNE Rc,#» «-1 ; проверка диапазона MOVLS Rc,#».» ; ЕСЛИ Rc

Деление и остаток от деления

Как программировать на Ассемблере в 2021 году

Как правило, подпрограммы деления для конкретных задач предоставляется в виде исходной кодов библиотек стандарта ANSI C, поставляемых вместе с набором выбранных средств разработки (ARM Cross Development Toolkit). В целях ознакомления ниже приведены лишь некоторые из подпрограмм деления.

; Делимое находится в Ra, делитель — в Rb. MOV Rcnt,#1 ; Бит, управляющий делением. Div1 CMP Rb,#0x80000000 ; Перемещать Rb до тех пор, пока он больше Ra CMPCC Rb,Ra MOVCC Rb,Rb,ASL#1 MOVCC Rcnt,Rcnt,ASL#1 BCC Div1 MOV Rc,#0 Div2 CMP Ra,Rb ; Проверить на возможность простого вычитания. SUBCS Ra,Ra,Rb ; Вычесть если все ОК, ADDCS Rc,Rc,Rcnt ; разместить соответствующий бит в результат MOVS Rcnt,Rcnt,LSR#1 ; бит, управляющий сдвигом MOVNE Rb,Rb,LSR#1 ; делить на 2, пока не закончено. BNE Div2 ; Результат деления размещен в Rc ; Остаток от деления — в Ra.

Контроль за переполнением в ARM7TDMI

    Переполнение при умножении чисел без знака с 32-битным результатом

UMULL Rd,Rt,Rm,Rn ; От 3 до 6 (включительно) машинных тактов TEQ Rt,#0 ; +1 такт при переполнении регистра BNE overflow
SMULL Rd,Rt,Rm,Rn ; От 3 до 6 (включительно) машинных тактов TEQ Rt,Rd ASR#31 ; +1 такт при переполнении регистра BNE overflow
UMLAL Rd,Rt,Rm,Rn ; От 3 до 7 (включительно) машинных тактов TEQ Rt,#0 ; +1 такт при переполнении регистра BNE overflow

SMLAL Rd,Rt,Rm,Rn ; От 4 до 7 (включительно) машинных тактов TEQ Rt,Rd, ASR#31 ; +1 такт при переполнении регистра BNE overflow
UMULL Rl,Rh,Rm,Rn ; От 3 до 6 (включительно) машинных тактов ADDS Rl,Rl,Ra1 ; накопление для младшего слова ADC Rh,Rh,Ra2 ; накопление для старшего слова BCS overflow ; +1 такт при переполнении обоих регистров
SMULL Rl,Rh,Rm,Rn ; От 3 до 6 (включительно) машинных тактов ADDS Rl,Rl,Ra1 ; накопление для младшего слова ADC Rh,Rh,Ra2 ; накопление для старшего слова BVS overflow ; +1 такт при переполнении обоих регистров

Примечание: контроль за переполнением не имеет смысла при умножении чисел со знаком и без знака с 64-битным результатом, т.к. операнды — 32-битные слова, и поэтому переполнение в этом случае никогда не возникает.

ЯЗЫК АССЕМБЛЕРА за 3 МИНУТЫ

Генератор псевдослучайных чисел

Часто при разработке программ требуются генераторы псевдо-случайных чисел. Самые эффективные алгоритмы основаны на командах исключающее ИЛИ вместе с командами циклического побитового сдвига. К сожалению, для генерации 32-битного результата требуется более одной обратной связи, чтобы добиться максимального диапазона (т.е.

2^32-1 циклов перед повторение псевдослучайной последовательности), поэтому приведенный ниже пример использует в своей работе 33-битный регистр с «рабочими» битами 33 и 20. Формула для работы алгоритма: = EOR , сдвиг влево 33-битного числа и размещение в младшем разряде; эта операция требует обработки всех (т.е. 32 бита). Вся цепочка по получении одного псевдослучайного числа выполняется за 5S машинных тактов:

; Опорное случайное число в Ra (32 бита), ; + 1 бит в младшем бите Rb, используется Rc. TST Rb,Rb,LSR#1 ; Старший бит поместить во флаг переноса MOVS Rc,Ra,RRX ; 33-битный циклический сдвиг вправо ADC Rb,Rb,Rb ; перенос разместить в младшем бите Rb EOR Rc,Rc,Ra,LSL#12 EOR Ra,Rc,Rc,LSR#20 ; Новое псевдослучайное число размещается Ra.

Умножение на константу с использованием операций сдвига

Умножение на 2^n (1,2,4,8,16,32..)

MOV Ra, Rb, LSL #n

Умножение на 2^n+1 (3,5,9,17..)

ADD Ra,Ra,Ra,LSL #n

Умножение на 2^n-1 (3,7,15..)

RSB Ra,Ra,Ra,LSL #n
ADD Ra,Ra,Ra,LSL #1 ; multiply by 3 MOV Ra,Ra,LSL#1 ; and then by 2

Умножение на 10 с добавлением числа

ADD Ra,Ra,Ra,LSL#2 ; multiply by 5 ADD Ra,Rc,Ra,LSL#1 ; multiply by 2 and add in next digit

Общий рекурсивный метод для Rb := Ra*C, C — константа:

    Если C — четное, то C = 2^n*D, D — нечетное:

D=1: MOV Rb,Ra,LSL #n D<>1: MOV Rb,Rb,LSL #n
D=1: ADD Rb,Ra,Ra,LSL #n D<>1: ADD Rb,Ra,Rb,LSL #n
D=1: RSB Rb,Ra,Ra,LSL #n D<>1: RSB Rb,Ra,Rb,LSL #n

Эта часть кода не совсем оптимальна, но близка к этому. Пример ее неоптимальности — умножение на 45:

RSB Rb,Ra,Ra,LSL#2 ; multiply by 3 RSB Rb,Ra,Rb,LSL#2 ; multiply by 4*3-1 = 11 ADD Rb,Ra,Rb,LSL#2 ; multiply by 4*11+1 = 45
ADD Rb,Ra,Ra,LSL#3 ; multiply by 9 ADD Rb,Rb,Rb,LSL#2 ; multiply by 5*9 = 45

Чтение слова при неизвестном выравнивании

; Адрес находится в Ra (32 бита), ; используются регистры Rb, Rc; результат в Rd. ; d должно быть менее чем, например, 0,1. BIC Rb,Ra,#3 ; выровнить адрес по границе одного слова. LDMIA Rb, ; Прочитать 64 бита, содержащие ответ AND Rb,Ra,#3 ; Фактор коррекции в байтах MOVS Rb,Rb,LSL#3 ; . теперь в битах и проверка на выравненность. MOVNE Rd,Rd,LSR Rb ; Выделить младшую часть результата слова ; (если не выровнено) RSBNE Rb,Rb,#32 ; get other shift amount ORRNE Rd,Rd,Rc,LSL Rb ; combine two halves to get result ; Получить число других сдвигов ; Комбинируя двумя половина, сформировать ; результат

Источник: www.gaw.ru

Немного про ARM ассемблер. Пишем многопоточную программу.

Пишем простую многопоточную программку на ARM ассемблере.

Ассемблер не плохой язык программирования. Одновременно и мощный и сложный, но не такой сложный как можно подумать. Предлагаю развенчать мифы об этом. Вспомнить наши корни и попробовать написать программу на ассемблере под ARM Cortex-M3 , в качестве подопытного будем использовать отладку с алиэкспрес для микроконтроллера stm32f103c8 .

Если заинтересовал — читайте дальше.

Проект будем делать в Keil. Создаем пустой проект и не добавляем ни одного пакета.

Код простейшей программы

Окунёмся же сразу в пучину ассемблерных команд. И по ходу пьессы буду объяснять некоторые тонкости.

AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE 0x00000100 __initial_sp PRESERVE8 THUMB AREA RESET, DATA, READONLY __Vectors DCD __initial_sp ; Указатель вершины основного стека DCD Start ; Указатель на начало программы SPACE (12 * 4) ; Пропуск 12-и ненужных векторов DCD PendSV_Handler ; DCD SysTick_Handler ; ; тут ещё могут быть прерывания PRESERVE8 AREA |.text|, CODE, READONLY Start PROC BL INIT_MCU ; Инициализация микроконтроллера INF_LOOP B . ; Вечный цикл ENDP

Этот код ничего не делает.

Читайте сверху вниз.

Сначала размечается область для стэка в оперативной памяти, за подробностями обращайтесь к документации (ссылки оставлю в конце заметки). В области STACK резервируются 0x100 байт памяти. __initial_sp — это метка(указатель) на вершину стэка. Стек здесь растёт в сторону уменьшения адреса.

Следующая область — это область с данными доступными только для чтения (располагается в ПЗУ). Порядок констант должен быть именно таким и ни каким иначе. Перным словом идёт указатель на стэк, вторым указатель на точку входа в программу, затем таблица векторов прерываний.

Читайте также:
Программа что устанавливать обои на Андроид

PRESERVE8 — необходимо для выравнивания.

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

Разобрали простейщий код — пора приступать к более значительной задаче, будем управлять 4-я потоками, два из них будут выводить в USART1 разный текст, и два будут мигать светодиодами с разной частотой.

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

Потоки (задачи)

И так, как же будет выглядеть подпрограмма реализующая наш поток:

TASK_LED PROC ; Для BitBand под рукой всегда должны быть нолик и единичка MOV R11, #0 MOV R12, #1 ; BitBand для PC13.ODR MOV32 R0, (GPIOC_ODR Цикл с мигалкой SCL STR R12, [R0] ; Выключаем светодиод BL Delay ; Вызов функции задержки STR R11, [R0] ; Включаем светодиод BL Delay ; Вызов функции задержки B SCL BX LR ENDP

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

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

А именно, при возникновении прерывания в стек автоматически сохраняются некоторые регистры, в таком порядке: xPSR , PC , LR , R12 , R3 , R2 , R1 , R0 . Следовательно стек задачи нужно стразу инициализировать нужными значениями:

FILL_STACK_TASK PROC PUSH ; Значения по-умолчанию для заполнения стэков MOV32 R1, #0x01000000 ; Значение для xPSR MOV32 R3, #0x0 ; LDR R4, =INF_LOOP ; Возврат из задачи в бесконечный цикл ; Регистры сохраняемые при возникновении прерывания STR R1, [R0, #-0x04] ! ; xPSR = 0x01000000 STR R2, [R0, #-0x04] ! ; PC = 0xfffffffe STR R4, [R0, #-0x04] ! ; LR = INF_LOOP STR R3, [R0, #-0x04] ! ; R12 = 0 STR R3, [R0, #-0x04] ! ; R3 = 0 STR R3, [R0, #-0x04] ! ; R2 = 0 STR R3, [R0, #-0x04] ! ; R1 = 0 STR R3, [R0, #-0x04] ! ; R0 = 0 ; Дополнительные регистры тоде можно сохранить POP ENDP

Здесь в R0 хранится указатель на стек этой задачи, в ресистре R2 — указатель на процедуру задачи, с наложеной маской 0xfffffffe (Это магия возврата из прерываний, в документации на ядро можно почитать более подробно), как уже заметили ригистр связи LR инициализируем указателем на наш бесконечный цикл, на случай если выйдем из процедуры, если этого не сделать можно словить фатальную ошибку, а так нет, просто будем вхолостую гонять вечный цикл вместо выполнения этой задачи.

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

Идём дальше, мы реализовали одну из задач, реализовали процедуру инициализации стека, осталось научиться переключать контекст. Первое что приходит на ум, это переключать его в прерывании с определенным интервалом времени, пусть будет милисекунда. Системный таймер? И да и нет. Если просто переключить контекст в прерывании системного таймера, то можно также словить фатальную ошибку. Необходимо использовать специальное прерывание, которое есть в ядре ARM Cortex-M3 — PendSV .

PendSV прерывание должно иметь самый низкий приоритет, что бы оно гарантированно было выполнено после всех остальных прерываний. И ещё это программное прерывание, это означает что его нужно принудительно вызывать:

MOV32 R0, ICSR MOV32 R1, SCB_ICSR_PENDSVSET STR R1, [R0]

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

Переключение контекста будет выполняться по следующему алгоритму:

  • Если мы первый раз вошли в планировщик, то мы просто меняем значение регистра SP на указатель стека первой задачи и завершаем прерывание, всё, теперь мы выполняем первую задачу до возникновения следующего прерывания переключения контекста.
  • Теперь нам необходимо сохранить адрес стэка из регистра SP , взять адрес следующего стека и применить его, выйти из прерывания. Теперь мы выполняем следующую задачу и так далее.

Код реализующий это может выглядить примерно так:

PendSV_Handler PROC PUSH ; Сохраняем дополнительные регистры ; Сбрасываем флаг прерывания MOV32 R0, ICSR MOV32 R1, SCB_ICSR_PENDSVCLR STR R1, [R0] ; Получаем ячейку с нужным стэком LDR R0, =Task_Runing LDR R1, [R0] ; Номер текущего стэка AND R1, R1, #0x3 ; Очищаем лишнее по маске ; Получаем указатель на указатель на текущий стек LDR R2, =Task_SP ; Указатель на массив указателей стеков MOV R12, #4 ; Количество байт в слове MLA R12, R12, R1, R2 ; ADR = 4* N + ADRSP[] LDR R3, [R12] ; Адрес стека для нужной задачи ; Проверяем первый ли это запуск планировщика LDR R4, =Task_IsRuning LDR R5, [R4] CMP R5, #1 BEQ IS_NOFIRST ; Это первый запуск планировщика MOV R5, #1 STR R5, [R4] MSR MSP, R3 ; Переключаем стэк на первую задачу B EXIT_PSV IS_NOFIRST ; Не первый раз мы здесь MRS R6, MSP ; получаем SP STR R6, [R12] ; Номер следующего стэка ADD R1, R1, #1 AND R1, R1, #0x3 STR R1, [R0] MOV R12, #4 MLA R12, R12, R1, R2 ; Адрес стека для задачи LDR R3, [R12] ; Переключаем стэк на первую задачу MSR MSP, R3 EXIT_PSV POP ; Восстанавливаем дополнительные регистры BX LR ALIGN 4 ENDP PRESERVE8

Да и кстати нужно не забыть выделить память для всех наших переменных, что-то вроде этого:

;————————СЕКЦИЯ СО СТЭКАМИ ЗАДАЧ—————————————————- AREA STACK_TASK, DATA, READWRITE, ALIGN=3 ; Секция со стеками задач Stack_A_Mem SPACE 0x00000100 ; Stack_A ; указатель на вершину A Stack_B_Mem SPACE 0x00000100 ; Stack_B ; указатель на вершину B Stack_C_Mem SPACE 0x00000100 ; Stack_C ; указатель на вершину C Stack_D_Mem SPACE 0x00000100 ; Stack_D ; указатель на вершину D ;————————СЕКЦИЯ С ПЕРЕМЕННЫМИ——————————————————- AREA VAR, DATA, READWRITE, ALIGN=3 Task_IsRuning DCD 0 ; Номер текущей задачи Task_Runing DCD 0 ; Номер текущей задачи Task_SP DCD Stack_A, Stack_B, Stack_C, Stack_D ; Указатели стека ALIGN

Литература:

  1. RM0008 Reference manual, — Описание микроконтроллера
  2. PM0056 Programming manual — Описание ядра Cortex®-M3 и его команд.
  3. ARM and Thumb instruction

PS

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

Как обычно, хорошего кодинга и поменьше багов.

  • Реализация обработчика прерывания клавиатуры в окружении DOS
  • Visual Studio Code для ARM микроконтроллеров stm32 и других.

CDEblog

Блог инженера-схемотехника, конструктора и немного программиста

Источник: cdeblog.ru

Примеры программ на ассемблере для arm

Библиотека сайта rus-linux.net

as (ассемблер, который преобразует исходный код на языке ассемблера в бинарный код) и ld (линковщик, который создает результирующий исполняемый файл). Обе утилиты находятся в пакете программного обеспечения binutils , поэтому они уже могут присутствовать в вашей системе. Разумеется, вам также понадобится хороший текстовый редактор; я всегда рекомендую использовать Vim для разработки программ, но он имеет высокий порог вхождения, поэтому Nano или любой другой текстовый редактор с графическим интерфейсом также отлично подойдет.

Готовы начать? Скопируйте следующий код и сохраните его в файле myfirst.s :

.global _start _start: mov r7, #4 mov r0, #1 ldr r1, =string mov r2, #stringlen swi 0 mov r7, #1 swi 0 .data string: .ascii «Ciao!n» stringlen = . — string

Эта программа всего-навсего выводит строку «Ciao!» на экран и если вы читали статьи, посвященные использованию языка ассемблера для работы с центральными процессорами архитектуры x86, некоторые из использованных инструкций могут быть вам знакомы. Но все же, существует множество различий между инструкциями архитектур x86 и ARM, что также можно сказать и синтаксисе исходного кода, поэтому мы подробно разберем его.

Читайте также:
Программа которая видит все устройства на компьютере

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

as -o myfirst.o myfirst.s ld -o myfirst myfirst.o

Теперь вы можете запустить созданную программу с помощью команды ./myfirst . Вы наверняка обратили внимание на то, что исполняемый файл имеет очень скромный размер около 900 байт — если бы вы использовали язык программирования C и функцию puts() , размер бинарного файла был бы больше примерно в пять раз!

В данном случае мы зашли в установленную на Raspberry Pi систему по протоколу SSH, запустили программу Tmux для разделения экрана, запустили текстовый редактор Nano для редактирования исходного кода и осуществили его ассемблирование.

В данном случае мы зашли в установленную на Raspberry Pi систему по протоколу SSH, запустили программу Tmux для разделения экрана, запустили текстовый редактор Nano для редактирования исходного кода и осуществили его ассемблирование.

Создание собственной операционной системы для Raspberry Pi

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

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

Одним из лучших подобных документов является документ под названием Baking Pi ( www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/index.html ) от сотрудников Университета Кэмбриджа. По сути, он является набором руководств, описывающих приемы работы с языком ассемблера для включения светодиодов, доступа к пикселям на экране, получения клавиатурного ввода и так далее. В процессе чтения вы узнаете очень много об аппаратном обеспечении Raspberry Pi, причем руководства были написаны для оригинальных моделей этих одноплатных компьютеров, поэтому нет никаких гарантий того, что они будут актуальны для таких моделей, как A+, B+ и Pi 2.

Если вы предпочитаете язык программирования C, вам следует обратиться к документу с ресурса Valvers, расположенному по адресу http://tinyurl.com/qa2s9bg и содержащему описание процесса настройки кросскомпилятора и сборки простейшего ядра операционной системы, причем в разделе Wiki полезного ресурса OSDev, расположенном по адресу http://wiki.osdev.org/Raspberry_Pi_Bare_Bones , также приведена информация о том, как создать и запустить простейшее ядро ОС на Raspberry Pi.

Как говорилось выше, самой большой проблемой в данном случае является необходимость разработки драйверов для различных аппаратных устройств Raspberry Pi: контроллера USB, слота SD-карты и так далее. Ведь даже код для упомянутых устройств может занять десятки тысяч строк. Если вы все же хотите разработать собственную полнофункциональную операционную систему для Raspberry Pi, вам стоит посетить форумы по адресу www.osdev.org и поинтересоваться, не разработал ли уже кто-либо драйверы для этих устройств и, при наличии возможности, адаптировать их для ядра своей операционной системы, сэкономив тем самым большое количество своего времени.

Как все это работает

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

С помощью следующей инструкции мы помещаем число 4 в регистр r7 . (Если вы никогда не работали с языком ассемблера ранее, вам следует знать, что регистром называется ячейка памяти, расположенная непосредственно в центральном процессоре. В большинстве современных центральных процессоров реализовано небольшое количество регистров по сравнению с миллионами или миллиардами ячеек оперативной памяти, но при этом регистры незаменимы, так как работают гораздо быстрее.) Чипы архитектуры ARM предоставляют разработчикам большое количество регистров общего назначения: разработчик может использовать до 16 регистров с именами от r0 до r15 , причем эти регистры не связаны с какими-либо историческими сложившимися ограничениями, как в случае архитектуры x86, где некоторые из регистров могут использоваться для определенных целей в определенные моменты.

Итак, хотя инструкция mov и очень похожа на одноименную инструкцию архитектуры x86, вам в любом случае следует обратить внимание на символ решетки рядом с числом 4 , указывающий на то, что далее расположено целочисленное значение, а не адрес в памяти. В данном случае мы желаем использовать системный вызов write ядра Linux для вывода нашей строки; для использования системных вызовов следует заполнять регистры необходимыми значениями перед тем, как простить ядро выполнить свою работу. Номер системного вызова должен помещаться в регистр r7 , причем число 4 является номером системного вызова write.

С помощью следующей инструкции mov мы помещаем дескриптор файла, в который должна быть записана строка «Ciao!», то есть, дескриптор стандартного потока вывода, в регистр r0 . Так как в данном случае используется поток стандартного вывода, в регистр помещается его стандартный дескриптор, то есть, 1 . Далее нам нужно поместить адрес строки, которую мы хотим вывести, в регистр r1 с помощью инструкции ldr (инструкция «загрузки в регистр»; обратите внимание на знак равенства, указывающий на то, что далее следует метка, а не адрес). В конце кода, а именно, в секции данных мы объявляем эту строку в форме последовательности символов ASCII. Для успешного использования системного вызова «write» нам также придется сообщить ядру операционной стемы о том, какова длина выводимой строки, поэтому мы помещаем значение stringlen в регистр r2 . (Значение stringlen рассчитывается путем вычитания адреса окончания строки из адреса ее начала.)

На данный момент мы заполнили все регистры необходимыми данными и готовы к передаче управления ядру Linux. Для этого мы используем инструкцию swi , название которой расшифровывается как «software interrupt» («программное прерывание»), осуществляющую переход в пространство ядра ОС (практически таким же образом, как и инструкция int в статьях, посвященных архитектуре x86). Ядро ОС исследует содержимое регистра r7 , обнаруживает в нем целочисленное значение 4 и делает вывод: «Так, вызывающая программа хочет вывести строку». После этого оно исследует содержимое других регистров, осуществляет вывод строки и возвращает управление нашей программе.

Таким образом мы видим на экране строку «Ciao!», после чего нам остается лишь корректно завершить исполнение программы. Мы решаем эту задачу путем помещения номера системного вызова exit в регистр r7 с последующим вызовом инструкции программного прерывания номер ноль. И на этом все — ядро ОС завершает исполнение нашей программы и мы снова перемещаемся в командную оболочку.

Vim (слева) является отличным текстовым редактором для написания кода на языке ассемблера.

Vim (слева) является отличным текстовым редактором для написания кода на языке ассемблера — файл для подсветки синтаксиса данного языка для архитектуры ARM доступен по ссылке http://tinyurl.com/psdvjen .

Читайте также:
Понятие о ситуационном анализе этапы планирования программы профилактики

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

Обратный инжиниринг

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

objdump -d myfirst

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

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

Подпрограммы, циклы и условные инструкции

Наша программа начинается с помещения указателя на начало строки и значения ее длины в соответствующие регистры для последующего осуществления системного вызова write , причем сразу же после этого осуществляется переход к подпрограмме print_string , расположенной ниже в коде. Для осуществления этого перехода используется инструкция bl , название которой расшифровывается как «branch and link» («ветвление с сохранением адреса»), причем сама она сохраняет текущий адрес в коде, что позволяет вернуться к нему впоследствии с помощью инструкции bx . Подпрограмма print_string просто заполняет другие регистры для осуществления системного вызова write таким же образом, как и в нашей первой программе перед переходом в пространство ядра ОС с последующим возвратом к сохраненному адресу кода с помощью инструкции bx .

Вернувшись к осуществляющему вызов коду, мы можем обнаружить метку под названием loop — название метки уже намекает на то, что мы вернемся к ней через некоторое время. Но сначала мы используем еще один системный вызов с именем read (под номером 3 ) для чтения символа, введенного пользователем с помощью клавиатуры. Поэтому мы помещаем значение 3 в регистр r7 и значение 0 (дескриптор стандартного потока ввода) в регистр r0 , так как нам нужно прочитать пользовательский ввод, а не данные из файла.

Далее мы размещаем адрес, по которому мы хотим сохранить символ, прочитанный и помещенный ядром ОС в регистр r1 — в нашем случае это область памяти char , описанная в конце секции данных. (На самом деле, нам нужно машинное слово, то есть, область памяти для хранения двух символов, ведь в ней будет храниться и код клавиши Enter. При работе с языком ассемблера важно всегда помнить о возможности переполнения областей памяти, ведь в нем нет никаких высокоуровневых механизмов, готовых прийти вам на помощь!).

Вернувшись к основному коду, мы увидим, что в регистр r2 помещается значение 2 , соответствующее двум символам, которые мы хотим сохранить, после чего осуществляется переход в пространство ядра ОС для выполнения операции чтения. Пользователь вводит символ и нажимает клавишу Enter. Теперь нам нужно проверить, что это за символ: мы помещаем адрес области памяти ( char в секции данных) в регистр r1 , после чего с помощью инструкции ldrb загружаем байт из области памяти, на которую указывает значение из этого регистра.

Квадратные скобки в данном случае указывают на то, что данные хранятся в интересующей нас области памяти, а не в самом регистре. Таким образом, регистр r2 теперь содержит единственный символ из области памяти char из секции данных, причем это именно тот символ, который ввел пользователь. Наша следующая задача будет заключаться в сравнении содержимого регистра r2 с символом «q» , который является 113 символом таблицы ASCII (обратитесь к таблице символов, расположенной по адресу www.asciichart.com ). Теперь мы используем инструкцию cmp для выполнения операции сравнения, после чего используем инструкцию beq , имя которой расшифровывается как «branch if equal» (переход при условии равенства), для перехода к метке done в том случае, если значение из регистра r2 равно 113. Если это не так, то мы выводим нашу вторую строку, после чего осуществляем переход к началу цикла с помощью инструкции b .

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

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

Чипы ARM были впервые использованы в линейке компьютеров Acorn Archimedes, а на сегодняшний день практически захватили рынок мобильных устройств (источник изображения: http://tinyurl.com/qy9p2l5 ).

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

Разумеется, приведенная в статье информация относительно использования языка ассемблера для архитектуры ARM является всего лишь вершиной айсберга. Использование данного языка программирования всегда связано с огромным количеством нюансов и если вы хотите, чтобы мы написали о них в одной из следующих статей, просто дайте нам знать об этом! Пока же рекомендуем посетить отличный ресурс с множеством материалов для изучения приемов создания программ для систем Linux, исполняющихся на компьютерах с центральными процессорами архитектуры ARM, который расположен по адресу http://tinyurl.com/nsgzq89 . Удачного программирования!

  • «Начинаем программировать на языке ассемблера»
  • «Школа ассемблера: условные инструкции, циклы и библиотеки»
  • «Начинаем программировать на языке ассемблера: переход на уровень аппаратного обеспечения»
  • «Школа ассемблера: разработка операционной системы»
  • «Школа ассемблера: расширение возможностей разработанной операционной системы»
  • «»

Источник: rus-linux.net

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru