Ассемблер уроки: Уроки по Ассемблеру | Ravesli

Содержание

Урок 1. Начало программирования на ассемблере для Windows

Урок 1. Начало программирования на ассемблере для Windows

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

Создание приложение для Windows в общем случае имеет
больше стадий чем для Dos. Это объясняется тем, что при
компоновке Windows-программы в нее включается не
только код исходных модулей, но и ресурсы. Итак создание
программы для Windows должно содержать следующие
этапы :

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

В данной статье будет использоваться компилятор и
компоновщик фирмы Borland — Turbo Assembler и Turbo Link.
Конечно возможно некоторым покажеться странным
использование данного продукта, но могу вас уверить на
мой взгляд удобнее инструмента для программирования на
ассемблере для Windows вы не найдете. Я использую
версию Turbo Assembler 5.0.

В этой статье я также не буду рассказывать о создании
ресурсов и присоеденении их к исполняемому файлу, мы
рассмотрим только простой пример программы, и
попробуем найти какие-то сходства с программированием
под Dos.

Итак маленький пример:

Файл hello.asm

.model large, WINDOWS PASCAL
— Подключаем файл где описаны константы (типа «MB_OK»,
«MB_ICONEXCLAMATION»)
include windows.inc
— Говорим что будем использовать функцию API MessageBox
extrn MESSAGEBOX:proc
— Сегмент данных
.data
— Пустое место для информации Program Manager’a
freespace db 16 dup(0)
— Заголовок диалогового окна
lpszTitle db ‘Generic Sample Assembly Application’,0
— Текст диалогового окна
lpszText db ‘Hello World !’,0
— Сегмент кода
.code
— Наш старый «добрый» start (На самом деле WinMain)
start:
— Инициализируем задачу и получаем входные параметры
call INITTASK
or ax,ax
— Если инициализация прошла успешно
jnz @@OK
— Если ошибка
jmp @@Fail
@@OK:
— Сохраняем HINSTANCE
mov hInstance,di
— Инициализируем приложение
call INITAPP,hInstance
or ax,ax
jnz @@InitOK
@@Fail:
— Если инициализация завершилась неудачно
mov ax, 4CFFh
int 21h
@@InitOK:
— Выводим на экран диалоговое окно
call MESSAGEBOX,0,ds offset lpszText,ds offset
lpszTitle,MB_OK+MB_ICONEXCLAMATION
— Хе-хе а вот и выход
mov ax,4c00h
int 21h
end start

Как видите разработка простых приложений для Windows
практически не отличается от программирования для Dos.
Однако есть маленькие особенности которые мы сейчас
рассмотрим:

.model large, WINDOWS PASCAL

Про эту строчку много говорить и не надо, но она имеет
одну особенность. Система Windows и ее функции
используют алгоритм «прямой» передачи параметров через
стэк, как в языке Pascal. Поэтому мы и вводим этот
модификатор в директиву modal.

include windows.inc

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

extrn MESSAGEBOX:proc

Наверно одна из самых существенных строк этого
примера. Она говорит компилятору, что мы будем
использовать внешнюю функцию MessageBox. Я думаю у
вас не возникает никаких сомнений — существование этой
функции в Windows API.

freespace db 16 dup(0)

Зачем в начале сегмента данных стоят 16 байт с нулями ?
Для приложений Win16 эти 16 байт являются
обязательным условием нормальной работы программы.
При загрузке Вашего приложения Windows заменяет эти 16
байт своей информацией. Для более подробного
разъяснения этой проблеммы обратитесь к книге Мэта
Патрика (Matt Patrick) «Внутри Windows» («Windows
Internals»), 1993, Addison Wesley.

Всем известно, что каждое приложение для Windows
начинается с WinMain. Через параметры переданные этой
функции можно было получить командную строку,
идентификатор предидущего экземпляра программы и так
далее и тому подобнее. Немного сложнее процедура
инициализации приложения происходит в Assembler.

Первым шагом в инициализации приложения под
Windows будем считать вызов функции INITTASK. После
выполнения эта функция возвращает:

При успешном завершении AX=1
DX содержит nCmdShow, то есть параметр,
указывающий на стиль просмотра окна
ES:BX содержит адрес командной строки
SI содержит идентификатор ранее загруженной
программы hPrevInstance
DI содержит идентификатор загруженной программы
hInstance

После обработки входных параметров нужно
инициализировать приложение,
используя функцию
INITAPP. Единственным параметром этой функции
служит идентификатор приложения hInstance.Функция
INITAPP возвращает в AX=1 если приложение успешно
проинициализировано.

Для того, что бы посмотреть, что же эта программа умеет
делать вы должны создать еще один файл .def

Файл hello.def

NAME HELLO
EXETYPE WINDOWS

CODE MOVABLE DISCARDABLE
DATA MOVABLE MULTIPLE DISCARDABLE
STACKSIZE 5120
HEAPSIZE 4096
DESCRIPTION ‘Copyright(C) 1999 by BlackWolf’

Содержимое этого файла мы рассмотрим подробнее :

NAME HELLO

Имя получаемого модуля «HELLO».

EXETYPE WINDOWS

Тип получаемого модуля — исполняемый файл Windows.

CODE MOVABLE DISCARDABLE

Ключевым словом CODE мы можем выставлять
параметры нашего кодового сегмента. О возможных
параметрах сегмента смотрите ниже.

DATA MOVABLE MULTIPLE DISCARDABLE

Тоже самое только для сегмента данных.

STACKSIZE 5120

Устанавливаем размер стэка равным 5120.

HEAPSIZE 4096

Устанавливаем размер «кучи» равным 4096.

DESCRIPTION ‘Copyright(C) 1999 by BlackWolf’

Директива DESCRIPTION вставляет в файл кода
указанный текст и обычно применяется для примечания о
авторских правах.

Теперь рассмотрим параметры сегментов. Они могут быть
различных типов и в принципе формат инструкций CODE
и DATA выглядит таким образом:

CODE [FIXED|MOVABLE]
[DISCARDABLE|NONDISCARDABLE]
[PRELOAD|LOADONCALL]

Скобки в данном случае показывают, что элемент не
обязателен. Вертикальная черта означает «ИЛИ». Опции
директивы CODE означают:

FIXED — Сегмент имеет фиксированный адрес.
MOVABLE — Сегмент может быть перемещен, что бы
освободить пространство в памяти.
DISCARDABLE — Сегмент может быть выгружен,
чтобы освободить пространство.
NONDISCARDABLE — Сегмент не может быть
выгружен.
PRELOAD — Сегмент загружается в память при
запуске приложения.
LOADONCALL — Сегмент загружается в память
только когда происходит обращение к некоторому его
элементу.

DATA [NONE|SINGLE|MULTIPLE]
[READONLY|READWRITE] [PRELOAD|LOADONCALL]
[SHARED|NONSHARED]

Скобки в данном случае показывают, что элемент не
обязателен. Вертикальная черта означает «ИЛИ». Опции
директивы DATA означают:

NONE — Cегмент данных отсутствует (применяется
только для DLL).
SINGLE — Единственный сегмент данных,
разделяемый всеми процессами (применяется по
умолчанию для DLL).
MULTIPLE — Несколько сегментов данных
(применяется по умолчанию для исполняемых
файлов).
READONLY — Данные в сегменте можно только
читать, но не изменять.
READWRITE — Данные в сегменте можно как читать,
так и изменять.
PRELOAD — Сегмент заранее автоматически
загружается в память.
LOADONCALL — Сегмент загружается в память при
обращении к нему.
SHARED — Одна копия сегмента данных разделяется
между всеми процессами (применяется по
умолчанию для 16-битных DLL)
NONSHARED — Отдельная копия сегмента данных
загружается для каждого процесса (применяется по
умолчанию для приложений и 32-битных DLL).

Существует также еще одна очень интересная директива —
STUB. Она вставляет в файл .EXE кода для Windows
программу Dos. Эта директива никогда не применяется для
библиотек. Если вы не указываете директиву STUB Turbo
Assembler вставит в ваш исполняемый файл программу
WINSTUB.EXE. Эта программа является заглушкой для
запуска Windows приложений под Dos. При запуске вашего
приложения под Dos вы получите примерно такое
сообщение :

This program must be run under Microsoft Windows.

Используя данную директиву вы можете написать
программу которая будет прекрасно работать как под Dos
так и под Windows.

После написания файлов hello.asm и hello.def вы должны
скомпилировать и скомпоновать программу используя
такие параметры для Turbo Assembler:

tasm hello.asm

После компиляции вы должны получить .OBJ файл.

tlink hello,hello,,import.lib(*),hello

После компоновки вы получите готовый исполняемы файл
Windows. 🙂

(*) Если вы используете импорт функций API или импорт
функций из других DLL, вы обязательно должны включить
в компоновку библиотеку import.lib для 16-битных
приложений, или библиотеку import32.lib для 32-битных
приложений.

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

Ассемблер MS-DOS — завершаем цикл вводных статей об ассемблере.

Прощай ассемблер MS-DOS и здравствуй Windows!

Мы закончили изучать 16 битный ассемблер MS-DOS и приступаем к изучению программирования на 32 битном ассемблере для Windows.

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

16 битный ассемблер — это практически первый язык программирования, возможность «на молекулярном» уровне общаться с древними машинами-динозаврами. В дальнейшем Вы поймёте, что основы работы современных процессоров мало изменились до нашего времени. Обилие ненужной информации в Интернете создаёт иллюзию сложности. В действительности — ничего сложного нет.

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

В завершение курса 16-ти битного программирования на ассемблере, перечислим ряд учебников. Если кто-то захочет расширить свои знания или уточнить отдельные моменты написания кода для операционной системы MS-DOS, он может обратиться к ниже перечисленной литературе.

Мы бы советовали прочитать первую книгу: «20 уроков ассемблера под DOS», остальные — сверх нормы для тех, кто хочет отшлифовать пройденный материал.

Книга 1. 20 уроков ассемблера под DOS.

Книга Семёна Леонидовича Углева «20 уроков ассемблера под DOS» — просто находка для начинающих изучать ассемблер. Просто конкурент нашему циклу статей. Всё понятно, кратко и практично. Содержится основные данные, чтобы изучить ассемблер MS-DOS пошагово. Плавно переходим от простой программы к резидентной, затем пишем простой MS-DOS вирус, а затем — антивирус!

Изучаем ассемблер MS-DOS пошагово на примере написания вируса и антивируса.

Книга 2. Программирование на ассемблере.

Книга В.В. Одинокова и В.П. Коцубинского «Программирование на ассемблере» — типовой учебник для студентов технических ВУЗов. Сухо и по-преподавательски. Имеются лабораторные работы для закрепления материала.

Типовой учебник для студентов ВУЗов.

Книга 3. Аппаратное обеспечение IBM PC.

Книга известнейших братьев Александра и Григория Фроловых. Содержит примеры программ на ассемблере и Си.

Аппаратное обеспечение IBM PC c исходниками.

Книга 4. Тонкая настройка и оптимизация MS-DOS.

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

Тонкая настройка и оптимизация MS-DOS — вспомогательная информация об операционной системе.

Книга 5. MS-DOS для программиста — часть первая.

Первая часть отличнейшего учебника по программированию на ассемблере в операционной системе MS-DOS 6.22. Братья Фроловы излагают информацию интересно, толково и доступно. Весь код рабочий, как всегда.

MS-DOS для программиста. Часть 1.

Книга 6. MS-DOS для программиста — часть вторая.

Вторая часть замечательного учебника для желающих на практике изучить ассемблер MS-DOS.

MS-DOS для программиста. Часть 2.

Книга 7. Ассемблер и программирование для IBM PC.

Не можем пропустить самую известную книгу Питера Абеля, по которой учились программировать первые Российские или тогда Советские программисты. Распространялась в текстовом режиме. Интернета не было, поэтому переведённое произведение представляло собой особую ценность. Прочитать интересно и сейчас.

Питер Абель: «Ассемблер и программирование для IBM PC».

Книга 8. The Art of Assembly Language Programming (AoA).

Переходим к рассмотрению англоязычных учебников. Начнём с «The Art of Assembly Language Programming (AoA)» от Randall Hyde. Объёмная, информация, но изложена суховато. Можно найти HTML и PDF версии на официальном сайте: https://www.plantation-productions.com/Webster/www.artofasm.com/index.html

The Art of Assembly 16 bit.

Книга 9. Zen of Assembly Language.

