Ну вот теперь мы вплотную приблизились к эмулятору Raspberry Pi. На самом деле я мог бы сейчас одной фразой сказать, как скомпилировать программу, чтобы она запустилась на эмуляторе. Но, думаю, что так будет менее интересно. Судя по активности, данная тема заинтересовала лишь 2-3 человека, да и для них, скорее всего интересен не конечный результат, а повествование. Поэтому я расскажу, как я пришёл к конечному результату, шаг за шагом.
Для того, чтобы писать приложения под операционную систему особенно не требуется каких-то изощрений. Писать и отлаживать можно прямо на Raspberry Pi. Другое дело низкоуровневое программирование - bare metal. Здесь нужно либо поверять написанную программу на реальном железе, либо иметь эмулятор. Правда в итоге написанную программу всё равно придётся запускать на настоящем микрокомпьютере, так как ни один эмулятор не способен обеспечить 100%-ного качества эмуляции. Пик интереса к bare metal программированию пришёлся на 7-10 лет назад, спустя короткое время после выхода Raspberry Pi, когда этот микрокомпьютер утвердился в качестве некоего стандарта, и когда появилось достаточное количество информации о его схемотехнике. Но на тот момент вообще не существовало какого-либо эмулятора этого устройства. Да, уже тогда существовал QEMU, который неплохо эмулировал устройства, базирующие на архитектуре ARM, но среди них не было платформы Raspberry Pi. Разработчики игры PiFox, очевидно для удобства отладки, на базе QEMU сделали свой эмулятор, который худо-бедно мог эмулировать архитектуру Raspberry Pi. Доводилось мне читать упоминания и о других подобных начинаниях. Но в конечном итоге, спустя несколько лет в QEMU появилась официальная поддержка Raspberry Pi. И начали они с эмуляции Raspberry Pi 2. А на сегодня мы имеем возможность эмулировать Raspberry Pi 1, 2, 3 и Zero.
Когда я только начал изучать вопрос, то поиски приводили меня лишь к всевозможным инструкциям для запуска RaspbianOS на QEMU. Сам запуск осуществлялся на кастомном ядре и на архитектуре отличной от Raspberry Pi - Arm Versatile. Предлагались инструкции по самостоятельным конфигурированию и сборке ядра, также готовые ядра под разные версии RaspbianOS. Но в итоге в QEMU завезли нативную эмуляцию Raspberry Pi и появилась возможность использовать стандартное ядро для запуска RaspbianOS. Правда, ядро не может подхватываться из образа, его предварительно необходимо оттуда извлечь, положить рядом с загружаемым образом и прописать в ключ запуска.
Вот так примерно будет выглядеть довольно длинная строка запуска нативной эмуляции RaspbianOS:
Код:
qemu-system-aarch64 -M raspi3b -append "rw earlyprintk loglevel=8 console=ttyAMA0,115200 dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootdelay=1" -dtb ./dtbs/bcm2710-rpi-3-b-plus.dtb -sd 2020-08-20-raspios-buster-armhf-full.img -kernel kernel8.img -m 1G -smp 4 -serial stdio -usb -device usb-mouse -device usb-kbd
И действительно, всё загружается и работает. Только это никак не помогает понять как же загрузить свою собственную программу в эмулятор QEMU. Из всего этого стало лишь понятно, что нужен особым образом скомпилированный kernel. И что я только не делал, как только не экспериментировал, но моя программа отказывалась запускаться под эмулятором. Вот оно по всем законам должно работать, но работает! И, честно сказать, руки мои практически опустились. Но тут мои поиски привели меня вот к этому примеру. Этот пример написан на ассемблере, он в графическом виде выводит надпись "NO OS" на экран, а также выводит надпись "No OS installed" в консоль. И при этом данный пример должен был запускаться под эмулятором! (Тоже скачайте его себе для экспериментов, я в дальнейшем буду объяснять всё именно на его примере!)
И у меня получилось скомпилировать и запустить эту простую программу под эмулятором!
Не скажу, что это было для меня просто, но у меня всё получилось. И вот тут пришло время немножко подробнее остановиться на средствах разработки.
FASMARM
В качестве кросс-ассемблера я остановил свой выбор на FASMARM. Я уже достоверно не помню как я на него вышел, но скорее всего потому что этот кросс-ассемблер фигурировал в примерах Питера Лемона. По своей сути FASMARM является аддоном для Flat Assembler и работает как под Windows, так и под Linux. Этот инструмент представляет из себя крохотный по сегодняшним меркам исполняемый файл. Он не требует инсталляции и не подтягивает какие-либо сторонние библиотеки.
Любопытно, что данный кросс-ассемблер сам написан на ассемблере. А что самое интересное - все последние версии FASMARM компилируются на Flat Assembler'е, то есть на самом себе.
Скачать сам ассемблер и его исходники можно с официальной странички: https://arm.flatassembler.net/
Использование его предельно простое:
А на выходе моментально получим готовый для запуска файл filename.bin.
Но определяющим при выборе ассемблера для меня стал всё-таки синтаксис. Дело в том, разные программы написанные на ассемблере ARM под разные компиляторы скорее всего потребуют доработок, а иногда весьма серьёзных из-за различий в синтаксисе. Например, FASMARM для обозначения комментариев использует привычную для меня точку с запятой ( ; ), а стандартные инструменты для этой же цели используют "собаку" ( @ ). Также весьма существенно отличается синтаксис макросов, обозначение чисел, наличие и отсутствие некоторых псевдокоманд. Нет смысла сейчас подробно останавливаться на этих различиях, а достаточно просто знать, что они есть.
Большую часть примеров, которые я находил в начале моего изучения, я смог скомпилировать на FASMARM либо вообще без переделок, либо с какими-то минимальными доработками. И вот как раз тот самый единственный найденный мной пример, который должен запускался под эмулятором, ни в какую не хотел переделываться под FASMARM. Да, к тому же ещё требовал линковки. Пришлось разбираться как компилировать другим инструментом. Оказалось, что там тоже нет ничего сложного.
Arm GNU Toolchain
Для начала нужно скачать инструментарий на сайте ARM. Инструментарий бесплатный, но с недавних пор, чтобы скачать его из РФ придётся воспользоваться VPN или Tor.
Качаем с этой страницы: https://developer.arm.com/downloads/...hain-downloads
Прямую ссылку на установочный файл не привожу, так как с выходом новых версий она меняется. Но вам нужно будет скачать примерно такой файл: arm-gnu-toolchain-12.3.rel1-mingw-w64-i686-arm-none-eabi.exe ,где:
w64-i686 - платформа компилятора Windows 64 бит
arm-none-eabi - целевая платформа ARM 32 бит
Скачиваем и устанавливаем.
Чтобы скомпилировать тот самый пример я подготовил вот такой bat-файл, основываясь на информации из Makefile:
Код:
@set file=boot-arm-raspi
@set toolchain="c:\Program Files (x86)\Arm GNU Toolchain arm-none-eabi\12.3 rel1\bin\arm-none-eabi"
python bmp2inc.py > bmp_no_os.inc
%toolchain%-as -o %file%.o -mcpu=arm1176jzf-s %file%.s
%toolchain%-ld -o %file%.elf -Ttext 0x8000 %file%.o
@if exist %file%.o del %file%.o
На выходе получаем boot-arm-raspi.elf, который можем запустить в эмуляторе следующей командой:
Код:
"c:\Program Files\qemu\qemu-system-arm.exe" -M raspi2b -serial stdio -kernel boot-arm-raspi.elf
Оказалось, что QEMU может обрабатывать elf-файлы, забирая оттуда всю необходимую для запуска информацию.
И всё бы хорошо, но этот вариант годится только для компилирования исходников с помощью Arm GNU Toolchain, а мне хотелось как и раньше пользоваться FASMARM. И я в итоге разобрался как прикрутить FASMARM к этой цепочке.
Прежде всего в исходном тексте своей программы в самом начале нужно добавить вот такие строки:
Код:
format ELF
section '.text' executable
public _start
org 0x8000
_start:
А для компиляции я создал вот такой батник:
Код:
@set file=filename
@set toolchain="c:\Program Files (x86)\Arm GNU Toolchain arm-none-eabi\12.3 rel1\bin\arm-none-eabi"
fasmarm %file%.s
%toolchain%-ld.exe -o %file%.elf -Ttext 0x8000 %file%.o
@if exist %file%.o del %file%.o
Ну и сразу здесь же напишу, что на выходе вы можете получить не только elf, но и бинарный файл, который можно записать на SD-карту и запустить на реальной Raspberry Pi. Для этого в конец цепочки компиляции нужно добавить ещё одну строку:
Код:
%toolchain%-objcopy %file%.elf -O binary %file%.img
Собственно, это и есть рабочий рецепт, которым я и начал пользоваться. И на этом можно было бы завершить данное сообщение. Но оказалось, что на деле для запуска скомпилированых программ под эмулятором можно обойтись вообще без всех этих плясок с Arm GNU Toolchain. И всё делается предельно просто!
В процессе изучения другого вопроса, я натолкнулся на ключ QEMU "-d in_asm", который показывает в консоли последние выполненные команды. Я сделал и скомпилироваал простую программу, состоящую из одной строки:
Я запустил её под эмулятором с указанным ключом. И что же я увидел:
Данная команда работает по адресу 0x10000! Это очень странный адрес! 32-х битные приложения запускаются и работают с адреса 0x8000. Для старых ядер адрес запуска был нулевой (0x0000). (Этот адрес можно вернуть, если в системной файле Raspberry Pi config.txt прописать команду kernel_old = 1.) Есть ещё адрес 0x200000 - это адрес запуска 64-х битных бинарников. Но никогда и нигде мне не попадалось упоминание о 0x10000. Я попробовал скомпилировать и запустить первую попавшуюся программу с этого адреса. И она как ни в чём не бывало запустилась под QEMU! Всё оказалось до безобразия просто.
Для запуска под эмулятором вам нужно всего лишь в своём коде прописать org 0x10000! (Собственно, это и есть та самая фраза, которой можно было ограничиться в этом сообщении.)
Теперь вы можете брать примеры Питера Лемона, о которых говорилось выше, менять там ORG, компилировать, и запускать всё под QEMU.