執筆者 : 原田 秀一
はじめに
現在ArmアーキテクチャのCPUは採用も多く情報も溢れていますが、改めてARMv8-Aアーキテクチャでの64bit環境(AArch64)でのブートプログラム作成についてまとめます。
ブート処理の内容は過去記事 RISC-V OSを作ろう (1) ~ブート処理 - VA Linux エンジニアブログ と同様にOSを動かす土台とできるようにします。
環境の準備
ツールチェーンはArmが提供しているものを使用しました。 https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
実行環境はQEMUを使用します。
専用の実験環境を作るためDockerfileを用意しました。
Dockerfile
FROM ubuntu RUN apt-get update && apt-get install -y curl xz-utils libncursesw5 libpython2.7 libexpat1 qemu-system-arm make \ && curl -sL https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz \ | tar xJ -C /usr ENV PATH="$PATH:/usr/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin" ARG UID=1000 ARG GID=1000 ARG WORK_DIR='/work' RUN useradd -m -u ${UID} docker && mkdir -p $WORK_DIR && chown -R $UID:$GID $WORK_DIR USER ${UID} ENTRYPOINT ["/bin/bash"]
今回は以下のバージョンとなっています。
名称 | バージョン |
---|---|
Arm GNU Toolchain | 10.3-2021.07 |
qemu-system-arm | 6.2.0 |
- Arm GNU Toolchainは現在さらに新しいバーンジョンが提供されているようです。
- gdbはArm GNU Toolchainに含まれています。
ブートプログラム
RISC-Vの記事と同様に、今回もテキストやデータもすべてRAM上に配置して動作させることにします。
ブート処理では
- ベクタテーブルの設定
- AArch64ステートの設定
- 例外レベルの変更
- スタックポインタ初期化
- bssセクション初期化
を行います。
例外レベル
最初に抑えておくべき仕様として例外レベルがあります。 ARMv8-Aでは例外レベル(Exception Level)としてEL0,EL1,EL2,EL3が定義されています。 EL3が最も権限が強くEL0が最低となりアクセス可能なシステムレジスタ等が異なってきます。
各例外レベルは以下の用途が想定されています。
例外レベル | 用途 |
---|---|
EL3 | セキュアモニタ |
EL2 | ハイパーバイザ |
EL1 | OS |
EL0 | ユーザー |
EL3, EL2に対応しているかはハードウェア実装依存、EL1, EL0は必須実装となっていますが、多くの場合は全レベル実装されてるようです。
リセット時は実装されている最高権限で実行開始されます。
今回はEL3, EL2が実装された環境を想定してEL3で実行開始された状態からOS動作のためにEL1に遷移するようにします。
EL1に遷移する前にしか行えない設定処理があるため遷移方法は後ほど扱います。
例外動作の概要
例外動作の概要を以下の図に示します。
通常処理を実行中に例外発生することで例外処理の実行が開始されます。 このとき、元々実行していた通常処理に復帰するためのレジスタとして、 PCがELR_ELxに、 PSTATEがSPSR_ELxに保存されます。
例外処理はeret命令の実行により終了します。 このとき、ELR_ELxがPCに、SPSR_ELxがPSTATEに復帰され例外発生した箇所から通常処理が再開されます。
ELR_ELx, SPSR_ELxのxは例外処理が実行される例外レベルとなります。 例えば、EL0で通常処理実行中に例外が発生しEL1に遷移する場合は それぞれELR_EL1, SPSR_EL1となります。
また、例外要因や設定によって例外レベルの遷移がない場合もあります。 以下の例はEL1で例外処理実行中に例外発生しEL1で例外処理が実行される場合を示します。
このような場合、ELR_EL1, SPSR_EL1が例外発生毎に上書きされるためプログラム側で対策しておく必要があります。 次回予定の割り込み処理でこの辺りのことは扱います。
TrustZone
Armのセキュリティ関連機能としてTrustZoneというものがあります。 概念的にSecure World / Non-Secure Worldに分離が行われ、Non-Secure WorldからはSecure Worldのリソースにアクセスできなくするものです。
今回はNon-Secure Worldで動作させます。 この設定にはシステムレジスタSCR_EL3のNSビットを1に設定します。
ベクタテーブルの設定
例外発生時に参照されるベクタテーブルは、EL3, EL2, EL1毎にベースアドレスの設定を行う必要があり、 それぞれ VBAR_EL3, VBAR_EL2, VBAR_EL1 レジスタを設定します。
ベクタテーブルの中身は、例外タイプと例外レベル遷移の有無等でオフセットアドレスが決まっています。
AArch64 exception vector table - AArch64 Exception and Interrupt Handling
例外タイプは、同期例外、割り込み(IRQ)、割り込み(FIQ)、システムエラーの4つとなっています。
例外レベル遷移の有無等 は、
- 同じ例外レベルで例外が発生しSP0を使用する場合
- 同じ例外レベルで例外が発生しSPxを使用する場合
- 下位の例外レベルがaarch64動作していた場合
- 下位の例外レベルがaarch32動作していた場合
の4つの条件があります。
SP0, SPxは、使用するスタックポインタ状態を意味します。 SPは例外レベル毎に存在し、EL0以外ではSPとしてSP_EL0を使用するか現在の例外レベルのSP_ELxを使用するか選択できます。 (PSTATE.SPビットで設定)
今回は割り込みは使用しませんが意図せず例外が発生した場合にはセルフジャンプになるようベクタテーブルを定義しておきます。 (ファイルはvector.Sとします)
.section ".entry.text", "ax" .global default_vector_table .balign 0x800 default_vector_table: // Current EL with SP0 .balign 0x80 curr_el_sp0_sync: // Synchronous b . .balign 0x80 curr_el_sp0_irq: // IRQ/vIRQ b . .balign 0x80 curr_el_sp0_fiq: // FIQ/vFIQ b . .balign 0x80 curr_el_sp0_serr: // SError/vSError b . // Current EL with SPx .balign 0x80 curr_el_spx_sync: // Synchronous b . .balign 0x80 curr_el_spx_irq: // IRQ/vIRQ b . .balign 0x80 curr_el_spx_fiq: // FIQ/vFIQ b . .balign 0x80 curr_el_spx_serr: // SError/vSError b . // Lower EL using AArch64 .balign 0x80 lower_el_a64_sync: // Synchronous b . .balign 0x80 lower_el_a64_irq: // IRQ/vIRQ b . .balign 0x80 lower_el_a64_fiq: // FIQ/vFIQ b . .balign 0x80 lower_el_a64_serr: // SError/vSError b . // Lower EL using AArch32 .balign 0x80 lower_el_a32_sync: // Synchronous b . .balign 0x80 lower_el_a32_irq: // IRQ/vIRQ b . .balign 0x80 lower_el_a32_fiq: // FIQ/vFIQ b . .balign 0x80 lower_el_a32_serr: // SError/vSError b .
ブート処理では上記で定義したベクタテーブルを例外レベル毎のVBAR_ELxに設定します。
ldr x5, =default_vector_table msr VBAR_EL3, x5 msr VBAR_EL2, x5 msr VBAR_EL1, x5
AArch64ステートの設定
リセット後初期状態はAArch64ステートで実行されますが、EL2,EL1,EL0に対しては実行ステートを設定する必要があります。 今回はすべての例外レベルでAArch64ステートで実行するために以下の設定を行います。
- EL2をAArch64に設定 (SCR_EL3.RW = 1)
- EL1をAArch64に設定 (HCR_EL2.RW = 1)
EL2は使用しませんが上位の例外レベルがAArch32で下位のレベルがAArch64という組み合わせはできないため、EL2もAArch64に設定しておく必要があります。 またNon-Secureにするための設定もSCR_EL3を使用するためここで同時に行っています(SRC_EL3.NS= 1 )。
// Non-Secure // set EL2 to AArch64 mrs x0, SCR_EL3 mov x1, SCR_EL3_RW | SCR_EL3_NS orr x0, x0, x1 msr SCR_EL3, x0 // set EL1 to AArch64 mov x2, #HCR_EL2_RW msr HCR_EL2, x2
スタックポインタ設定
今回ブート処理ではスタックは使用していませんがスタックポインタを設定しておきます。
// set stack pointer ldr x4, =_stack_end mov sp, x4
例外レベルの遷移
下位の例外レベルに遷移するには例外を抜ける動作を行います。 例外からの復帰先アドレスや状態の設定を行った後に復帰命令eret実行します。
復帰先アドレスをELR_ELR3に設定
今回はEL1_startを復帰先として設定しています。
復帰時のPSR(PSTATE)値をSPSR_EL3に設定
ここではA, I, Fビットを設定することで復帰時にSError, IRQ, FIQをマスクするようにしています。 またEL1に遷移しSP_EL1を使用するようにモード設定しています。
レジスタ仕様はこちら SPSR_EL3
モードは例外レベルとSP選択の組み合わせとなっておりEL1h, EL1tのように記載されています。 このh, tは、それぞれhandler mode, thread modeに由来するようです。
SP_EL1を設定
今回は現在のSPをそのままEL1時のSPとして使用しています。
eret命令の実行
// tansition (return) to EL1 EL3_TO_EL1: // ELR_EL3 ldr x0, =EL1_start msr ELR_EL3, x0 // set up the return data // SPSR_EL3 mov x0, PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1h msr SPSR_EL3, x0 mov x24, sp msr SP_EL1, x24 eret
EL1での動作
EL1に遷移した後、main関数を呼び出します。 main関数が終了してきた場合はセルフジャンプするようにしています。
EL1_start: bl main b .
ここまでのboot処理をstart.Sとしてまとめます。 システムレジスタのビットを定義したC言語ヘッダファイルをarmv8def.hとして作成しインクルードしています。 アセンブラソースに対してもCプリプロセッサを適用するためstart.Sと拡張子は大文字になっています。
start.S
#include "armv8def.h" .section .reset,"ax",@progbits .global _start _start: // vector table ldr x5, =default_vector_table msr VBAR_EL3, x5 msr VBAR_EL2, x5 msr VBAR_EL1, x5 // Non-Secure // set EL2 to AArch64 mrs x0, SCR_EL3 mov x1, SCR_EL3_RW | SCR_EL3_NS orr x0, x0, x1 msr SCR_EL3, x0 // set EL1 to AArch64 mov x2, #HCR_EL2_RW msr HCR_EL2, x2 // set stack pointer ldr x4, =_stack_end mov sp, x4 // tansition (return) to EL1 EL3_TO_EL1: // ELR_EL3 ldr x0, =EL1_start msr ELR_EL3, x0 // set up the return data // SPSR_EL3 mov x0, PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1h msr SPSR_EL3, x0 mov x24, sp msr SP_EL1, x24 eret EL1_start: bl main b .
armv8def.h
#ifndef _ARMV8DEF_H_ #define _ARMV8DEF_H_ /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d #define PSR_MODE_MASK 0x0000000f /* AArch64 SPSR bits */ #define PSR_F_BIT 0x00000040 #define PSR_I_BIT 0x00000080 #define PSR_A_BIT 0x00000100 /* SCR_EL3 bits */ #define SCR_EL3_RW 0x00000400 #define SCR_EL3_NS 0x00000001 /* HCR_EL2 bits */ #define HCR_EL2_RW 0x80000000 #endif /* _ARMV8DEF_H_ */
今回main関数ではbssセクションの初期化のみ行っています。 後述のリンカディレクティブファイルで定義しているbssセクション中に定義されたシンボルbss_start ~ bss_end間をゼロクリアします。
main.c
static void clearbss(void) { unsigned long long *p; extern unsigned long long _bss_start[]; extern unsigned long long _bss_end[]; for (p = _bss_start; p < _bss_end; p++) { *p = 0LL; } } void main(void) { clearbss(); }
メモリ配置
qemu仮想マシンタイプはvirtを使用します。
メモリマップが記載されてるドキュメントがわからなかったのでソースで確認しました。
https://github.com/qemu/qemu/blob/master/hw/arm/virt.c のうちVIRT_MEMの部分が該当します。
static const MemMapEntry base_memmap[] = { /* Space up to 0x8000000 is reserved for a boot ROM */ [VIRT_FLASH] = { 0, 0x08000000 }, [VIRT_CPUPERIPHS] = { 0x08000000, 0x00020000 }, /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */ [VIRT_GIC_DIST] = { 0x08000000, 0x00010000 }, [VIRT_GIC_CPU] = { 0x08010000, 0x00010000 }, [VIRT_GIC_V2M] = { 0x08020000, 0x00001000 }, [VIRT_GIC_HYP] = { 0x08030000, 0x00010000 }, [VIRT_GIC_VCPU] = { 0x08040000, 0x00010000 }, /* The space in between here is reserved for GICv3 CPU/vCPU/HYP */ [VIRT_GIC_ITS] = { 0x08080000, 0x00020000 }, /* This redistributor space allows up to 2*64kB*123 CPUs */ [VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, [VIRT_UART] = { 0x09000000, 0x00001000 }, [VIRT_RTC] = { 0x09010000, 0x00001000 }, [VIRT_FW_CFG] = { 0x09020000, 0x00000018 }, [VIRT_GPIO] = { 0x09030000, 0x00001000 }, [VIRT_SECURE_UART] = { 0x09040000, 0x00001000 }, [VIRT_SMMU] = { 0x09050000, 0x00020000 }, [VIRT_PCDIMM_ACPI] = { 0x09070000, MEMORY_HOTPLUG_IO_LEN }, [VIRT_ACPI_GED] = { 0x09080000, ACPI_GED_EVT_SEL_LEN }, [VIRT_NVDIMM_ACPI] = { 0x09090000, NVDIMM_ACPI_IO_LEN}, [VIRT_PVTIME] = { 0x090a0000, 0x00010000 }, [VIRT_SECURE_GPIO] = { 0x090b0000, 0x00001000 }, [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, [VIRT_SECURE_MEM] = { 0x0e000000, 0x01000000 }, [VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 }, [VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 }, [VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 }, /* Actual RAM size depends on initial RAM and device memory settings */ [VIRT_MEM] = { GiB, LEGACY_RAMLIMIT_BYTES }, };
VIRT_MEMがRAMの定義で、開始アドレスは 'GiB' となっています。
GiB
は
https://github.com/qemu/qemu/blob/master/include/qemu/units.h
に以下の定義となっています。
#define GiB (INT64_C(1) << 30)
これよりRAMは0x40000000番地~となっています。
リンカディレクティブファイルはこれに合わせてRAM領域を定義しています。 セクション配置は単純にRAM上に順にtext, rodata, data, bss, stackセクションを定義しています。
aarch64_virt.ld
OUTPUT_ARCH( "aarch64" ) ENTRY( _start ) MEMORY { ram (wxa!ri) : ORIGIN = 0x40000000, LENGTH = 128M } SECTIONS { .text : { PROVIDE(_text_start = .); *(.reset) *(.text .text.*) PROVIDE(_text_end = .); } >ram AT>ram .rodata : { PROVIDE(_rodata_start = .); *(.rodata .rodata.*) *(.note.* ) PROVIDE(_rodata_end = .); } >ram AT>ram .data : { . = ALIGN(4096); PROVIDE(_data_start = .); *(.data .data.*) } >ram AT>ram .bss :{ . = ALIGN(16); PROVIDE(_bss_start = .); *(.bss .bss.*) . = ALIGN(16); PROVIDE(_bss_end = .); } >ram AT>ram .stack :{ . = ALIGN(16); PROVIDE(_stack_start = .); . = . + 4096; PROVIDE(_stack_end = .); } >ram AT>ram }
コンパイル
aarch64用クロスコンパイラでコンパイルします。
標準ライブラリがない環境を指定する-ffreestanding
を指定しています。
今回の環境では、-ffreestanding
指定をしていない場合、clearbss関数でイブラリのmemset関数を使用するコードが生成されリンクエラーが発生します。
-ffreestanding
のオプション仕様はこちらから確認できます。
C Dialect Options (Using the GNU Compiler Collection (GCC))
aarch64-none-elf-gcc -c -O2 -ffreestanding -g main.c start.S vectors.S aarch64-none-elf-ld -T aarch64_virt.ld main.o start.o vectors.o
生成された命令列を確認してみます。
$ aarch64-none-elf-objdump -d a.out a.out: file format elf64-littleaarch64 Disassembly of section .text: 0000000040000000 <_start>: 40000000: 580002c5 ldr x5, 40000058 <EL1_start+0xc> 40000004: d51ec005 msr vbar_el3, x5 40000008: d51cc005 msr vbar_el2, x5 4000000c: d518c005 msr vbar_el1, x5 40000010: d53e1100 mrs x0, scr_el3 40000014: d2808021 mov x1, #0x401 // #1025 40000018: aa010000 orr x0, x0, x1 4000001c: d51e1100 msr scr_el3, x0 40000020: d2b00002 mov x2, #0x80000000 // #2147483648 40000024: d51c1102 msr hcr_el2, x2 40000028: 580001c4 ldr x4, 40000060 <EL1_start+0x14> 4000002c: 9100009f mov sp, x4 0000000040000030 <EL3_TO_EL1>: 40000030: 580001c0 ldr x0, 40000068 <EL1_start+0x1c> 40000034: d51e4020 msr elr_el3, x0 40000038: d28038a0 mov x0, #0x1c5 // #453 4000003c: d51e4000 msr spsr_el3, x0 40000040: 910003f8 mov x24, sp 40000044: d51c4118 msr sp_el1, x24 40000048: d69f03e0 eret 000000004000004c <EL1_start>: 4000004c: 94000009 bl 40000070 <main> 40000050: 14000000 b 40000050 <EL1_start+0x4> 40000054: 00000000 udf #0 40000058: 40000800 .word 0x40000800 4000005c: 00000000 .word 0x00000000 40000060: 40002000 .word 0x40002000 40000064: 00000000 .word 0x00000000 40000068: 4000004c .word 0x4000004c 4000006c: 00000000 .word 0x00000000 0000000040000070 <main>: 40000070: b0000001 adrp x1, 40001000 <_bss_end> 40000074: b0000000 adrp x0, 40001000 <_bss_end> 40000078: 91000022 add x2, x1, #0x0 4000007c: 91000000 add x0, x0, #0x0 40000080: eb00005f cmp x2, x0 40000084: 54000142 b.cs 400000ac <main+0x3c> // b.hs, b.nlast 40000088: d1000401 sub x1, x0, #0x1 4000008c: aa0203e0 mov x0, x2 40000090: cb020021 sub x1, x1, x2 40000094: 927df021 and x1, x1, #0xfffffffffffffff8 40000098: 91002021 add x1, x1, #0x8 4000009c: 8b020021 add x1, x1, x2 400000a0: f800841f str xzr, [x0], #8 400000a4: eb01001f cmp x0, x1 400000a8: 54ffffc1 b.ne 400000a0 <main+0x30> // b.any 400000ac: d65f03c0 ret Disassembly of section .entry.text: 0000000040000800 <default_vector_table>: 40000800: 14000000 b 40000800 <default_vector_table> 40000804: d503201f nop 40000808: d503201f nop 4000080c: d503201f nop 40000810: d503201f nop 40000814: d503201f nop 40000818: d503201f nop 4000081c: d503201f nop 40000820: d503201f nop 40000824: d503201f nop 40000828: d503201f nop 4000082c: d503201f nop 40000830: d503201f nop 40000834: d503201f nop 40000838: d503201f nop 4000083c: d503201f nop 40000840: d503201f nop 40000844: d503201f nop 40000848: d503201f nop 4000084c: d503201f nop 40000850: d503201f nop 40000854: d503201f nop 40000858: d503201f nop 4000085c: d503201f nop 40000860: d503201f nop 40000864: d503201f nop 40000868: d503201f nop 4000086c: d503201f nop 40000870: d503201f nop 40000874: d503201f nop 40000878: d503201f nop 4000087c: d503201f nop 0000000040000880 <curr_el_sp0_irq>: (以降同じ内容のベクタテーブルが続くので省略)
QEMU上で起動
生成されたオブジェクトa.outをaarch64用qemu上で起動します。 -d in_asm
オプションを指定し、実行された命令を出力してみます。
-cpu
オプションはARMv8-Aのcortex-A57を指定しています。
またqemuではオプションを指定しないとEL=1(EL3,EL2未実装状態)で実行開始されるようです。
-machine
オプションを使用して vitrualizaion=true
でEL2有効、secure=on
でEL3有効としています。
$ qemu-system-aarch64 -machine virt,virtualization=true,secure=on -cpu cortex-a57 -machine type=virt -m 128M -nographic -no-reboot -kernel a.out -d in_asm
_start
が配置された0x40000000番地から実行開始されているのがわかります。
msr/mrs命令の部分で(unknown)と出力されているのはqemuの逆アセンブル処理がシステムレジスタに対応していないためと思われます。
IN: 0x0000000040000000: 580002c5 ldr x5, pc+88 (addr 0x40000058) 0x0000000040000004: d51ec005 msr (unknown), x5 ---------------- IN: 0x0000000040000008: d51cc005 msr (unknown), x5 ---------------- IN: 0x000000004000000c: d518c005 msr (unknown), x5 ---------------- IN: 0x0000000040000010: d53e1100 mrs x0, (unknown) 0x0000000040000014: d2808021 mov x1, #0x401 0x0000000040000018: aa010000 orr x0, x0, x1 0x000000004000001c: d51e1100 msr (unknown), x0 ---------------- IN: 0x0000000040000020: d2b00002 mov x2, #0x80000000 0x0000000040000024: d51c1102 msr (unknown), x2 ---------------- IN: 0x0000000040000028: 580001c4 ldr x4, pc+56 (addr 0x40000060) 0x000000004000002c: 9100009f mov sp, x4 0x0000000040000030: 580001c0 ldr x0, pc+56 (addr 0x40000068) 0x0000000040000034: d51e4020 msr (unknown), x0 ---------------- IN: 0x0000000040000038: d28038a0 mov x0, #0x1c5 0x000000004000003c: d51e4000 msr (unknown), x0 ---------------- IN: 0x0000000040000040: 910003f8 mov x24, sp 0x0000000040000044: d51c4118 msr (unknown), x24 ---------------- IN: 0x0000000040000048: d69f03e0 unimplemented (UnconditionalBranchToRegister) ---------------- IN: 0x000000004000004c: 94000009 bl #+0x24 (addr 0x40000070) ---------------- IN: main 0x0000000040000070: b0000001 adrp x1, #+0x1000 (addr 0x40001000) 0x0000000040000074: b0000000 adrp x0, #+0x1000 (addr 0x40001000) 0x0000000040000078: 91000022 add x2, x1, #0x0 (0) 0x000000004000007c: 91000000 add x0, x0, #0x0 (0) 0x0000000040000080: eb00005f cmp x2, x0 0x0000000040000084: 54000142 b.hs #+0x28 (addr 0x400000ac) ---------------- IN: main 0x00000000400000ac: d65f03c0 ret ---------------- IN: 0x0000000040000050: 14000000 b #+0x0 (addr 0x40000050)
main関数をret命令で抜けて実行によりstart.Sの最後に配置しておいたセルフジャンプに入ってることがわかります。
GDBで制御
qemu上のプログラムをgdbを用いて操作してみます。 まず、qemuをgdb接続待ちで立ち上げます。待ち受けるポートは10000以外でも構いません。
qemu-system-aarch64 -machine virt,virtualization=true,secure=on -cpu cortex-a57 -machine type=virt -m 128M -nographic -no-reboot -kernel a.out -S -gdb tcp::10000
別のターミナルウィンドウからgdbを起動し、localhostの10000ポートに接続します。
docker@28fcb11dde64:/work$ aarch64-none-elf-gdb a.out GNU gdb (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 10.2.90.20210621-git Copyright (C) 2021 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-pc-linux-gnu --target=aarch64-none-elf". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://bugs.linaro.org/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out... (gdb) (gdb) target remote localhost:10000 Remote debugging using :10000 _start () at start.S:7 7 ldr x5, =default_vector_table
逆アセンブルしてみます。 qemuの出力では(unknown)となっていたはmsr/mrs命令のシステムレジスタ名が表示されています。
(gdb) disassemble Dump of assembler code for function _start: => 0x0000000040000000 <+0>: ldr x5, 0x40000058 <EL1_start+12> 0x0000000040000004 <+4>: msr vbar_el3, x5 0x0000000040000008 <+8>: msr vbar_el2, x5 0x000000004000000c <+12>: msr vbar_el1, x5 0x0000000040000010 <+16>: mrs x0, scr_el3 0x0000000040000014 <+20>: mov x1, #0x401 // #1025 0x0000000040000018 <+24>: orr x0, x0, x1 0x000000004000001c <+28>: msr scr_el3, x0 0x0000000040000020 <+32>: mov x2, #0x80000000 // #2147483648 0x0000000040000024 <+36>: msr hcr_el2, x2 0x0000000040000028 <+40>: ldr x4, 0x40000060 <EL1_start+20> 0x000000004000002c <+44>: mov sp, x4 End of assembler dump.
例外レベルを確認してみます。 これはアーキテクチャ仕様としてはPSTATEで確認するのですが、 gdb上では過去のアーキテクチャを踏襲しているためかPSTATEというレジスタ名は使用できずcpsrとしてアクセスする必要があるようです。
(gdb) info reg cpsr cpsr 0x400003cd 1073742797
この値のbit3,2の部分がELを示し、現在EL=3となっています。
EL3からEL1に遷移したところまで実行してみます。
(gdb) b EL1_start Breakpoint 1 at 0x4000004c: file start.S, line 41. (gdb) c Continuing. Breakpoint 1, EL1_start () at start.S:41 41 bl main
ここで再びcpsrを確認するとEL1になっていることがわかります。
(gdb) info reg cpsr cpsr 0x1c5 453
最後に
今回のブート処理ではAArch64ステートで実行し例外レベルをEL1にして特権プログラムの実行の準備までを行いました。 次回は割り込みを動かしてみようと思います。