Книга Майкла Абраша (Michael Abrash) с интрегующим названием. Вы часто будете встречать в литературе по программированию на ассемблере ссылки на Дао, Дзен и т.п. Изучая этот язык программирования у Вас появиться вначале предположение, а потом твёрдое убеждение в том, что наш Мир — всего лишь матрица, программа, набор нулей и единиц …

Дзен язака ассемблер Майкла Абраша.

Книга 10. Windows Assembly Language & Systems Programming.

Автор книги — Бари Каулер (Barry Kauler). В книге рассматривается как ассемблер MS-DOS, так и 32 битный ассемблер Windows. Большое количество исходного кода прилагается. Хороший англоязычный учебник.

Windows Assembly Language & Systems Programming от Barry Kauler.

Все рассмотренные книги находятся в одном архиве: СКАЧАТЬ АРХИВ.

Программирование на Ассемблере. Обучающие уроки

http://life-prog.ru/view_cat.php?cat=3

Язык
Ассемблер

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

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

Написанная
информация была с использованием книги:
Рисований О.М. Системне програмування:
Підручник. – Х.: НТУ “ХПІ”, 2010. – 912 с.

Введение
в Ассемблер

Для
того, чтобы программировать на Assembler,
необходимо:

Программа
MASM_v9.0 или MASM_v10.0

Отладчик,
например: OLLYDBG.EXE

В
установленном месте, где находится
программа MASM, создаете файл: aaa.bat

Я
выбрал такое название (aaa.bat)
для того, чтобы она была в самом верху.
И вы могли всегда его видеть. В этом
aaa.bat вносите такую информацию:

ml
/c /coff «work.asm»

link
/SUBSYSTEM:CONSOLE «work.obj»

work.asm
— это имя программы, которую нужно
компилировать. После ввода этой информации
и сохранения можно приступать к
программированию.

Ассемблер
имеет:


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

— метку начала программы,

— тело
программы,

-метку окончания программы

В
языке Ассемблер есть переменые разных
типов: знаковые и беззнаковые форматы
типов Shortlnt (signed char), Byte (unsigned char), Integer
(int), Word (unsigned int) и т. д.

Напишем
программу вычисления выражения: a –
e/b – de
, где:

a
= 5;

b = 27;

c = 86;

е = 1986;

d = 1112;

и
сохраним ее там же, где и aaa.bat: work.asm.
Если мы хотил откомпилировать другую
программу, то нужно в aaa.bat
изменить имя файлов, т. е. вместо work
заменить на имя. И сохранить его.
Если программа не содержит синтаксические
ошибки, то должен получиться файл с
расширением exe.

Программа:

.686
;
директива определения типа
микропроцессора

.model
flat,stdcall
; задание линейной модели памяти

;
но соглашения ОС Windows

.data
;
директива определения данных

_a
dw 5 ;
запись в 16-разрядный амбарчик памяти с
именем _а числа 5

_b dw 27 ;
запись _b = 16h

_c dw 86 ;
запись _c = 56h

_e dw 1986 ;
запись _e = 7c2h

_d dw 1112 ;
запись _d = 458

res dw 0 ;
резервирование памяти для сохранения
переменной res

.code
; директива начала сегмента команд

start:

mov
edx,0 ;
очистка регистров

mov
ebx,0

mov ecx,0

mov ах,_e ;
в регистр ах заносим число _e = 7c2h

mul _d ;
умножаем _e на _d

SHL edx,16 ;
делаем здвиг на 16

mov
dx,ax

push edx ;
бросаем значение в стек

mov
edx,0

mov ах,_e

mov cx,_b

div cx
; делим ах на cx

pop ecx ;
достаем из стека значене

sub
ecx,eax ;
отнимает

mov ах,_a

sub
eax,ecx

mov res, eax

ret ;
возвращение управления ОС

end
start ;
окончание программы с именем _start

Результат
работы программы:

как
мы видим, комментарии ставятся после
точки с запятой.

mov
куда, откуда — это команда
перевылки

mul
_d — это команда умнжения
регистра ax на _d. Результат попадает в
ax

shl
edx,16 — команда здвига на 16
разрядов

div
cx — команда деления ах на cx.
Результат попадает в ax

pop
ecx — команда достает из стека
значене

sub
ecx,eax — команда отнимает
значение ecx — eax. Результат попадает в
ecx

