-- Leo's gemini proxy

-- Connecting to any-key.press:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

squⱯt v0.0.1


С++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`.


MPIDR_EL1, Multiprocessor Affinity Register

PrimeCell UART (PL011) Technical Reference Manual

QEMU: ‘virt’ generic virtual platform


UPD(2023-09-12): Связи с тем, что ARM блокирует запросы к своей документации для пользователей из РФ, ниже добавлены ссылки на локально сохранённые документы.

Arm Architecture Registers

PrimeCell UART (PL011)


Конфигурирование сборки


Для сборки используются утилиты из состава 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` файл.


The LLVM Compiler Infrastructure


Просмотр содержимого собранных бинарных файлов


Утилиту `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