Так сложилось, что я совсем не знаю ассемблера. Даже несмотря на то, что я разрабатываю компиляторы, на уровень близкий к аппаратуре я почти не спускаюсь. Была пара попыток его выучить, но я просто не находил подходящего материала. В итоге решил что если его нет, то нужно написать самому. В этой заметке я планирую показать как написать простой Hello world на ассемблере.
- Изучить основы работы с ассемблером
- Сравнить ассемблеры процессоров различных архитектур и, как следствие, показать разные аппаратные особенности
- Написать материал по которому новички далее смогут самостоятельно продолжить изучение ассемблера
- Введение
- amd64
- sparc v9
- Эльбрус
- Послесловие
- Источники
1. Введение
Я буду стараться давать минимум теории, т.к. её рассказывают много где, гораздо более подробно и понятно. Поэтому буду описывать только то, что касается данного примера.
Итак, задача: написать программу, выводящую на экран сообщение «Hello, world». В качестве эталона возьмём программу на C:
#1 Первая программа на ассемблере MASM
int main()
const char * msg = «Hello, worldn»;
write(0, msg, 13);
return 0;
>
Сборка и запуск:
$ gcc t.c ./a.out
Hello, world
Здесь специально не использована стандартная библиотека, а применён системный вызов write. Подробнее про него можно прочесть по команде man 2 write.
2. amd64
В качестве процессора на данной архитектуре применяется Intel Core i5, операционная система — Gentoo GNU/Linux, синтаксис ATHello, worldn»
.set hello_str_len, . — hello_str — 1
.section .text
.globl _start
_start:
# Здесь подготавливаем и вызываем write
mov $1, %rax
mov $1, %rdi
mov $hello_str, %rsi
mov $hello_str_len, %rdx
syscall
# Здесь подготавливаем и вызываем exit
mov $60, %rax
mov $0, %rdi
syscall
Сборка и запуск:
$ as tt.s -o tt.o ld tt.o ./a.out
Hello, world
Теперь попытаемся понять что произошло.
Краткое описание синтаксиса:
На каждой строчке находятся команды (statement). Команда начинается с нуля и более меток, после которых находится ключевой символ, обозначающий тип команды. Всё что начинается с точки `.’ является директивой ассемблера. Всё что начинается с буквы является инструкцией ассемблера и транслируется в машинный код. Комментарии бывают многострочными `/**/’ и однострочными `#’.
- .data — в этой секции обычно хранятся константы
- .text — в этой секции обычно хранятся инструкции программы
- .bss — содержит обнулённые байты и применяется для хранения неинициализированной информации
Далее идёт директива .string . Это псевдо операция, копирующая байты в объектник.
Директива .set присваивает символу значение выражения. Т.о. мы говорим что символ hello_str_len равен выражению . — hello_str — 1 . Символ ` . ‘ означает текущий адрес. Вычитая из него адрес метки hello_str получаем длину строки с завершающим нулём. Чтобы он не попал на печать вычитаем 1.
06. Низкоуровневое программирование. Ассемблер. Пример программы. [Универсальный программист]
Директива .globl говорит что данный символ должен быть виден ld. Т.е. теперь символ _start сможет быть слинкован. Это нужно, т.к. вход в программу осуществляется именно через этот символ.
После метки _start начинаются непосредственно ассемблерные инструкции. И теперь опять вернёмся к теории.
Данная программа написана под процессор Intel архитектуры amd64 (она же x86_64). Это 64-х битное расширение архитектуры IA-32. Описание самой архитектуры процессора находится в [intel1]. Подробное описание команд процессора находится в [intel2].
Итак, в данной программе мы оперируем регистрами — внутренней памятью процессора. Архитектура amd64 содержит очень мало регистров — всего 16 64-х разрядных регистров общего назначения: RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, R8D-R15D.
Операция mov предназначена для копирования первого операнда во второй (заметьте, что это особенность синтаксиса AT
Но в целом таблицами, подготовленными хорошими людьми пользоваться удобнее.
Итак, видно, что вызов write требует 3 аргумента. Первый — это дескриптор файла вывода. Он кладётся на регистр rdi. Мы на rdi кладём 1, что является дескриптором stdout. На регистр rsi кладётся указатель на адрес строки.
И на регистр rdx кладётся длина строки. Всё, теперь, когда все регистры подготовлены, можно делать syscall и нам будет выведено сообщение.
Далее нужно выйти из программы. Для этого используется системный вызов exit. Он имеет номер 60 и требует код возврата в качестве первого аргумента. Мы завершаемся с кодом 0, как и положено успешно выполненной программе.
3. Sparc v9
Не устали? Теперь внезапно рассмотрим sparc. Меня эта платформа интересует, т.к. одна из линеек процессоров Эльбрус основана на этой архитектуре. Я тестировался на процессорах TI UltraSparc III+ (Cheetah+) с ОС Gentoo и процессорах Эльбрус R1000 c ОС Эльбрус. Итак, смотрим:
.section .data
hello_str:
.ascii «Hello, worldn»
.set hello_str_len, . — hello_str
_start:
! Подготавливаем и вызываем write
mov 1, %o0
set hello_str, %o1
mov hello_str_len, %o2
mov 4, %g1
ta 0x10
! Подготавливаем и вызываем exit
mov 0, %o0
mov 1, %g1
ta 0x10
Сборка и запуск:
$ as -Av9 -64 t1.s -o t1.o ld -Av9 -m elf64_sparc t1.o ./a.out
Hello, world
Вроде как отличий немного. Синтаксис as был описан в блоке amd64, разве что здесь однострочные комментарии задаются символом ! , поэтому его опускаем и переходим сразу к отличиям. Сразу скажу, что речь идёт о Sparc v9 если не оговорено другое. v9 является 64-х битным расширением архитектуры sparc v8. Начнём с регистров. Их здесь больше чем в amd64 — целых 32 общего назначения, доступных пользователю. Сами регистры называются %r0 — %r31, но у них есть логическое разделение:
Глобальные (global) | %g0 — %g7 | %r0 — %r7 |
Выходные (out) | %o0 — %o7 | %r8 — %r15 |
Локальные (local) | %l0 — %l7 | %r16 — %r23 |
Входные (in) | %i0 — %i7 | %r24 — %r31 |
Данные регистры называются r регистрами и используются для целочисленных вычислений. Плавающие регистры называются f регистрами, они расположены отдельно, и о них мы сегодня говорить не будем. Интересно отметить, что сама архитектура предполагает от 64 до 528 r регистров, но регистровое окно содержит только 24. Чтение %g0 всегда возвращает 0, а запись в него не даёт эффекта. Вообще на спарке регистры сделаны очень круто, но их очень долго описывать, советую прочитать документацию [sparcv9].
Инструкция sethi поместит старшие 22 бита hello_str (т.е. её адрес) на регистр %o2. Инструкция or поместит туда младший остаток. Обозначения %hi и %lo нужны для взятия старших и младших битов соответственно. Такие сложности возникают из-за того что инструкция кодируется 32 битами, и просто не может включать в себя 32-х битную константу.
Далее мы кладём значение 4 на глобальный регистр %g1. Можно догадаться что это номер вызова write. Системный возов будет искать номер вызова именно там.
Операция ta инициирует системное прерывание. Её аргументом является тип системного прерывания. Скажу честно — я не нашёл нормального описания системных вызовов для v9, а то что туда надо подавать 0x10 выяснил случайно из архивов какой-то переписки. Поэтому придётся просто это запомнить 🙂
Далее производятся аналогичные действия для вызова exit, думаю их пояснять не нужно.
Спасибо уважаемому Анониму за версию данной программы для SunOS 5.10:
.section «.text»
.global _start
_start:
mov 4,%g1 ! 4 is SYS_write
mov 1,%o0 ! 1 is stdout
set .msg,%o1 ! pointer to buffer
mov (.msgend-.msg),%o2 ! length
ta 8
mov 1,%g1 ! 1 is SYS_exit
clr %o0 ! return status is 0
ta 8
.msg:
.ascii «Hello world!n»
.msgend:
Запуск:
$ as t1.s -o t1.o ld t1.o ./a.out
Hello world!
4. Эльбрус
Ну и, собственно, жемчужина коллекции — процессор Эльбрус. Работа проводилась на процессоре Эльбрус-4С, который имеет архитектуру команд v3 (наше внутреннее обозначение). Управляется машина ОС Эльбрус. Про сам Эльбрус можно почитать в [elbrus], про какую-либо документацию, находящуюся в открытом доступе мне неизвестно.
Как и Sparc, архитектура Эльбруса рассчитана в первую очередь на то что оптимальный код выдаст компилятор. Но в отличает от Sparc, ассемблер Эльбруса вообще не предназначен для людей. Итак, вот наш пример:
$hello_msg:
.ascii «Hello, worldn 00»
.section «.text»
.global _start
_start:
! Подготавливаем вызов write
sdisp %ctpr1, 0x3
addd, 0 0x0, 13, %b[3]
addd, 2 0x0, [ _f64, _lts1 $hello_msg ], %b[2]
addd, 1 0x0, 0x1, %b[1]
addd, 3 0x0, 0x4, %b[0]
>
! Вызываем write
call %ctpr1, wbs = 0x4
>
! Подготавливаем вызов exit
sdisp %ctpr2, 0x1
addd, 0 0x0, 0x0, %b[1]
addd, 1 0x0, 0x1, %b[0]
>
! Вызываем exit
call %ctpr2, wbs = 0x4
>
Сборка и запуск:
$ las t.s -o t.o ld t.o ./a.out
Hello, world
Начнём с изменения синтаксиса.
Простые программы на ассемблере примеры
Данная глава посвящена простым примерам, демонстрирующим технику создания окон и других оконных элементов, которая была изложена в предыдущей главе.
I
- Свойства конкретного окна задаются при вызове функции CreateWindow определением параметра Style. Константы, определяющие свойства окна, содержатся в специальных файлах, которые подключаются при компиляции. Поскольку свойства фактически определяются наличием или отсутствием того или иного бита в константе, комбинация свойств — это просто сумма констант. В отличие от многих рекомендаций для разработчиков все константы здесь определяются непосредственно в программах.
- Окно создается на основе зарегистрированного класса. Окно может содержать элементы управления — кнопки, окна редактирования, списки, полосы прокрутки и т.д. Все эти элементы могут создаваться как окна с предопределенными классами (для кнопок «BUTTON», для окна редактирования «EDIT», для списка «LISTBOX» и т.д.).
- Система общается с окном, а следовательно, и с самим приложением посредством посылки сообщений. Эти сообщения должны обрабатываться процедурой окна. Программирование под Windows в значительной степени является программированием обработки таких сообщений. Сообщения генерируются системой также в случаях каких-либо визуальных событий, происходящих с окном или управляющими элементами 22 ; на нем. К таким событиям относятся передвижение окна или изменение его размеров, нажатие кнопки, выбор элемента в списке, передвижение курсора мыши и т.д. и т.п. Это и понятно, программа должна как-то реагировать на подобные события.
- Сообщение имеет код (будем обозначать его в программе MES) и два параметра (WPARAM и LPARAM). Для каждого кода сообщения придумано свое макроимя, хотя это всего лишь целое число. Например, сообщение WM_CREATE приходит один раз, когда создается окно, WM_PAINT посылается окну», если оно перерисовывается, сообщение WM_RBUTTONDOWN генерируется, если щелкнуть правой кнопкой мыши при расположении курсора мыши в области окна и т.д. Параметры сообщения могут не иметь никакого смысла либо играть уточняющую роль. Например, сообщение WM_COMMAND генерируется системой, когда что-то происходит с управляющими элементами окна. В этом случае по значению параметров можно определить, какой это элемент и что с ним произошло (LPARAM — дескриптор элемента, старшее слово WPARAM — событие, младшее слово WPARAM — обычно идентификатор ресурса см. Часть II). Можно сказать, что сообщение WM_COMMAND несет сообщение от элемента на окне.
- Сообщение может генерироваться не только системой, но и самой программой. Например, можно послать сообщение-команду какому-либо элементу управления (добавить элемент в список, послать строку в окно редактирования и т.п.). Иногда посылка сообщений используется как прием программирования. Например, можно придумать свои сообщения так, чтобы при их посылке программа выполнила те или иные действия. Естественно, это сообщение должно «отлавливаться» либо в процедуре какого-либо окна, либо в цикле обработки сообщений. Такой подход очень удобен, поскольку позволяет фактически осуществлять циклические алгоритмы так, чтобы возможные изменения с окном во время исполнения такого цикла сразу проявлялись на экране.
22 Вообще, можно выделить как управляющие элементы (кнопки, переключатели), так и управляемые элементы (окна редактирования), но мы все их будем называть управляющими.
23 Хотя на самом деле вызывается процедура окна с соответствующими значениями параметров, мы и в дальнейшем будем говорить о посылке окну сообщения.
II
Приступаем к разбору следующего примера. При запуске этой программы на экране появляется окно с кнопкой «Выход». Нажатие этой кнопки, как Вы понимаете, должно привести к выходу из программы.
Самое важное здесь, как мы узнаем, что кнопка нажата. Все просто: в начале проверка сообщения WM_COMMAND, а затем проверяем LPARAM — здесь хранится дескриптор (уникальный номер) окна (кнопка создается как окно). В данном случае для кнопки этого уже достаточно, чтобы определить событие 24 .
Да, и обратите внимание на свойства кнопки, которую мы создаем как окно, — это наиболее типичное сочетание свойств, но не единственное. Например, если Вы хотите, чтобы кнопка содержала иконку, то необходимым условием для этого будет свойство BS_ICON (или BS_BITMAP).
24 Честно говоря, здесь я, читатель, немного грешен. Убедившись, что событие произошло именно с кнопкой, нам бы следовало определить, какое событие произошло, проверив старшее слово параметра WPARAM (событие BN_CLICKED=0). Не вдаваясь в подробности, замечу, что в большинстве примеров, которые мы разбираем, для кнопки этого делать не обязательно.
III
Второй пример касается использования окна редактирования. Результат работы программы показан на Рис. 1.3.3, а сама программа — на Рис. 1.3.2. При нажатии кнопки «Выход» в начале появляется окно-сообщение с отредактированной строкой.
Обратите внимание на то, как осуществляется посылка сообщения окну (управляющему элементу). Для этого используют в основном две функции: SendMessage и PostMessage. Отличие их друг от друга заключается в том, что первая вызывает процедуру окна с соответствующими параметрами и ждет, когда та возвратит управление; вторая функция ставит сообщение в очередь и сразу возвращает управление.
Рис. 1.3.3. Работа программы со строкой редактирования (Программа на Рис. 1.3.2).
IV
Наконец последний в этой главе пример — окно-список. При создании окна в список помещаются названия цветов. Если произвести двойной щелчок по цвету, то появится окно-сообщение с названием этого цвета.
Двойной щелчок по элементу списка определяется по следующей схеме: отслеживается событие, происходящее со списком, а далее по старшему слову параметра WPARAM определяется, какое событие имело место (параметр [EBP+10Н], а его старшая часть [EBP+12Н]).
Не имея возможности подробно останавливаться на различных свойствах управляющих элементов, укажу основополагающую идею такого элемента, как список. Каждый элемент списка имеет ряд атрибутов, по которым он может быть найден в списке: порядковый номер в списке, название элемента, уникальный номер. Последняя характеристика наиболее важна, так как позволяет однозначно идентифицировать элемент.
В конце главы я бы хотел поговорить вот на какую тему. С окном на экране могут происходить самые интересные и необычные вещи.
Например, оно по желанию пользователя может перемещаться, менять свой размер, сворачиваться и разворачиваться, наконец, другие окна могут заслонять данное окно.
В таких ситуациях система посылает окну сообщение WM_PAINT. Такое же сообщение посылается окну при выполнении некоторых функций, связанных с перерисовкой окна, таких, например, как UpdateWindow.
Если сообщение WM_PAINT не обрабатывается процедурой окна, а возвращается системе посредством функции DefWindowProc, то система берет на себя не только перерисовку окна (что она и так делает), но и перерисовку содержимого окна. К содержимому окна, однако, относятся только дочерние окна, которыми являются кнопки, списки, окна редактирования и другие элементы управления. В следующих главах мы, в частности, будем говорить о том, как выводить в окно текстовую и графическую информацию.
Здесь, чтобы информация сохранялась в окне, нам не обойтись без обработки сообщения WM_PAINT.
В заключение заметим, что представленные в данной главе программы записаны для трансляции в MASM32.
Fore kc .ru
Рефераты, дипломы, курсовые, выпускные и квалификационные работы, диссертации, учебники, учебные пособия, лекции, методические пособия и рекомендации, программы и курсы обучения, публикации из профильных изданий
Источник: www.i-assembler.ru
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Cancel Create
hse-caos-2020 / 07-asm1 / README.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cannot retrieve contributors at this time
284 lines (226 sloc) 15.2 KB
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents Copy raw contents
Copy raw contents
Ассемблер x86, часть 1
Основное чтение: Р. Э. Брайант, Д. Р. О’Халларон. Компьютерные системы: архитектура и программирование. Глава 3.