Немного про 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 & 0x00FFFFFF) * 0x20  + 0x42000000 + 13 * 4      

                ; Цикл с мигалкой
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    {LR}

                ; Значения по-умолчанию для заполнения стэков
                MOV32   R1, #0x01000000 ; Значение для xPSR
                MOV32   R3, #0x0 ; 
                LDR     R4, =INF_LOOP   ; Возврат из задачи в бесконечный цикл

                ; Регистры сохраняемые при возникновении прерывания
                STR     R1,  [R0, #-0x04] !     ;   xPSR    = 0x01000000
                STR     R2,  [R0, #-0x04] !     ;   PC      = &TASK & 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     {PC}
                ENDP

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

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

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

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

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

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

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

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

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

PendSV_Handler  PROC
                PUSH    {r4-r11}            ; Сохраняем дополнительные регистры

                ; Сбрасываем флаг прерывания
                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     {r4-r11}            ; Восстанавливаем дополнительные регистры
                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

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

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

Please enable JavaScript to view the comments powered by Disqus.

Первая программа на Assembler. — it-black.ru

Вот и пришло время написать нашу первую программу на языке Assembler. Начнем мы с процессора Intel 8086. Будем писать программу под DOS. Программирование под Windows и Linux сложнее. Поэтому начнем с простого и понятного 16-битного процессора 8086.

DOS (дисковая операционная система, ДОС) — семейство операционных систем для персональных компьютеров, ориентированных на использование дисковых накопителей, таких как жёсткий диск и дискета.

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

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

Необходимые инструменты:

Для программирования на ассемблере нам необходим компилятор. Наиболее известные компиляторы это TASM, MASM и FASM. В этом курсе я решил использовать FASM. Это довольно новый, удобный, быстро развивающийся компилятор ассемблера, написанный на себе самом. Его преимущества — это поддержка сложных макросов и мультиплатформенность. Есть версии под DOS, Windows и Linux.

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

Если у вас Windows, тогда устанавливать надо на 32 битную версию (х86) т.к. на 64 битной версии (х64) будет выдавать ошибку. Мой компьютер 64 битной версии, поэтому я установил Windows 7 32 bit на виртуальную машину и уже туда установил FASM. Как установить Windows на виртуальную машину я объяснял в своей статье “Установка Windows 7 на виртуальную машину“.

Приступим к написанию первой программы:

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


  use16
  org 100h
  mov dx,hello
  mov ah,9
  int 21h
  
  mov ax,4C00h
  int 21h
  
  hello db 'Hello, World!$'
  

«use16» – сообщает, что нужно генерировать 16-битный код. Нам нужен именно такой для нашей первой программы.

«org 100h» – объясняет, что следующие команды и данные будут располагаться в памяти, начиная с адреса 100h. Дело в том, что при загрузке нашей программы в память, DOS размещает в первых 256 байтах (с адресов 0000h — 00FFh) свои служебные данные. Нам эти данные изменять нежелательно.

“mov dx,hello” – Помещаем “hello” в регист dx. Делаем что-то типо переменной.

“mov ah,9” – пишем номер функции DOS.

“int 21h” – обращаемся к функции DOS.

“mov ax,4C00h и int 21h” — это стандартное завершение процесса в DOS. Так мы будем завершать все наши программы.

“hello db ‘Hello, World!$’” – сообщаем что в “hello” хранится наше приветствие, которое будет выведено в консоль.

Чтобы откомпилировать программу надо выбрать меню Run->Compile. FASM предложит сохранить файл, если вы этого ещё не сделали, а затем скомпилирует. То есть переведет текст, набранный нами, в машинный код и сделает его программой.

Далее откройте консоль на вашем компьютере (Пуск->Выполнить->cmd и нажимаем Enter, откроется консоль). Потом зайдите в папку где вы сохранили файлы программы, найдите файл с расширением .COM (приложение DOS) возьмите его мышкой перенесите в консоль (либо пропишите путь к этому файлу) и нажмите Enter. После таких манипуляций вы увидите в консоли наше приветствие. Вот так все и работает. Поздравляю все получилось!

Второй урок. Простой код на ассемблере и устройство вкратце.

<< Назад к оглавлению

В данном тексте приведены ссылки на книги. «название
книги»:страница обозначает указатель на определенную страницу книги.
«название книги»:Гглава — главу. При этом ТО — сокращение для тех.
описания контроллера (Datasheet). Например ТО:15 — 15 страница
технического описания. ТО:Г3 — третья глава технического описания

 
Эта статья переработана. Существует так же старая версия с оболочкой MPLAB 8.91. Прочесть ее можно здесь.

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

Итак, что такое контроллер, и как он работает?
Конечно же, это просто черный жучек с ножками. За неимением никаких других членов, он общается с окружающим миром исключительно напряжением на ножках. Что в общем и демонстрировалось в программе в первом уроке. Просто с определенной переодичностью контроллер зажигал
светодиодик.
В
расcматриваемых нами контроллерах память разделена. Программа
записывается в отдельное пространство, именуемое памятью программы
(PROGRAM MEMORY). Переменные и настройки хранятся в памяти данных (DATA
MEMORY). Причем, ячейки памяти, выделенные для использования как
переменные называются регистрами общего назначения (GENERAL PURPOSE
REGISTERS или GPR) и настройки находятся в регистрах специального
назначения (SPECIAL FUNCTIONS REGISTERS или SFR). Память данных поделена
на 15 блоков и блоки меняются регистром BSR (BLOCK SELECT REGISTER).
Так же существует EEPROM memory — это флеш память, используемая для длительного хранения информации.
Подробнее о памяти контроллера смотри главу 1 ссылка.

Код программы

Для начала мы будем писать на ассемблере.
Если вы написали команду на ассемблере, то это будет ровно одна команда, записанная в контроллер.
Таким
образом вы будете отлично знать, что ваш контроллер делает и как. А вот
если вы пишете на C, то на каждую строчку, написанную вами могут
приходиться сотни команд, выполненных контроллером. Знать-то нужно и то и
другое, но ассемблер даст нам понимание того, что и как работает в
контроллере.

Ладно, начнем.
Открываем MPLABX IDE жмем File->New Project. В открывшемся окне выбираем Microchip Embedded Standalone Project

 выбираем семейство контроллеров advanced 8 bit MRUs и наш контроллер, например pic18f2550

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

Первый урок на ассемблере, поэтому выбираем MPASM. Далее. Задаем имя и прочие свойства. Укажите кодировку с русским. Например. UTF-8. Иначе ваши комментарии на кириллице не сохранятся.

И вот проект открыт. По началу в нем есть только makefile. Это файл для сборки проекта. Поэтому щелкаем на Source files->new->empty file

Ну назовем его например main.asm
Переписываем туда программу. Вот код:

Листинг source.asm

  #include <p18f2550.inc> ;подключение заголовков
list p=18f2550 ; выбор контроллера
CONFIG FOSC=INTOSCIO_EC ; Выбран внутренний генератор
  CONFIG MCLRE = OFF ; отключаем сигнал сброса
  CONFIG WDT   = OFF ; отключаем сторожевой таймер

DELAYCOUNTER EQU 0x00  ; задаем две переменные для создания задержки (паузы)
DELAYCOUNTER1 EQU 0x01; в программе. Они по адресу 0x00 и 0x01
A EQU 0; под буквой A будем подразумевать, что используется access bank

ORG 0 ; начинаем с 0 адреса памяти программы
  GOTO main ; перейти на метку main
  ORG 28h ; теперь пишем с 28 адреса. (сделано это для работы с прерываниями)
main ;(начинается программа main- просто метка для нашего удобства)

  CLRF BSR,A;       ; — выбираем нулевой блок памяти данных
  CLRF PORTA,A       ; — обнуляем выход на порту а    ;

  CLRF WDTCON,A; выключаем сторожевой таймер
  MOVLW 0x0F       ; записываем 0x0F в аккумулятор
  MOVWF ADCON1,A    ; перебрасываем значение в ADCON1 — настраиваем порт A на цифровой вход/выход
  CLRF TRISA,A ;- настраиваем порт а — только на выход
  
endloop ; это цикл, в который будет без конца гонять наша программа
  BTG LATA,2,A; мигнуть светодиодом 
  CALL delay255; вызвать паузу
  GOTO endloop; вернуться к началу цикла

delay255 ; функция вызова паузы
  SETF DELAYCOUNTER,A; записать 0xFF в счетчик времени
  GOTO delay; перейти к delay
delay 
  SETF DELAYCOUNTER1,A; записать 0xFF во 2й счетчик времени
delaysub1 ;
  DECFSZ DELAYCOUNTER1,F,A; уменьшить 2й счетчик на 1. если >0
  GOTO delaysub1; то вернуться на шаг назад
  DECFSZ DELAYCOUNTER,F,A; иначе — уменьшить 1й счетчик, если >0
  GOTO delay; вернуться на метку delay
  RETLW 0; иначе — выйти из функции

END ;конец программы

Для контроллера pic18f2550
https://yadi.sk/d/1YkpyyJTVLg2V
Для контроллера pic18f4550
https://yadi.sk/d/FxMGleO_VLgp7 — к сожалению не могу сейчас проверить
для копиляции щелкаем Run->build main project

после
компиляции у нас должен в папке проекта появиться HEX файл. Он находится в папке dist\default\production. Его можно и
нужно прошить в контроллер. Это та же программа, что и в уроке 1.
На самом деле среда MPLABX умеет работать с программаторами, но у меня PICkit 2 и с ней она работает посредственно, поэтому я прошиваю HEX файл напрямую через PICKIT

Пояснения к коду программы

Начнем потихоньку разбирать что там написано

В самом верху указываем то, какой у нас будет контроллер
#include <pic18f2550.inc>
Мы
их рлдключаем для того. чтобы вместо того, чтобы всегда и везде мы
записывали цифрами. мы могли использовать некоторые уже привычные нам
обозначения.
напирмер, без подключения заголовков. команда записи в регистр выбора блока (BSR) -строка 16 нашей программы выглядела бы так
MOVWF 0FE0;

так… Еще директива. 
list p=18f2550
Она просто говорит
компилятору, какой контроллер будем прошивать. 

Дальше уже интереснее. Нам нужно задать управляющее слово. это — настройки контроллера. 
Задает их директива CONFIG 
По поводу всех настроек контроллера обращаемся так же к техническому описанию. ТО:288

Конфигурационные биты

CONFIG FOSC=INTOSCIO_EC
Эта настройка говорит о том, что мы будем использовать внутренний генератор импульсов. Он — внутри контроллера. Читаем в ТО:25

CONFIG MCLRE = OFF
Здесь мы отключаем сигнал сброса. То есть контроллер будет включаться сразу с подачей на него питания.

CONFIG WDT = OFF
Отключаем сторожевой таймер. Просто. чтобы не «запариваться». Он служит защитой от зависания контроллера. Читаем в ТО:299

Более подробно конфигурационные биты рассмотрены во 2-й главе к этому уроку. ссылка

Тело программы

DELAYCOUNTER EQU 0x00
Эта директива (EQU) ставит в соответствие к DELAYCOUNTER 0x00
То есть мы пишем DELAYCOUNTER, а программа думает «0x00»
Вообще мы будем использовать DELAYCOUNTER как переменную. а 0x00 — просто адрес, где она хранится в общих регистрах.
Кстати. Забыл сказать. 0x00 — это просто число. Так мы записываем числа в 16ричной системе счисления.
то есть 0x6B = 107

Мы
используем для нашей программы память, входящую в access RAM, поэтому
мы вольны использовать ячейки памяти от 0x00 до 0x5F. А ячейки от 0x60
до 0xFF — это регистры общего назначения.

ORG 0
Этим мы указываем компилятору, чтобы он писал все последующие команды последовательно с адреса 0.

GOTO main
ORG 28h
main

Переходим на метку main. И начиная с 0x28 адреса пишем программу дальше. Метка main — адрес 0x28
Обратите
внимание, мы оставили свободное место между адресом программы 0x1 и
0x28. Зачем? Если мы будем в последствии использовать прерывания, то в
случае. если прерывание будет требовать на время приостановить
программу. оно начнет выполнять команды по адресу 0x8 или 0x18. и это
место нужно будет заполнить программой обработки прерывания.

Все последующие команды смотри в главе 3. Команды ассемблера. ссылка

MOVWF ADCON1
Когда мы записываем значение в регистр ADCON1. мы меняем настройки АЦП. По поводу 
использования встроенных устройств контроллера смотри главу 4. ссылка

Пожалуйста, помогите сделать статью
лучше. Увидели непонятность, неточность или  ошибку, сообщите в
комментарии, или напишите мне. Спасибо. [email protected]

Первая программа для AVR микроконтроллера на Ассемблере

Приведен и подробно разобран пример простой программы для AVR микроконтроллера на языке Ассемблер (Assembler). Собираем простую схему на микроконтроллере для мигания светодиодами, компилируем программу и прошиваем ее в микроконтроллер под ОС GNU Linux.

Содержание:

  1. Подготовка
  2. Принципиальная схема и макет
  3. Исходный код программы на Ассемблере
  4. Документация по Ассемблеру
  5. Работа с числами в Hex, Bin и Dec
  6. Компиляция и прошивка программы в МК
  7. Заключение

Подготовка

Итак, у нас уже есть настроенный и подключенный к микроконтроллеру программатор, также мы разобрались с программой avrdude, изучили ее настройки и примеры использования. Пришло время разработать свою первую программу, которая будет выполнять какие-то реальные действия с AVR микроконтроллером (МК).

Писать программу мы будем на языке программирования Ассемблер (Assembler, Asm). Основной ее задачей будет заставить поочередно и с установленной задержкой мигать два разноцветных светодиода (красный и синий), имитируя таким образом полицейскую мигалку.

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

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

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

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

В принципе там уже все готово, останется написать код программы и поочередным нажатием двух кнопок (Compile-Flash) скомпилировать и прошить программу в микроконтроллер.

Несмотря на то что у вас уже может быть настроена среда Geany, я приведу все консольные команды которые необходимы для компиляции и прошивки нашей программы в МК.

Принципиальная схема и макет

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

Рис. 1. Принципиальная схема мигалки на светодиодах и микроконтроллере ATmega8.

Примечание: принципиальная схема нарисована за несколько минут в программе Eeschema, которая входит в комплекс программ EDA(Electronic Design Automation) KiCAD (для Linux, FreeBSD, Solaris, Windows). Очень мощный профессиональный инструмент, и что не мало важно — свободный!

Схема устройства состоит из микроконтроллера ATmega8 и двух светодиодов, которые подключены через гасящие резисторы. К микроконтроллеру подключен ISP-коннектор для осуществления программирования через программатор. Также предусмотрены клеммы для подключения внешнего источника питания напряжением 5В.

То как выглядит данная схема в сборе на макетной баспаечной панели (BreadBoard) можно посмотреть на рисунке ниже:

Рис. 2. Конструкция светодиодной мигалки на микроконтроллере ATmega8.

К микроконтроллеру подключен программатор USBAsp, используя ISP интерфейс, от него же и будет питаться наша экспериментальная конструкция. Если нужно запитать конструкцию от внешнего источника питания напряжением 5В то достаточно его подключить к + и — линиям питания панели.

Исходный код программы на Ассемблере

Разработанная нами программа будет попеременно зажигать и гасить два светодиода. Светодиоды подключены к двум пинам PD0 и PD1 микроконтроллера.

Ниже приведен исходный код программы на Ассебмлере(Assembler, Asm) для микроконтроллера ATmega8. Сохраните этот код в файл под названием leds_blinking.asm для последующей работы.

; Светодиодная мигалка на микроконтроллере ATmega8
; https://ph0en1x.net

.INCLUDEPATH "/usr/share/avra/" ; путь для подгрузки INC файлов
.INCLUDE "m8def.inc"            ; загрузка предопределений для ATmega8
.LIST                           ; включить генерацию листинга

.CSEG                           ; начало сегмента кода
.ORG 0x0000                     ; начальное значение для адресации

; -- инициализация стека --
LDI R16, Low(RAMEND)  ; младший байт конечного адреса ОЗУ в R16
OUT SPL, R16          ; установка младшего байта указателя стека
LDI R16, High(RAMEND) ; старший байт конечного адреса ОЗУ в R16
OUT SPH, R16          ; установка старшего байта указателя стека

.equ Delay = 5        ; установка константы времени задержки

; -- устанавливаем пины PD0 и PD1 порта PORTD (PD) на вывод --
LDI R16, 0b00000011   ; поместим в регистр R16 число 3 (0x3)
OUT DDRD, R16         ; загрузим значение из регистра R16 в порт DDRD

; -- основной цикл программы --
Start:
    SBI PORTD, PORTD0 ; подача на пин PD0 высокого уровня
    CBI PORTD, PORTD1 ; подача на пин PD1 низкого уровня
    RCALL Wait        ; вызываем подпрограмму задержки по времени
    SBI PORTD, PORTD1 ; подача на пин PD1 высокого уровня
    CBI PORTD, PORTD0
    RCALL Wait
    RJMP Start        ; возврат к метке Start, повторяем все в цикле

; -- подпрограмма задержки по времени --
Wait:
    LDI  R17, Delay   ; загрузка константы для задержки в регистр R17
WLoop0:  
    LDI  R18, 50      ; загружаем число 50 (0x32) в регистр R18
WLoop1:  
    LDI  R19, 0xC8    ; загружаем число 200 (0xC8, $C8) в регистр R19
WLoop2:  
    DEC  R19          ; уменьшаем значение в регистре R19 на 1
    BRNE WLoop2       ; возврат к WLoop2 если значение в R19 не равно 0 
    DEC  R18          ; уменьшаем значение в регистре R18 на 1
    BRNE WLoop1       ; возврат к WLoop1 если значение в R18 не равно 0
    DEC  R17          ; уменьшаем значение в регистре R17 на 1
    BRNE WLoop0       ; возврат к WLoop0 если значение в R17 не равно 0
RET                   ; возврат из подпрограммы Wait

Program_name: .DB "Simple LEDs blinking program"

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

Все строки и части строк, которые начинаются с символа «;» — это комментарии. При компиляции и выполнении программы такие строчки игнорируются, они служат для документирования и примечаний.

При помощи директивы «.INCLUDEPATH» мы указываем путь «/usr/share/avra/», по которому компилятору нужно искать файлы для включения их в текущий файл с использованием директив «.INCLUDE«. В нашем примере подключается файл, полный путь к которому будет выглядеть вот так: «/usr/share/avra/m8def.inc».

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

Директива «.CSEG» (CodeSEGment) определяет начало программного сегмента (код программы что записан во флешь-память) — сегмента кода. Соответственно все что размещено ниже этой директивы относится к программному коду.

Для определения сегмента данных (RAM, оперативная память) или памяти EEPROM используются директивы «.DSEG» и «.ESEG» соответственно. Таким образом выполняется распределение памяти по сегментам.

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

При помощи директивы «.ORG» компилятору указывается начальный адрес «0x0000» сегмента, в данном случае мы указали начальный адрес сегмента кода. В данной программе эту директиву можно было бы и не использовать, поскольку по умолчанию адрес программного кода всегда 0x0000.

Дальше в коде происходит инициализация стека. Стек (Stack) — это область памяти (как правило у всех AVR чипов размещается в SRAM), которая используется микропроцессором для хранения и последующего считывания адресов возврата из подпрограмм, а также для других пользовательских нужд.

При вызове подпрограммы flhtc nt записывается в стек и начинается выполнение кода подпрограммы. По завершению подпрограммы (директива RET)

Стек работает по принципу LIFO (Last In — First Out, последним пришёл — первым вышел). Для адресации вершины стека используется указатель стека — SP (Stack Pointer), это может быть однобайтовое или двухбайтовое значение в зависимости от доступного количества SRAM памяти в МК.

При помощи инструкции «LDI» мы загружаем в регистр R16 значение младшего байта конечного адреса ОЗУ «Low(RAMEND)» (предопределенная константа в файле m8def.inc что содержит адрес последней ячейки SRAM), а потом при помощи инструкции OUT выполняем загрузку данного значения из регистра R16 в порт SPL (Stack Pointer Low). Таким же образом производится инициализация старшего байта адреса в указателе стека SPH.

Инструкция LDI используется для загрузки старшего и младшего значений из константы в регистр общего назначения. А инструкция OUT позволяет выполнить операцию загрузки с немного иной спецификой — из регистра общего назначения в регистр периферийного устройства МК, порт ввода-вывода и т.п.

Если не произвести инициализацию стека то возврат из подпрограмм станет невозможным, к примеру в приведенном коде после выполнения инструкции перехода к подпрограмме «RCALL Wait» возврат не будет выполнен и программа не будет работать как нужно.

Директива «.equ» выполняет присвоение указанному символьному имени «Delay» числового значения «5», по сути мы объявили константу. Имя константы должно быть уникальным, а присвоенное значение не может быть изменено в процессе работы программы.

Дальше мы устанавливает два канала (пины PD0, PD1) порта DDRD (PortD) на вывод, делается это загрузкой двоичного значения 0b00000011 (0x3, число 3) в регистр R16 с последующим выводом этого значения из него в порт DDRD при помощи команды OUT.

По умолчанию все каналы (пины) порта настроены на ввод. При помощи двоичного числа 0b00000011, где последние биты установлены в 1, мы переводим каналы PD0 и PD1 в режим вывода. 

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

При помощи инструкции «SBI» выполняем установку бита PORTD0 (предопределен в файле m8def.inc) в порте PORTD чем установим на пине PD0 высокий уровень. Используя инструкцию «CBI» выполняется очистка указанного (PORTD1) бита в порте PORTD и тем самым устанавливается низкий уровень на пине PD1.

Дальше с помощью инструкции RCALL выполняем относительный вызов подпрограммы которая начинается с метки «Wait:«. Здесь для запоминания адреса возврата уже используется стек, который мы инициализировали в начале программы.

После завершения подпрограммы (в нашем случае ее функция — задержка по времени) программа вернется к позиции где был выполнен вызов подпрограммы (адрес возврата будет получен из стека) и с этого места продолжится выполнение последующих операторов.

После вызова подпрограммы задержки «Wait» следуют вызовы инструкций SBI и CBI в которых выполняется установка битов порта PORTD таким образом, что теперь на пине PD0 у нас будет низкий уровень, а на пине PD1 — высокий.

По завершению этих инструкций следует еще один вызов подпрограммы задержки «Wait», а дальше следует инструкция «RJMP» которая выполнит относительный переход к указанной метке — «Start», после чего программа снова начнет установку битов в порте с задержками по времени.

Таким образом выполняется реализация бесконечного цикла в котором будут «дергаться» пины порта PORTD микроконтроллера и поочередно зажигаться/гаснуть светодиоды которые подключены к каналам данного порта (пины PD0, PD1).

После основного цикла программы следует наша подпрограмма задержки по времени. Принцип ее работы заключается в выполнении трех вложенных циклов, в каждом из которых происходит вычитание (DEC) единички из числа которое хранится в отдельном регистре, и так до тех пор пока значение не достигнет нуля. Инструкция «DEC» декрементирует значение указанного регистра и требует для этого 1 рабочий такт процессора.

При помощи инструкций «BRNE» (условный переход) выполняется анализ нулевого бита статусных флагов процессора (Zero Flag, ZF). Переход на указанную в инструкции метку будет выполнен если после выполнения предыдущей команды нулевой флаг был установлен. В данном случае проверяется значение нулевого флага после выполнения команд «DEC» над значениями которые хранится в регистрах общего назначения (R17, R18, R19). Инструкция «BRNE» требует 1/2 такта процессора.

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

По умолчанию, без установки фьюзов что задают источник и частоту тактового генератора, в микроконтроллере ATmega8 используется откалиброванный внутренний RC-генератор с частотой 1МГц. Если же мы изменим частоту МК на 4Мгц то наши светодиоды начнут мигать в 4 раза быстрее, поскольку на каждую операцию вычитания и сравнения будет тратиться в 4 раза меньше времени.

Завершается подпрограмма инструкцией «RET«, которая выполняет возврат из подпрограммы и продолжение выполнения инструкций с того места, с которого эта подпрограмма была вызвана (на основе сохраненного адреса возвращения, который сохранился в стеке при вызове инструкции «RCALL»).

При помощи директивы «.DB» в памяти программ (флешь) резервируется цепочка из байтов под строчку данных «Simple LEDs blinking program», эти данные являются статичными и их нельзя изменять в ходе работы программы. Для резервирования слов (Double Word) нужно использовать директиву «.DW».

В данном случае, у нас во FLASH-память вместе с программным кодом будет записана строка «Simple LEDs blinking program«, которая содержит название программы. Данные из этой строчки нигде в программе не используются и приведены в качестве примера.

При каждом резервировании данных с использованием директивы «.DB» или «.DW» должна предшествовать уникальная метка, которая пригодится нам когда нужно будет получить адрес размещаемых данных в памяти для дальнейшего их использования, в нашем случае это «Program_name:«.

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

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

RJMP DataEnd
Program_name: .DB "Simple LEDs blinking program"
DataEnd:

Документация по Ассемблеру

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

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

Приведу несколько полезных документов, которые вы можете скачать и использовать для справки при разработке программ на AVR ASM.

Справка по Ассемблеру для Atmel AVR (перевод Руслана Шимкевича): atmel-avr-assembler-quick-doc-ru.zip (16Кб, HTML, RU).

Справка по инструкциям Atmel Assembler: atmel-avr-instruction-set-manual-en.pdf.zip (700Кб, PDF, EN, 2015).

Работа с числами в Hex, Bin и Dec

В коде программы для загрузки значений в регистры используются числа и в скобках приведены их значения в шестнадцатеричной системе счисления, например: «50 (0x32, )». В двоичной системе счисления числа указываются в формате «0b00000011». Для удобной переконвертации чисел из шестнадцатеричной системы счисления в десятичную, двоичную и наоборот отлично подходит программный калькулятор из среды рабочего окружения KDE — KCalc.

Рис. 3. KCalc — простое и эффективное решение для пересчета между разными системами счисления.

В настройках (Settings) нужно выбрать режим (Numeral System Mode), после чего программа приобретет вид что на рисунке выше. Переключаться между системами счисления можно устанавливая флажки в полях «Dec», «Hex», «Bin». Для примера: переключаемся в Hex и набираем «FF», потом переключаемся в Dec и видим число в десятичной системе счисления — 255, просто и удобно.

В операционной системе GNU Linux с рабочей средой GNOME (например Ubuntu) также есть подобный калькулятор, это программа — galculator.

Компиляция и прошивка программы в МК

 Итак, у нас уже есть полный код программы, который мы сохранили в файл с именем «leds_blinking.asm». Теперь самое время скомпилировать его, делается это нажатием кнопки «Compile» в предварительно настроенной среде Geany или же отдельной командой в консоли:

avra --includepath /usr/share/avra/ leds_blinking.asm

Если результат выполнения будет без ошибок то мы получим файл прошивки в формате Intel HEX — «leds_blinking.hex», который уже можно прошивать во флешь-память микроконтроллера.

Примечание: опцию «—includepath /usr/share/avra/» можно и не указывать, поскольку в файле с исходным кодом уже была указана директива «.INCLUDEPATH» для поиска файлов с предопределениями для разных моделей МК.

Осталось прошить микроконтроллер используя полученный файл «leds_blinking.hex». В примере я использую программатор USBAsp и микроконтроллер ATmega8, вот так выглядит команда для записи получившегося файла во флешь-память МК:

avrdude -p m8 -c usbasp -P usb -U flash:w:leds_blinking.hex

Примечание: в команде используется относительный путь к файлу leds_blinking.hex, поэтому для успешного выполнения команды нужно перейти в терминале(консоли) в директорию где находится данный файл.

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

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

Заключение

Увеличив значение константы «Delay» можно уменьшить частоту мерцания светодиодов, а уменьшив — увеличить частоту. Также можете попробовать добавить несколько светодиодов к свободным каналам порта (PD2-PD7) и модифицировать программу таким образом чтобы получить бегущий огонь из светодиодов.

В заключение приведу краткое видео работы рассмотренной схемы на двух светодиодах:

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

Начало цикла статей: Программирование AVR микроконтроллеров в Linux на языках Asembler и C.

Запись сборки ARM (Часть 1)

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

Пошагово будут рассмотрены следующие темы:

Учебное пособие по основам сборки ARM

:
Часть 1: Введение в сборку ARM
Часть 2: Регистры типов данных
Часть 3: Набор инструкций ARM
Часть 4: Инструкции памяти: загрузка и сохранение данных
Часть 5: Загрузка и сохранение нескольких частей
6: Условное выполнение и ветвление
Часть 7: Стек и функции

Чтобы следовать примерам, вам понадобится лабораторная среда на базе ARM.Если у вас нет устройства ARM (например, Raspberry Pi), вы можете настроить свою собственную лабораторную среду на виртуальной машине с помощью QEMU и дистрибутива Raspberry Pi, следуя этому руководству. Если вы не знакомы с базовой отладкой с помощью GDB, вы можете получить основы в этом руководстве. В этом руководстве основное внимание будет уделено 32-разрядной версии ARM, а примеры скомпилированы на ARMv6.

Почему ARM?

Это руководство обычно предназначено для людей, которые хотят изучить основы сборки ARM. Специально для тех из вас, кто интересуется написанием эксплойтов на платформе ARM.Возможно, вы уже заметили, что процессоры ARM повсюду вокруг вас. Когда я оглядываюсь вокруг, я могу насчитать в моем доме гораздо больше устройств с процессором ARM, чем процессоры Intel. Сюда входят телефоны, маршрутизаторы и, не говоря уже об устройствах Интернета вещей, продажи которых в наши дни, похоже, стремительно растут. Тем не менее, процессор ARM стал одним из самых распространенных ядер ЦП в мире. Это подводит нас к тому факту, что, как и ПК, устройства IoT подвержены неправильным злоупотреблениям при проверке ввода, таким как переполнение буфера.Учитывая широкое использование устройств на базе ARM и возможность неправильного использования, атаки на эти устройства стали гораздо более распространенными.

Тем не менее, у нас больше экспертов, специализирующихся на исследованиях безопасности x86, чем в ARM, хотя язык ассемблера ARM, пожалуй, самый простой из широко распространенных языков ассемблера. Итак, почему все больше людей не уделяют внимания ARM? Возможно, потому, что существует больше учебных ресурсов, посвященных эксплуатации на Intel, чем на ARM. Подумайте о великолепных руководствах по Intel x86 Exploit, написанных Fuzzy Security или Corelan Team — подобные руководства помогают людям, интересующимся этой конкретной областью, получить практические знания и вдохновение для изучения, выходящего за рамки того, что описано в этих руководствах.Если вы заинтересованы в написании эксплойтов x86, учебники Corelan и Fuzzysec станут вашей идеальной отправной точкой. В этой серии руководств мы сосредоточимся на основах сборки и написании эксплойтов на ARM.

Процессор

ARM против процессора Intel

Между Intel и ARM много различий, но главное отличие — это набор команд. Intel — это процессор CISC (Compstruction Set Computing), который имеет более крупный и многофункциональный набор инструкций и позволяет многим сложным инструкциям обращаться к памяти.Следовательно, он имеет больше операций, режимов адресации, но меньше регистров, чем ARM. Процессоры CISC в основном используются на обычных ПК, рабочих станциях и серверах.

ARM является процессором RISC (вычисление с сокращенным набором инструкций) и поэтому имеет упрощенный набор инструкций (100 инструкций или меньше) и регистры более общего назначения, чем CISC. В отличие от Intel, ARM использует инструкции, которые работают только с регистрами, и использует модель памяти Load / Store для доступа к памяти, что означает, что только инструкции Load / Store могут обращаться к памяти.Это означает, что для увеличения 32-битного значения по определенному адресу памяти на ARM потребуется три типа инструкций (загрузка, увеличение и сохранение), чтобы сначала загрузить значение по определенному адресу в регистр, увеличить его в регистре и сохранить это обратно в память из реестра.

Уменьшенный набор команд имеет свои достоинства и недостатки. Одним из преимуществ является то, что инструкции могут выполняться быстрее, потенциально обеспечивая большую скорость (системы RISC сокращают время выполнения за счет сокращения тактовых циклов на инструкцию).Обратной стороной является то, что меньшее количество инструкций означает больший акцент на эффективном написании программного обеспечения с ограниченными доступными инструкциями. Также важно отметить, что ARM имеет два режима: режим ARM и режим Thumb. Инструкции Thumb могут быть 2 или 4 байтами (подробнее об этом в Части 3: Набор инструкций ARM).

Еще больше различий между ARM и x86:

  • В ARM большинство инструкций можно использовать для условного выполнения.
  • Процессоры Intel серии x86 и x86-64 используют формат с прямым порядком байтов
  • Архитектура ARM была прямым порядком байтов до версии 3.С тех пор процессоры ARM стали BI-endian и имеют настройку, которая позволяет переключать endianness .

Есть различия не только между Intel и ARM, но и между разными версиями ARM. Цель этой серии руководств — сделать ее как можно более общей, чтобы вы получили общее представление о том, как работает ARM. Как только вы поймете основы, вы легко узнаете нюансы для выбранной вами целевой версии ARM. Примеры в этом руководстве были созданы на 32-битной ARMv6 (Raspberry Pi 1), поэтому пояснения относятся именно к этой версии.

Названия различных версий ARM также могут сбивать с толку:

Семейство ARM Архитектура ARM
ARM7 ARM v4
ARM9 ARM v5
ARM11 ARM v6
Cortex-А ARM v7-A
Cortex-R ARM v7-R
Cortex-M ARM v7-M

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

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

Так что же такое ассемблер? Язык ассемблера — это всего лишь тонкий слой синтаксиса поверх машинного кода, который состоит из инструкций, закодированных в двоичных представлениях (машинном коде), что и понимает наш компьютер. Так почему бы вместо этого просто не написать машинный код? Что ж, это было бы занозой в заднице. По этой причине мы напишем сборку ARM, которая намного проще для понимания людьми.Наш компьютер не может запускать ассемблерный код сам, потому что ему нужен машинный код. Инструмент, который мы будем использовать для сборки ассемблерного кода в машинный код, — это GNU Assembler из проекта GNU Binutils с именем as , который работает с исходными файлами, имеющими расширение * .s.

После того, как вы написали файл сборки с расширением * .s, вам нужно собрать его с помощью as и связать с ld:

 $ как program.s -o program.o
$ ld program.o -o программа 

Давайте начнем с самого низа и перейдем к языку ассемблера.На самом низком уровне у нас есть электрические сигналы в нашей цепи. Сигналы формируются путем переключения электрического напряжения на один из двух уровней, например, 0 вольт («выключено») или 5 вольт («включено»). Поскольку просто посмотрев, мы не можем легко определить, какое напряжение находится в цепи, мы решили записать шаблоны включенных / выключенных напряжений, используя визуальные представления, цифры 0 и 1, чтобы не только представить идею отсутствия или наличия сигнал, но также потому, что 0 и 1 — цифры двоичной системы. Затем мы группируем последовательность 0 и 1, чтобы сформировать инструкцию машинного кода, которая является наименьшей рабочей единицей компьютерного процессора.Вот пример инструкции на машинном языке:

1110 0001 1010 0000 0010 0000 0000 0001

Пока все хорошо, но мы не можем вспомнить, что означает каждый из этих шаблонов (0 и 1). По этой причине мы используем так называемую мнемонику, аббревиатуры, чтобы помочь нам запомнить эти двоичные шаблоны, где каждой инструкции машинного кода дается имя. Эти мнемоники часто состоят из трех букв, но это не обязательно. Мы можем написать программу, используя эту мнемонику в качестве инструкций.Эта программа называется программой на языке ассемблера, а набор мнемоник, который используется для представления машинного кода компьютера, называется языком ассемблера этого компьютера. Следовательно, язык ассемблера — это самый низкий уровень, используемый людьми для программирования компьютера. Операнды инструкции идут после мнемоники. Вот пример:

MOV R2, R1

Теперь, когда мы знаем, что программа сборки состоит из текстовой информации, называемой мнемоникой, нам нужно преобразовать ее в машинный код.Как упоминалось выше, в случае сборки ARM проект GNU Binutils предоставляет нам инструмент с именем как . Процесс использования ассемблера, такого как как , для преобразования из языка ассемблера (ARM) в машинный код (ARM) называется сборкой.

Таким образом, мы узнали, что компьютеры понимают (реагируют) на наличие или отсутствие напряжений (сигналов) и что мы можем представлять несколько сигналов в виде последовательности нулей и единиц (битов). Мы можем использовать машинный код (последовательности сигналов), чтобы заставить компьютер реагировать определенным образом.Поскольку мы не можем вспомнить, что означают все эти последовательности, мы даем им сокращения — мнемонику и используем их для представления инструкций. Этот набор мнемоник является языком ассемблера компьютера, и мы используем программу под названием Assembler для преобразования кода из мнемонического представления в машиночитаемый машинный код так же, как это делает компилятор для языков высокого уровня.

6 лучших + бесплатные курсы и классы по языку ассемблера [2021 ОКТЯБРЬ]

Изучение языка ассемблера имеет решающее значение для каждого, кто работает в индустрии разработки игр.Это поможет вам стать лучшим разработчиком игр и позволит вам понять другие аспекты разработки игр. Многие люди пытаются изучать ассемблер самостоятельно, но не получают нужных ресурсов или платформ для достижения своих целей. Здесь мы пытаемся помочь каждому выучить язык ассемблера из лучших ресурсов и платформ электронного обучения. Наши отраслевые эксперты составили список из лучших классов, курсов, учебных пособий, программ обучения и сертификации на ассемблере, доступных в Интернете. Мы постарались включить в список все важные курсы или учебные пособия, чтобы расширить ваши знания и понимание языка ассемблера. Более того, он состоит из бесплатных и платных курсов, которые помогут вам выбрать учебную программу в соответствии с вашим бюджетом. Ознакомьтесь с нашей подборкой лучших курсов эликсира .

6 лучших + бесплатных курсов и занятий по языку ассемблера [2021 ОКТЯБРЬ]

1. Изучите язык ассемблера, создав игры для Atari 2600 (Udemy)

Разработка игр становится все более и более важной в секторе разработки программного обеспечения.Вот почему изучение языка ассемблера стало важным навыком для разработчиков игр. Этот курс от Udemy разработан, чтобы помочь вам повысить свои навыки в разработке игр. Это , ориентированный на Atari 2600, который позволит вам изучить базовые и базовые концепции программирования с использованием языка ассемблера 6502 . Независимо от того, являетесь ли вы новичком в разработке игр или имеете многолетний опыт, вы найдете этот курс очень полезным и практичным для вашей карьеры. Благодаря минимальной аппаратной структуре вы получите опыт работы с классической консолью Atari 2600 VCS.

Ключевые УТП —

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

— Изучите и поймите, как работает кодирование низкоуровневого программирования и дает вывод на языке программирования высокого уровня.

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

— Изучите работу различных игровых эмуляторов при создании и запуске собственных картриджей Atari ROM.

Продолжительность: 15 часов

Рейтинг: 4,7 из 5

Вы можете Зарегистрируйтесь здесь

Обзор: отличное введение в язык ассемблера и сложности косвенного программирования оборудования. VCS представляет собой хороший пример проблем с синхронизацией, хотя ее применение сегодня немного устарело.- Крейг Стоббс.

2. Приключения языка ассемблера: полный курс (удеми)

Этот курс, от основ до программирования продвинутого уровня, поможет вам изучить язык ассемблера под руководством группы опытных преподавателей и профессионалов отрасли. Серия x86 Adventures, представленная в этом курсе, поможет вам начать работу с нуля без каких-либо предварительных знаний. Более того, студент узнает о двоичной системе кодирования, поразрядных операциях, подпрограммах логики и многом другом .Существуют различные упражнения, позволяющие отрабатывать кодирование для создания различных программ и проектов. Студенты также могут получить доступ к этим учебным ресурсам на GitHub. Кроме того, вы испытаете работу технически подкованных инструментов — от ассемблера до отладчика. Взгляните на наш взгляд на Best Julia Programming Courses .

Ключевые УТП —

— Изучите и поймите структуры кодирования языка ассемблера с самого начала с помощью архитектуры x86 и ее компонентов.

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

— Узнайте, как писать код на архитектуре x86 с помощью языка ассемблера, понимая, как работает компьютер.

— Узнайте о FASM (Flat Assembler), а также о его установке и основных концепциях, которые могут помочь глубже погрузиться в программирование на языке ассемблера.

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

Продолжительность: 30 часов

Рейтинг: 4.6 из 5

Вы можете Зарегистрируйтесь здесь

Отзыв: Учитель очень подробный и ничего не предполагает. Объяснения исчерпывающие и эффективные, с добавлением немного невозмутимого юмора. 5 звезд за отличный курс. — Мэтт Саут.

3. Программирование на языке ассемблера x86 с нуля ™ (Udemy)

Это еще один отличный курс по Udemy, который учит программированию на языке ассемблера x86 от Ground Up ™.Этот курс включает полную историю, архитектуру и практическую реализацию ассемблера. Регистрация в этой программе поможет вам повысить квалификацию с помощью набора команд x86 Streaming SIMD Extension (SSE), x86 Core Instruction Set и x86 Advanced Vector Extension (AVX) Instruction Set. Студенты получат навыки, разработав сложные алгоритмы обработки изображений на ассемблере x86. По окончании курса вы сможете составлять сложные математические алгоритмы для надежных систем и приложений для мэйнфреймов.

Ключевые УТП —

— Познакомьтесь с программированием на языке ассемблера x86 и его основными концепциями, связанными с низкоуровневым программированием.

— Освойте основные принципы набора команд x86, набора команд расширения MMX и расширенного набора команд векторного расширения.

— Узнайте, как разрабатывать сложные математические алгоритмы в сборках x86, понимая различия в кодировании между x86-32 и x86-64.

— Уметь создавать таблицы переходов и таблицы поиска в сборке x86 и понимать, как применять стандарт с плавающей запятой IEEE-754.

— Свобода подготовки, не выходя из дома, с полным доступом к материалам курса на любом устройстве.

Продолжительность: 15 часов

Рейтинг: 4,7 из 5

Вы можете Зарегистрируйтесь здесь

Обзор: отличное введение в язык ассемблера и сложности косвенного программирования оборудования. VCS представляет собой хороший пример проблем с синхронизацией, хотя ее применение сегодня немного устарело.- Крейг Стоббс.

4. Язык ассемблера ARM от Ground Up ™ 1 (Udemy)

Это продвинутый курс программирования на ассемблере ARM от Ground Up, предназначенный для повышения ваших навыков. Он поможет вам охватить все основные и второстепенные аспекты практического ассемблера, такие как проектирование, архитектура и программирование систем ARM с различными структурами данных, такими как FIFO и LIFO. Кроме того, студенты будут разрабатывать алгоритмы автоматизации в конечных автоматах, таких как машины Мили и Мура.В конце этого курса вы сможете создавать надежные и сложные алгоритмы, решая сложные математические задачи, такие как серия Тейлора, и выполняя двоичный поиск в сборке. Не забудьте проверить наш список из лучших курсов Lua .

Ключевые УТП —

— Получите обзор языка ассемблера ARM, процессоров, вычислительных устройств и инструментов сборки, чтобы получить полезные знания о языках программирования низкого уровня.

— Понять работу философии дизайна ARM, встроенных систем, объединенных с процессорами ARM и технологией шины.

— Мастер создания структур данных на ассемблере, построения сложных алгоритмов на ассемблере и написания драйверов оборудования на ассемблере.

— Узнайте о различных наборах инструкций ARM и архитектуре RISC, чтобы овладеть искусством построения структур данных на ассемблере.

— Обзор Kell µversion, а также сведения о том, как она работает с программированием на ассемблере.

Продолжительность: 16 часов

Рейтинг: 4.2 из 5

Вы можете Зарегистрируйтесь здесь

Обзор: Объяснение было потрясающим. Один из лучших курсов, которые я прошел, и который стоит потраченного времени. Инструктор всегда на связи, если возникнут какие-либо проблемы или сомнения. — Сохан Джозеф Салдана.

5. Ассемблерные языки и ассемблеры Еврейского университета в Иерусалиме (Coursera)

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

Ключевые УТП —

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

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

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

— Изучите процесс сборки аппаратного обеспечения вместе с реализацией языка ассемблера в различных компьютерных системах.

Продолжительность: 2-3 часа

Рейтинг: 4.9 из 5

Вы можете Зарегистрируйтесь здесь

Отзыв: один из лучших курсов, которые я прошел на Coursera. Сейчас я чувствую себя гораздо лучше осведомленным о низкоуровневой работе компьютеров. Я надеюсь, что в какой-то момент Coursera предложит вторую часть курса.- RQ.

6. Учебное пособие по программированию на сборке (Учебное пособие)

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

Ключевые УТП —

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

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

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

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

Продолжительность: часы

Рейтинг: 4.4 из 5

Вы можете Зарегистрируйтесь здесь

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

Давайте узнаем сборку x86-64! Часть 0

То, как меня учили сборке x86 в университете, было полностью устаревшим за много лет к тому времени, когда я пошел в первый класс. Это было где-то в 2008 или 2009 году, и 64-битные процессоры уже начали становиться вещью даже на моей шее. Тем временем мы занимались DOS, реальным режимом, сегментацией памяти и всем остальным из старых плохих времен.

Тем не менее, я усвоил достаточно этого во время занятий (и в последующие годы), чтобы понять, что происходит на другом конце компилятора, и это помогло мне несколько раз. Однако я никогда не писал вручную сколько-нибудь значительного объема сборки x86 для чего-то нетривиального. Из-за того, что я был заперт внутри (из-за глобальной пандемии), я решил изменить эту ситуацию, чтобы скоротать время.

Я хотел сосредоточиться конкретно на x86-64 и полностью забыть / пропустить любую устаревшую хрень, которая больше не актуальна для этой архитектуры.После того, как я немного углубился в это, я также решил опубликовать свои заметки в виде руководств в этом блоге, поскольку, похоже, есть желание для этого типа контента.

Все, что я напишу в этих сообщениях, будет обычной 64-битной программой для Windows. Мы будем использовать Windows, потому что это ОС, которую я использую на всех моих нерабочих машинах, и когда вы опускаетесь до уровня написания сборки, становится невероятно невозможно игнорировать операционную систему, на которой вы работаете. .Я также постараюсь пойти как можно более «с нуля» — никаких библиотек, нам разрешено только обращаться к операционной системе и все.

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

Получение инструментов

В этой серии статей мы будем использовать два основных инструмента.

Ассемблер

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

Единого согласованного стандарта для языка ассемблера x86-64 не существует.Существует много ассемблеров, и хотя некоторые из них имеют много общего, у каждого есть свой набор функций и причуд. Поэтому важно, какой ассемблер вы выберете. В этой серии мы будем использовать
Flat Assembler (или сокращенно FASM). Мне он нравится, потому что он маленький, простой в получении и использовании, имеет хорошую систему макросов и поставляется с удобным маленьким редактором.

Отладчик

Еще один важный инструмент — отладчик. Мы будем использовать его для проверки состояния наших программ.Хотя я почти уверен, что для этого можно использовать интегрированный отладчик Visual Studio, я думаю, что автономный отладчик лучше, когда все, что вам нужно сделать, это посмотреть на разборку, память и регистры. Я всегда использовал OllyDbg для подобных вещей, но, к сожалению, у него нет 64-битной версии. Поэтому мы будем использовать WinDbg. Приведенная здесь версия представляет собой обновленную версию этого почтенного инструмента с немного более приятным интерфейсом. Кроме того, вы можете получить версию, отличную от магазина Windows, здесь как часть Windows 10 SDK.Просто убедитесь, что во время установки вы отменили выбор всего остального, кроме WinDbg. Для наших целей эти две версии в основном взаимозаменяемы.

Мыслить в сборке

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

Вид на 10000 футов

ЦП только «умеют» делать фиксированное количество определенных вещей. Когда вы слышите, как кто-то говорит о «наборе инструкций», они имеют в виду набор вещей, для которых был разработан конкретный ЦП, а термин «инструкция» просто означает «одну из вещей, которые может делать ЦП». Большинство инструкций так или иначе параметризованы и, как правило, очень просты. Обычно инструкция представляет собой что-то вроде «записать заданное 8-битное значение в заданное место в памяти» или «интерпретировать значения из регистров A и B как 16-битные целые числа со знаком, умножить их и записать результат в регистр. А «.

Ниже представлена ​​простая ментальная модель архитектуры, с которой мы начнем.

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

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

Регистры

Вы можете думать о регистрах как о особом виде памяти, встроенной прямо в ЦП, которая очень мала, но чрезвычайно быстро доступна. В x86-64 существует множество различных типов регистров, и пока мы будем рассматривать только так называемые универсальные регистры , которых шестнадцать. Каждый из них имеет ширину 64 бита, и для каждого из них младший байт, слово и двойное слово можно адресовать индивидуально (кстати, 1 «слово» = 2 байта, 1 «двойное слово» = 4 байта, если вы раньше не слышал эту терминологию).

Регистр Младший байт Младшее слово Нижнее слово
rax al ax eax
rbx bl bx ebx
rcx cl cx ecx
rdx dl dx edx
rsp spl sp esp
RSI Sil Si ESI
rdi dil di edi
баррель баррель баррель баррель
R8 R8B R8W R8D
R9 R9B R9W R9D
R10 R10B R10W R10D
R11 R11B R11W R11D
R12 R12B R12W R12D
R13 R13B R13W R13D
R14 R14B R14W R14D
R15 R15B R15W R15D

Кроме того, старшие 8 битов rax , rbx , rcx и rdx могут называться ah , bh , ch и dh .

Обратите внимание, что хотя я сказал, что это регистры «общего назначения», некоторые инструкции могут использоваться только с определенными регистрами, а некоторые регистры имеют особое значение для определенных инструкций. В частности, rsp содержит указатель стека (который используется такими инструкциями, как push , pop , call и ret ), а rsi и rdi служат в качестве индекса источника и назначения для » строковые «манипуляции» инструкциями.Другой пример, когда определенные регистры подвергаются «специальной обработке», — это инструкции умножения, которые требуют, чтобы одно из значений умножителя было в регистре rax , и записывают результат в пару регистров rax и rdx .

Помимо этих регистров, мы также рассмотрим специальные регистры rip и rflags . rip содержит адрес следующей инструкции для выполнения.Он модифицируется инструкциями потока управления, такими как call или jmp . rflags содержит набор двоичных флагов, указывающих различные аспекты состояния программы, например, был ли результат последней арифметической операции меньше, равен или больше нуля. Поведение многих инструкций зависит от этих флагов, и многие инструкции обновляют определенные флаги как часть своего выполнения. Регистр флагов также можно читать и записывать «оптом» с помощью специальных инструкций.

На x86-64 регистров намного больше. Большинство из них используются для команд SIMD или с плавающей запятой, и мы не будем рассматривать их в этой серии.

Память и адреса

Вы можете думать о памяти как о большом массиве байтовых «ячеек», пронумерованных, начиная с 0. Мы будем называть эти числа «адресами памяти». Все просто, правда?

Что ж … раньше адресация памяти была довольно утомительной.Видите ли, регистры в старых процессорах x86 были только 16-битными. Шестнадцати бит достаточно для адресации памяти объемом 64 килобайта, но не более. Аппаратное обеспечение фактически могло использовать адреса шириной до 20 бит, но вы поместили «базовый» адрес в специальный сегментный регистр, а инструкции, которые читают или записывают память, будут использовать 16-битное смещение в этом сегменте для получения окончательного 20-битный «линейный» адрес. Существовали отдельные сегментные регистры для частей кода, данных и стека (и еще несколько «лишних»), и сегменты могли перекрываться.

В x86-64 этих проблем нет. Сегментные регистры для кода, данных и стека все еще присутствуют, и они загружены некоторыми специальными значениями, но как программист пользовательского пространства вам не нужно беспокоиться о них. Во всех смыслах и целях вы можете предположить, что все сегменты начинаются с 0 и распространяются на всю адресуемую длину памяти. Итак, насколько нам известно, на x86-64 наши программы рассматривают память как «плоский» непрерывный массив байтов с последовательными адресами, начинающимися с 0, как мы уже говорили в начале этого раздела…

Ладно, возможно, я немного исказил правду. Все не так просто. Хотя верно, что в 64-битной Windows ваши программы видят память как плоский непрерывный массив байтов с адресами, начинающимися с 0, на самом деле это сложная иллюзия, поддерживаемая совместной работой ОС и ЦП.

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

Каждый процесс получает «плоское» адресное пространство, как описано выше (мы назовем его «виртуальным адресным пространством»). Для каждого процесса ОС устанавливает соответствие между его виртуальными адресами и фактическими физическими адресами в памяти. Это сопоставление соблюдается аппаратным обеспечением: «виртуальные» адреса динамически преобразуются в физические адреса во время выполнения.Таким образом, один и тот же адрес (например, 0x410F119C) может отображаться в два разных места в физической памяти для двух разных процессов. Вкратце, так происходит разделение между процессами.

И последнее, на что я хочу обратить ваше внимание, это то, как инструкции и данные, с которыми они работают, хранятся в одной и той же памяти. Хотя это может показаться очевидным выбором, это не значит, что компьютеры должны иметь для работы. Это свойство, характерное для модели фон Неймана — в отличие от модели Гарварда, где инструкции и данные хранятся в отдельных запоминающих устройствах.Реальным примером гарвардского компьютера является микроконтроллер AVR на вашем Arduino.

Наша первая программа

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

Вот код нашей первой программы на сборке x86-64:

 
формат PE64 NX GUI 6.0
начало записи

раздел '.text' код читаемый исполняемый файл
Начните:
        int3
        Ret
 
 

Анализируем код

Мы рассмотрим это построчно.

  • формат PE64 NX GUI 6.0 — это директива, сообщающая FASM формат двоичного файла, который мы ожидаем от него создать — в нашем случае Portable Executable Format (который используется в большинстве программ Windows). Позже мы поговорим об этом подробнее.
  • начало записи — определяет точку входа в нашу программу. Директива entry требует метки, которой в данном случае является «start». Метку можно рассматривать как имя адреса в нашей программе, поэтому в данном случае мы говорим, что «точка входа в программу находится на том адресе, который находится на метке« start »». Обратите внимание, что вам разрешено ссылаться на метки, даже если они определены позже в программном коде (как здесь).
  • раздел ».text 'код читаемый исполняемый файл — эта директива указывает начало нового раздела в переносимом исполняемом файле, в данном случае раздела, содержащего исполняемый код. Подробнее об этом позже.
  • start: — это метка, обозначающая точку входа в нашу программу. Мы упоминали об этом ранее в директиве «entry». Обратите внимание, что сами метки не создают исполняемый машинный код: они просто способ для программиста пометить места в адресном пространстве исполняемого файла.
  • int3 — это специальная инструкция, которая заставляет программу вызывать обработчик исключений отладки — при работе под отладчиком это приостанавливает выполнение программы и позволяет нам проверить ее состояние или продолжить выполнение шаг за шагом. Вот как на самом деле реализуются точки останова — отладчик заменяет один байт в исполняемом файле кодом операции, соответствующим int3, и когда программа попадает в него, отладчик берет на себя (очевидно, исходное содержимое памяти по адресу точки останова должно быть запоминаются и восстанавливаются перед выполнением или пошаговым выполнением).В нашем случае для удобства мы жестко кодируем точку останова непосредственно в точке входа, чтобы нам не приходилось каждый раз устанавливать ее вручную через отладчик.
  • ret — эта инструкция извлекает адрес из вершины стека и передает выполнение на этот адрес. В нашем случае мы вернемся к коду ОС, который изначально вызвал нашу точку входа.

Запустите FASMW.EXE, вставьте приведенный выше код в редактор, сохраните файл и нажмите Ctrl + F9 .Ваша первая программа сборки завершена! Давайте теперь загрузим его в отладчик и пройдемся по нему пошагово, чтобы убедиться, что он действительно работает.

Использование отладчика

Откройте WinDbg. Перейдите на вкладку View и убедитесь, что видны следующие окна: Disassembly, Registers, Stack, Memory и Command. Перейдите в File> Launch Executable и выберите исполняемый файл, который вы только что создали с помощью FASM. На этом этапе ваше рабочее пространство должно выглядеть примерно так:

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

В окне регистров вы можете увидеть содержимое регистров x86-64, которые мы обсуждали ранее.

Окно памяти показывает необработанное содержимое памяти программы вокруг данного виртуального адреса.Мы воспользуемся им позже.

Окно стека показывает текущий стек вызовов (как вы можете видеть, сейчас он находится внутри ntdll.dll).

Наконец, окно command позволяет вводить текстовые команды и показывает сообщения журнала.

Если вы в это время нажмете F5, программа продолжит работу, пока не достигнет другой точки останова. Следующая точка останова — это та, которую мы жестко запрограммировали. Попробуйте нажать F5, и вы увидите что-то вроде этого:

Вы должны уметь распознать две написанные нами инструкции — int3 и ret.Чтобы перейти к следующей инструкции, нажмите F8. Когда вы это сделаете, обратите внимание на окно регистров — вы должны увидеть, что регистр rip обновляется по мере продвижения (WinDbg выделяет регистры, которые меняются красным).

Сразу после выполнения инструкции ret вы вернетесь к коду, который вызвал точку входа нашей программы.

Как вы можете видеть на изображении выше, следующее, что произойдет, это вызов RtlExitUserThread (довольно понятное имя).Если вы сейчас нажмете F5, основной поток вашей программы очистится и завершится, и программа тоже. Или будет? …

По правде говоря, используя ret , я немного сократил путь. В Windows процесс завершается, если выполняется одно из следующих условий:

  • Любой поток явно вызывает функцию WinAPI ExitProcess
  • Все потоки вышли из

Но здесь мы выходим из основного потока, так что все должно быть хорошо, верно? Ну вроде как.Нет гарантии, что Windows не запустила других фоновых потоков (например, для загрузки библиотек DLL или чего-то подобного) в нашем процессе. Кажется, что, по крайней мере, в этом примере основной поток является единственным (я проверил, и процесс не задерживается), но это может измениться. Программа Windows с хорошим поведением всегда должна вызывать ExitProcess в соответствующее время.

Чтобы иметь возможность вызывать функции WinAPI, нам нужно узнать несколько вещей о формате файла Portable Executable, о том, как загружаются библиотеки DLL и соглашениях о вызовах.

Формат PE и импорт DLL

Функция ExitProcess находится в KERNEL32.DLL (да, это не опечатка, KERNEL32 — это имя 64-разрядной библиотеки. 32-разрядные версии этих библиотек, предоставленные для обратных совместимости, находятся в именах папок SysWOW64. Я не шучу.). Чтобы вызвать его, нам сначала нужно импортировать его.

Мы не будем здесь полностью рассматривать формат Portable Executable. Он подробно документирован на веб-сайте Microsoft docs.Вот пара основных фактов, которые нам необходимо знать:

  • PE-файлы состоят из разделов . Мы уже видели в нашей программе раздел, содержащий исполняемый код, но разделы могут содержать другие типы данных.
  • Информация о том, какие символы из каких DLL импортируются, хранится в специальном разделе под названием ‘.idata’.

Давайте посмотрим на раздел .idata.

Согласно документам, раздел .idata начинается с таблицы каталогов импорта (IDT).Каждая запись в IDT соответствует одной DLL, имеет длину 20 байт и состоит из следующих полей:

  • 4-байтовый относительный виртуальный адрес (RVA) таблицы поиска импорта (ILT), который содержит имена импортируемых функций. Подробнее об этом позже
  • 4-байтовое поле отметки времени (обычно 0)
  • Индекс цепи экспедитора (обычно 0)
  • 4-байтовый RVA строки с завершающим нулем, содержащей имя DLL
  • 4-байтовый RVA таблицы адресов импорта (IAT).Структура IAT такая же, как у ILT, с той лишь разницей, что содержимое IAT изменяется во время выполнения загрузчиком — он перезаписывает каждую запись адресом соответствующей импортированной функции. Таким образом, теоретически вы можете иметь поля ILT и IAT, указывающие на один и тот же фрагмент памяти. Более того, я обнаружил, что установка указателя ILT на ноль также работает, хотя я не уверен, поддерживается ли это поведение официально.

Таблица каталога импорта завершается записью, в которой все поля равны нулю.

ILT / IAT — это массив 64-битных значений, оканчивающихся нулевым значением. Нижний 31 бит каждой записи содержит RVA записи в таблице подсказок / имен (содержащей имя импортированной функции). Во время выполнения записи IAT заменяются фактическими адресами импортированных функций.

Упомянутая выше таблица подсказок / имен состоит из записей, каждая из которых должна быть выровнена по четной границе. Каждая запись начинается с 2-байтовой подсказки (которую мы пока проигнорируем) и строки с завершающим нулем, содержащей имя импортированной функции и нулевой байт (при необходимости), чтобы выровнять следующую запись по четной границе.

Разобравшись с этим, давайте посмотрим, как мы определим раздел .idata нашего исполняемого файла в FASM.

 
раздел '.idata' импортировать доступный для чтения, доступный для записи
idt:; таблица каталога импорта начинается здесь
     ; запись для KERNEL32.DLL
     dd rva kernel32_iat
     дд 0
     дд 0
     dd rva имя_ядра
     dd rva kernel32_iat
     ; NULL запись - конец IDT
     dd 5 dup (0)
name_table:; подсказка / таблица названий
        _ExitProcess_Name dw 0
                          db "ExitProcess", 0, 0

kernel32_name: db "KERNEL32.DLL ", 0
kernel32_iat:; импортировать адресную таблицу для KERNEL32.DLL
        ExitProcess dq rva _ExitProcess_Name
        dq 0; конец IAT KERNEL32
 
 

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

Директивы db, dw, dd и dq заставляют FASM выдавать необработанное значение байта / слова / двойного слова / четверного слова соответственно.Неудивительно, что оператор rva возвращает относительный виртуальный адрес своего аргумента. Итак, dd rva kernel32_iat заставит FASM выдать 4-байтовое двоичное значение, равное RVA метки kernel32_iat .

Здесь мы только что использовали файл fasm db / dw / etc. директивы для точного описания содержимого нашего раздела .idata.

Соглашение о вызовах 64-битной Windows

Теперь мы почти готовы, наконец, вызвать ExitProcess.Однако мы должны ответить на один вопрос: как работает вызов функции? Думаю об этом. Существует инструкция call , которая помещает текущее значение rip в стек и передает выполнение на адрес, указанный в его параметре. Также существует инструкция ret , которая извлекает адрес из стека и передает туда выполнение. Нигде не указано, как аргументы должны передаваться функции или как обрабатывать возвращаемые значения. Аппаратному обеспечению на это просто наплевать.Задача вызывающего и вызываемого — заключить договор между собой. Эти правила могут выглядеть примерно так:

  • Вызывающий должен поместить аргументы в стек (начиная с последнего)
  • Вызываемый объект должен удалить параметры из стека перед возвратом.
  • Вызываемый должен поместить возвращаемые значения в регистр eax

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

Хорошая новость заключается в том, что в 64-битной Windows существует только одно соглашение о вызовах, о котором вам нужно знать, — соглашение о вызовах Microsoft x64. Плохая новость заключается в том, что это сложно — в отличие от многих старых соглашений, он требует, чтобы первые несколько параметров передавались через регистры (в отличие от передачи в стек), что может быть полезно для производительности.

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

  • Указатель стека должен быть выровнен по 16-байтовой границе
  • Первые четыре целых числа или аргумента-указателя передаются в регистры rcx, rdx, r8 и r9; первые четыре аргумента с плавающей запятой передаются в регистрах от xmm0 до xmm3. Любые дополнительные аргументы передаются в стек.
  • Несмотря на то, что первые 4 аргумента не передаются в стек, вызывающей стороне по-прежнему требуется выделить для них 32 байта пространства в стеке.Это необходимо сделать, даже если функция имеет менее 4 аргументов.
  • Вызывающий отвечает за очистку стека.

Вооружившись этим знанием, мы, наконец, можем вызвать ExitProcess:

 
формат PE64 NX GUI 6.0
начало записи

раздел '.text' код читаемый исполняемый файл
Начните:
        int3
        sub rsp, 8 * 5; настроить стек ptr и выделить теневое пространство.
        xor rcx, rcx; Первый и единственный аргумент - это код возврата, переданный в rcx.позвоните [ExitProcess]


раздел '.idata' импортировать доступный для чтения, доступный для записи
idt:; таблица каталога импорта начинается здесь
     ; запись для KERNEL32.DLL
     dd rva kernel32_iat
     дд 0
     дд 0
     dd rva имя_ядра
     dd rva kernel32_iat
     ; NULL запись - конец IDT
     dd 5 dup (0)
name_table:; подсказка / таблица названий
        _ExitProcess_Name dw 0
                          db "ExitProcess", 0, 0

kernel32_name db "KERNEL32.DLL ", 0
kernel32_iat:; импортировать адресную таблицу для KERNEL32.DLL
        ExitProcess dq rva _ExitProcess_Name
        dq 0; конец IAT KERNEL32
 
 

Давайте пройдемся по новым строкам одну за другой.

  • sub rsp, 8 * 5 — команда sub вычитает свой второй операнд из своего первого операнда и сохраняет результат в первом операнде. В этом случае мы вычитаем 40 из текущего значения указателя стека (обратите внимание, что несколько парадоксально, стек «растет» вниз, т.е.е. вставка в стек или выделение в нем места уменьшает значение указателя стека). Таким образом, мы выравниваем стек по 16-байтовой границе и одним махом выделяем «теневое пространство» для первых 4 аргументов. Как это работает? Ну, до того, как наша точка входа была вызвана, указатель стека был выровнен по 16-байтовой границе. В результате вызова адрес возврата был помещен в стек, уменьшив значение указателя стека на 8 и выбив его из выравнивания. Нам нужно вычесть еще 8 байтов, чтобы снова привести его в соответствие, и еще 32 байта для учета теневого пространства, отсюда и значение 40.
  • xor rcx, rcx — напомним, что первый целочисленный аргумент должен быть передан в регистре rcx. Здесь мы устанавливаем значение этого регистра равным нулю, выполняя побитовую операцию исключающего ИЛИ с самим собой.
  • call [ExitProcess] — это то, что, наконец, вызывает ExitProcess. Квадратные скобки вокруг имени метки обозначают косвенное обращение — вместо вызова адреса, на который указывает метка, значение, записанное в памяти по этому адресу, используется в качестве целевого адреса для вызова.Конечно, метка, которую мы используем, указывает на то место в таблице импорта, куда загрузчик записал адрес требуемой функции!

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

Это первая часть. В следующий раз мы попробуем сделать что-нибудь поинтереснее, чем просто выйти из процесса 🙂


Понравился пост? Следите за этим блогом в Twitter, чтобы узнать больше!

Программирование на ассемблере мэйнфрейма Билл Куоллс

Введение

Я являюсь автором текста «Программирование на ассемблере мэйнфреймов» , ISBN 0-471-24993-9, опубликованного в 1998 году издательством Wiley Computer Publishing.Эта книга больше не издается. Я решил, что вместо того, чтобы публиковать самостоятельно, я сделаю его доступным для всех для некоммерческого использования через Интернет. Это мой небольшой способ сказать «спасибо» многим неизвестным, чей вклад в Интернет обогатил мою личную и профессиональную жизнь. Я надеюсь, что кто-то сочтет это полезным. Если так, я хотел бы получить известие от вас. Пожалуйста, напишите мне на [email protected]. Спасибо.
p.s. Я могу пройти корпоративное обучение по этой и многим другим темам в области информационных технологий.Пожалуйста, свяжитесь со мной, если я могу быть полезен вашей компании.

Содержание

Программное обеспечение

  • Сохраните этот файл на свой компьютер и распакуйте его.
  • Прочтите этот файл для объяснения.

Отзывы

  • «ВАУ !!!!! Это ФАНТАСТИЧНО! Большое вам спасибо! У меня уже есть ваша замечательная книга, и я часто рекомендовал ее другим студентам, но они не могли позволить себе цену в 99 долларов, за которую она иногда идет. подержанный рынок.»- Лиделл Андерсон
  • «Я очень рад, что моя копия вашей книги благополучно хранится в моей библиотеке и,
    даже если бы я мог получить за это 194 доллара — максимальная цена, которая сейчас указана на
    abebooks.com — мне бы это не было интересно «, — Джей Мозли.
  • «Сэр: Просто хотел сказать вам, что ваша книга, вероятно, моя самая любимая книга по программированию. Я не смог бы сдать язык ассемблера без него. У меня все еще есть бумажная копия, так как я не мог заставить себя перепродать ее после окончания урока. Самое странное, что я даже не люблю программировать, однако я был слишком близок к тому, чтобы получить диплом по программированию, чтобы пойти другим путем.В любом случае, я рад, что вы выложили его для всех студентов. Если требуется язык ассемблера, это самый простой способ его изучить », — Уэйн Мерфи, системный инженер, USAF.
  • «Большое спасибо за эту книгу. Я купил эту книгу несколько лет назад, чтобы использовать ее в качестве учебника для обучения ассемблерам некоторых моих коллег, которые программировали только на COBOL. Каким-то образом я потерял свою копию книги, и я очень рад найти эту онлайн-копию, чтобы освежить свои навыки программирования после 8 лет работы на пенсии.Я установил Hercules и MVS370, и эта книга должна создать среду для изучения ассемблера. Еще раз спасибо. «- Дейл Эрвин, Лима, Перу.
  • «Поискав в Интернете информацию, я нашел вашу книгу. Это отличная книга для начинающих и не очень новичков. Большое спасибо за то, что поделились этой чудесной информацией об ассемблере». — Анхель Луис Домнгес, системный программист z / OS, Мадрид, Испания

Моя домашняя страница

Уроки программирования на сборке TIGCC

Уроки сборки TIGCC

Программирование сборки для TI-68k

Эта часть Техно-Плазы предназначена для обучения людей программированию в
сборка для вычислителей ТИ-89 и ТИ-92 + / V200.Большая часть информации
содержащиеся в этих уроках, могут быть применены к другим платформам Motorola 68000
тоже, хотя их становится очень мало.

Если вы начинающий программист, советуем взглянуть на
Вместо этого уроки программирования TIGCC C, поскольку C — это
гораздо более простой язык для изучения и использования, и есть больше ресурсов
доступны вам. Однако я осознал, что многие люди по-прежнему хотят
чтобы изучить сборку, а ресурсов, посвященных этому, по-прежнему очень мало
задача.

Единственный другой важный источник документации по программному обеспечению TI-68k
программирование — это проект TIGCC. В рамках проекта TIGCC продолжаются работы по
разработать интегрированную среду разработки (IDE) для использования при создании TI-68k
программное обеспечение. Он включает компилятор C, ассемблер для кода 68000 и
многообъектный компоновщик для создания программных файлов TI-68k. В своей работе они
собрали библиотеку информации по программированию, полезной для всех, кто
стремится разработать программное обеспечение TI-68k.Вы можете просмотреть их документацию
онлайн,
или из TIGCC IDE.

Если вам нужна информация о конкретных кодах операций Motorola 68000 и их
использования, вы можете проконсультироваться
Справочное руководство для программиста Motorola 68000. Вы должны иметь
Adobe Reader, чтобы просмотреть это.


Уроки программирования Techno-Plaza Assembly


Вопросы или комментарии?
Свяжитесь с Техно-Плаза, используя нашу форму обратной связи.


AVR-Tutorial


© 2002-2020, http://www.avr-asm-tutorial.net
Вы можете использовать, копировать и распространять эти страницы, если вы сохраняете вместе с ними информацию об авторских правах.

Обзор AVR и их
оборудование
Симулятор для AVR
код ассемблера
Курсы ассемблера для начинающих
в разных вариантах
по видам инструкций
аппаратное и программное обеспечение
для высокоуровневых уродов
Что такое AVR? Для абсолютных новичков
Изучение приложений
71456 загрузок файлов исходного кода ассемблера в 2020 г. (195 в день)
Четыре самых популярных приложения:
Чтение клавиш клавиатуры
Счетчик частоты
Генератор сигналов
Драйвер шагового двигателя ATtiny13
Четыре последних добавленных приложения:
Таймер для яиц с ATmega25
Длинный таймер с Duo-LED + динамиком и ATtiny25
Длинный таймер с Duo-LED и ATtiny13
Генераторы синусоидальных сигналов с сетями R / 2R
Использование внутреннего оборудования контроллера
компоненты в исходных кодах ассемблера на этом веб-сайте
Программные алгоритмы на ассемблере
исходники на сайте
Обучающий ассемблер
Введение для начинающих
на язык ассемблера AVR.Также доступен в виде полного PDF-документа
для печати всего курса
(Скачать, 1,1 МБ)
Микро-курс для начинающих из 14 лекций
от простого к сложному с ATtiny13 и ATtiny24, аппаратным и программным обеспечением,
все на макете, также доступно в
один полный документ PDF
Начальный курс для начинающих, знакомящий с
аппаратное обеспечение AVR (в основном ATtiny24) и их программное программирование на ассемблере
в 11 лекциях использует тренажер для визуализации эффектов
Начальный курс для начинающих
использование симулятора для изучения эффекта инструкций ассемблера,
также доступен в формате PDF
Введение в двоичные числа и двоичную математику
с практическими примерами кода для сложения, вычитания, умножения и деления двоичных
номера любого размера
Четыре простых примера программирования с расширенными комментариями в качестве первых
шаги практического введения в программирование на ассемблере:
Смысл и требования,
Примеры простого программирования
Software-Know-How, специальный ассемблер
команды: LPM, скачки стека, макросы
Все о временных петлях от микросекунд
от миллисекунд и секунд до часов, дней и месяцев: все, что вам нужно, это
петлю, или две, или три…
Все, что вам нужно знать о прерываниях для
новички, в векторах int, источниках int, выполнении int и том, как управляют int
весь дизайн программы, целый новый мир, чтобы узнать о
У вас есть один, вы его используете, чувствуете зависимость? если ты
хотите избавиться от препарата Arduino, вы получите помощь, чтобы избавиться от этого
здесь.
Инструменты для программирования на ассемблере
Ассемблер командной строки с расширенной проверкой ошибок и комментированием,
бесплатно для скачивания
Для удобной работы ассемблера командной строки: оконный вызывающий
включая редактирование исходных и включаемых файлов, просмотр файла списка,
поиск ошибок и редактирование ошибочных строк и т. д., бесплатно
скачать здесь
Как собрать файлы внешнего исходного кода
Как
преобразовать исходный код из одного в другой типа
Программное обеспечение Windows для генерации ассемблера
файлы исходного кода со стандартной структурой
Расширенное программирование на ассемблере
Двоичное умножение, деление, преобразование
подробно о числовых форматах и ​​фиксированных десятичных дробях, аппаратное умножение
Программирование и тестирование аппаратной части
Плата STK200: EEPROM, внешняя RAM, LCD-дисплей, SIO-интерфейс
Приложения в ассемблере
Малые приложения: ИК-устройства дистанционного управления,
матрица 8 на 8 светодиодов, синхронизированные часы DCF77, декодер PCM-to-PWG,
генератор частоты с терминальным управлением, цифровой генератор сигналов
с регулировкой частоты / ширины импульса и ЖК-дисплеем, таймер в подарок,
контроллер / драйвер шагового двигателя, акробатическая игральная кость, светодиодный секвенсор и
регулятор интенсивности
Подключение двухстрочного ЖК-дисплея к четырехстрочному
подключение к плате программирования STK500 с базовыми подпрограммами для
управление ЖК-дисплеем и приложение для небольших часов
Преобразование аналогового напряжения в цифровое
с помощью платы STK500, встроенного аналогового компаратора и таймера / счетчика 1
как генератор ширины импульса
Подключение клавиатуры к AVR
и зондирование с использованием соединений портов или с помощью резисторной матрицы и AD
конвертер.Доработаны версии резисторного матричного энкодера и графического
там же предоставляются программные инструменты.
Преобразование цифрового значения в аналоговое напряжение с помощью
сеть с буферизацией R / 2R, включая
генерация волн, таких как пилообразные, треугольные, синусоидальные формы и небольшой
приложение для тонального плеера. Со страницами расчета фильтров R / 2R и RC
и программные инструменты.
Погрузчик Accu с ATmega16
Архивированная веб-страница для чтения в автономном режиме
Вся веб-страница для скачивания,
упаковано примерно 25 МБ.После загрузки распакуйте этот файл в
отдельный каталог, в котором хранятся пути.
Статистика на этой странице
Статистика посещений веб-сайтов,
исходный код на этих страницах и частоты использования для инструкций и директив
в исходном коде ассемблера

Знакомство с 6502 Assembly!

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

Двоичный

Процессор, в зависимости от его аппаратного обеспечения, будет иметь несколько инструкций, которые он будет принимать. Инструкция — это действие, которое процессор может выполнить, например «сложить» или «вычесть». Полный набор инструкций, которые может выполнять процессор, известен как его набор инструкций. Вы можете попросить процессор выполнить инструкцию, передав ему соответствующий двоичный шаблон.Это максимально низкий уровень, на который мы можем пойти. Например, если 6502 задан 8-битный шаблон , 10000101, , он интерпретирует это как . Сохранение содержимого накопителя в определенной ячейке памяти . На данный момент не беспокойтесь о том, что именно это означает, цель — показать, что, передавая двоичные шаблоны процессору, он выполняет инструкцию.

Мы можем использовать эти инструкции для построения более сложных процессов, чем те, которые доступны нам в виде отдельных инструкций.Это программа. Программа — это больше, чем просто список инструкций. Он должен будет включать данные, с которыми нужно работать, и места в памяти, где хранить вещи и получать к ним доступ. Сказать процессору добавить что-то бессмысленно, если вы не можете сказать ему, какие числа суммировать и где сохранить результат! Вот пример программы:

 10100101 
01100000
01100101
01100001
10000101
01100010

Это очевидно, правда? (Это не так.) Это добавит 2 числа и где-нибудь сохранит результат.Чтобы было понятно, это не написано на ассемблере, это машинный язык. В конце концов, весь код, который вы пишете, независимо от языка, будет преобразован, чтобы выглядеть примерно так. Как человек, который хочет писать код, хотя у машинного языка есть много очевидных недостатков:

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

Шестнадцатеричный

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

 A5 
60
65
61
85
62

Это немного лучше, чем двоичный эквивалент. Его легче читать, писать и отлаживать. Ранее я утверждал, что процессор будет принимать вещи только в форме 8-битного шаблона — это все еще верно. Перед тем как эту программу можно будет запустить на 6502, ее необходимо перевести в двоичный код — это делается с помощью шестнадцатеричного загрузчика.Когда мы начинаем конструировать язык более высокого уровня, чем машинный код, мы начинаем идти на компромиссы. Для нашего здравого смысла нам нужен более понятный для человека язык программирования, но стоимость использования загрузчика заключается в том, что он также должен занимать место в памяти, память, которую вы больше не можете использовать для запуска своей программы. Написание программы в двоичном (или, возможно, даже шестнадцатеричном) формате было бы неразумным делом, поэтому использование загрузчика стоит затрат.

Запись в шестнадцатеричном формате по-прежнему имеет множество явных проблем. В основном, как бы вы узнали, что делает этот код, если бы я не сказал вам заранее? Как кто-то должен помнить, что представляют все эти шестнадцатеричные значения? Чтобы уменьшить эту проблему, мы добавим в наш код еще один уровень абстракции: мнемонику.

Мнемоника инструкций

Здесь мы дадим нашему набору инструкций (надеюсь) запоминающиеся имена. Затем, вместо того, чтобы ссылаться на инструкции в шестнадцатеричном формате, мы будем использовать их заданные имена. Программа, использующая эту мнемонику, написана на языке Ассемблер. Если я напишу тот же пример сложения, что и раньше в сборке 6502, он будет выглядеть так:

 LDA 60 долларов 
ADC 61
STA 62 долларов

Программа начинает выглядеть намного чище и удобочитаемой.Хотя на данный момент все это может не иметь большого значения для вас, я обещаю, что легче запомнить мнемонику, чем шестнадцатеричные символы. Зная, что LDA в общих чертах означает загрузку, ADC означает добавление, а STA как бы означает сохранение, позволяет нам получить представление о том, что делает программа. Даже если бы нам не сказали цель кода, но мы знали, что означают инструкции, у нас было бы хорошее представление о том, что он будет делать.

Здесь я был осторожен, говоря, что приведенный выше код написан на «языке ассемблера 6502», а не просто на «языке ассемблера».Это связано с тем, что названия команд определяются производителем процессора и поэтому могут сильно отличаться от процессора к процессору. Комбинация этого и другого аппаратного обеспечения процессора, позволяющая использовать разные инструкции, означает, что код сборки, написанный для одного типа процессора, не будет работать на другом. Это огромная разница по сравнению с современным языком высокого уровня. Я могу написать сценарий Python на MacBook с процессором Intel i7, а затем легко (теоретически) запустить его на машине Windows 10 с процессором i3.В случае сборки дело обстоит иначе. Ассемблерный код 6502 не будет работать на современной машине x86.

Чтобы преобразовать наш ассемблерный код во что-то, что машина может понять и запустить, мы используем ассемблер. Это возьмет нашу программу, написанную на ассемблере (наша исходная программа), и вернет то, что 6502 может запустить (объектную программу). Как и в случае использования шестнадцатеричного загрузчика, это связано с расходами, но оно того стоит, поэтому мы можем избежать использования машинного языка.

Небольшое отступление — кто-то сказал мне, что я играл немного быстро и небрежно с некоторой терминологией.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *