-- Leo's gemini proxy
-- Connecting to any-key.press:1965...
-- Connected
-- Sending request
-- Meta line: 20 text/gemini
С++11 код, запускаемый на aarch64 железе.
Код проекта расположен в следующих исходных файлах:
`boot.S`: стартовый (загрузочный) код в виде ассемблерного листинга. Код загоняет в бесконечный цикл все процессоры, кроме _первого_ (используя для идентификации регистр MPIDR_EL1). А на первом процессоре обнуляет секцию `BSS`, настраивает стек на адрес точки входа и отдает управление в C++ функцию `kernel_entry_point`. Стек по мере наполнения растет в сторону уменьшения адреса, поэтому стартовые код (точки входа) не перетирается.
`kernel.cc`: модуль условного ядра. Вызывает необходимую инициализацию (на текущий момент это только UART) и передает управление коду полезной нагрузки (на текущий момент это `UART echo test mode`: получение из UART символов и их обратная отсылка).
`uart_virt.cc`: реализация работы UART(PL011) для платформы virt в QEMU.
`linker.ld`: описание геометрии секций для компоновщика.
Проект собирается и запускается с использованием простого `Makefile`.
UPD(2023-09-12): Связи с тем, что ARM блокирует запросы к своей документации для пользователей из РФ, ниже добавлены ссылки на локально сохранённые документы.
Для сборки используются утилиты из состава LLVM. Для учета локальных особенностей системы, где происходит сборка, используется файл `config.mk`.
Сборка и запуск протестированы в двух arm64-конфигурациях:
Alpine Linux v3.17. Из особенностей тут только то, что используется утилита `objcopy` из пакета `llvm14`.
$ cat config.mk OBJCOPY = llvm14-objcopy
OpenBSD 7.2. Локальный конфиг нужен для явного использования инструментария LLVM из пакетов, так как встроенный в базу не умеет кросс-компилировать.
$ cat config.mk AS = /usr/local/bin/clang CXX = /usr/local/bin/clang++ LD = /usr/local/bin/ld.lld OBJCOPY = /usr/local/bin/llvm-objcopy
Сборка (команда `make`) последовательно вызовет следующие утилиты:
`${AC}` (по умолчанию `clang`): обработка ассемблерного файла (`.S`) для получения объектного (`.o`).
`${CXX}` (по умолчанию `clang++`): обработка C++ файлов (`.cc`) для получения объектных (`.o`).
`${LD}` (по умолчанию `ld.lld`): компоновка исполняемого (`.elf`) файла из полученных на предыдущих шагах объектных файлов.
`${OBJCOPY}` (по умолчанию `llvm-objcopy`): дамп исполняемого файла в плоское представление, как если бы этот файл был загружен в память на исполнение. Результатом получает `.img` файл.
Утилиту `llvm-objdump` можно использовать для инспектирования содержимого получившегося `.elf` файла:
$ llvm-objdump -d squat.elf squat.elf: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: a0 00 38 d5 mrs x0, MPIDR_EL1 4: 00 5c 40 92 and x0, x0, #0xffffff 8: 60 00 00 b4 cbz x0, 0x14 <_first_processor> 000000000000000c <_idle>: c: 5f 20 03 d5 wfe 10: ff ff ff 17 b 0xc <_idle> 0000000000000014 <_first_processor>: 14: 60 14 00 10 adr x0, #652 18: 41 14 00 10 adr x1, #648 000000000000001c <_zero_bss>: 1c: 1f 84 00 f8 str xzr, [x0], #8 20: 1f 00 01 eb cmp x0, x1 24: cb ff ff 54 b.lt 0x1c <_zero_bss> 28: c0 fe ff 10 adr x0, #-40 2c: 1f 00 00 91 mov sp, x0 30: 02 00 00 94 bl 0x38 <kernel_entry_point> 34: f6 ff ff 17 b 0xc <_idle> Disassembly of section .text: 0000000000000038 <kernel_entry_point>: 38: fd 7b bf a9 stp x29, x30, [sp, #-16]! 3c: fd 03 00 91 mov x29, sp 40: 25 00 00 94 bl 0xd4 <_ZN5Board4Uart10initializeEv> 44: 1f 20 03 d5 nop 48: a0 10 00 10 adr x0, #532 4c: 04 00 00 94 bl 0x5c <_ZN12_GLOBAL__N_116uart_send_stringEPKc> 50: 18 00 00 94 bl 0xb0 <_ZN12_GLOBAL__N_119uart_echo_test_modeEv> 54: fd 7b c1 a8 ldp x29, x30, [sp], #16 58: c0 03 5f d6 ret
Тут стоит запомнить, что первая инструкция, которая ожидается к исполнению `mrs x0, MPIDR_EL1` имеет байт код `a0 00 38 d5`.
А результирующий _плоский_ `.img` файл можно смотреть с использованием классического `hexdump`:
$ hexdump -C squat.img 00000000 a0 00 38 d5 00 5c 40 92 60 00 00 b4 5f 20 03 d5 |..8..\@.`..._ ..| 00000010 ff ff ff 17 60 14 00 10 41 14 00 10 1f 84 00 f8 |....`...A.......| 00000020 1f 00 01 eb cb ff ff 54 c0 fe ff 10 1f 00 00 91 |.......T........| 00000030 02 00 00 94 f6 ff ff 17 fd 7b bf a9 fd 03 00 91 |.........{......|
Тут стоит снова заметить, что в начале дампа мы снова видим заветный байт код `a0 00 38 d5`, то есть нашу инструкцию `mrs x0, MPIDR_EL1`. Это минимальный признак того, что сборка прошла успешно.
Пощупать текущую полезную нагрузку можно выполнив команду `make qemu`:
$ make qemu qemu-system-aarch64 -M virt -cpu cortex-a53 -kernel squat.img -nographic -monitor none -serial stdio Squat entry point UART echo test mode >
В таком режиме код ждет получения очередного символа через UART, а затем отсылает его же обратно.
При необходимости можно запустить QEMU в режиме отладки командой `make gdb-remote`. В отличии от обычного запуска (`make qemu`) при запуске qemu добавляются аргументы `-s -S`, которые включают отладку и ждут подключения отладчика.
В нашем случае отладчиком будем выступать `lldb`, который локально подключится к QEMU через порт `1234`:
$ lldb (lldb) gdb-remote 1234
-- Response ended
-- Page fetched on Mon May 20 11:38:34 2024