ARMv8のベアメタル開発環境構築②

前回、詳細確認を飛ばした部分について理解を深めたい。

# Hello worldプログラム
コード自体はすべて、以下のサイトのものを使用させてもらった。
https://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/

## test.c
```c:test.c
volatile unsigned int * const UART0DR = (unsigned int *)0x101f1000;
 
void print_uart0(const char *s) {
 while(*s != '\0') { /* Loop until end of string */
 *UART0DR = (unsigned int)(*s); /* Transmit char */
 s++; /* Next char */
 }
}
 
void c_entry() {
 print_uart0("Hello world!\n");
}
```

実際のHello worldを出力する部分は通常のCで書かれている。main関数に相当するものがc_entryで、print_uart0という関数では受け取った文字のアドレスを"UART0DR"に引き渡している。

### UART0DRはなにものか
今回使用しているボードはversatilepb(RealView Platform Baseboard??)であった。こちらのメモリマップを確認すると、0x101f1000はUART0 Interfaceとなっている。UART0,1,2が搭載されているが、いずれもPL011。
https://developer.arm.com/documentation/dui0224/i/programmer-s-reference/memory-map?lang=en

PL011のメモリマップによると、0番地はUARTDR(Data Register)となっている。
https://developer.arm.com/documentation/ddi0183/g/programmers-model/summary-of-registers?lang=en

UARTDRの詳細を読むと、[7:0]によって送信/受信データのやり取りを行うとのこと。特に送信(armによるprint)はただ書き込むだけで、スタートビットとストップビットを付加した上で送信してくれるとのこと。したがって、print_uart関数は非常にシンプルな記述になっている。

>For words to be transmitted:
>
>if the FIFOs are enabled, data written to this location is pushed onto the transmit FIFO
if the FIFOs are not enabled, data is stored in the transmitter holding register (the bottom word of the transmit FIFO).
The write operation initiates transmission from the UART. The data is prefixed with a start bit, appended with the appropriate parity bit (if parity is enabled), and a stop bit. The resultant word is then transmitted.

## start.s
続いて、main関数に相当するc_entry関数をだれが呼び出しているのかを確認する。まず今回使用しているelfファイルのヘッダ情報を確認してみる。

```sh
$ readelf -h test.elf
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x10000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          67312 (bytes into file)
  Flags:                             0x5000200, Version5 EABI, soft-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         15
  Section header string table index: 14
```
この中で```Entry point address: 0x10000```という記述がある。最初に実行すべき命令がelfファイルの0x10000番地にあることを示しているらしい。実際にtest.elfをバイナリエディタで見てみると、0番地にヘッダ情報があり、しばらく0データが続いた後に0x10000番地からゼロでないデータが書き込まれているようだ。
エントリーポイントを0x10000番地に指定するのはリンカスクリプトの役割で、これは後で見ることにする。ひとまず0x10000番地に置かれた命令が、先ほどのc_entry()を呼び出すところを確認する。

```c:startup.s
.global _Reset
_Reset:
 LDR sp, =stack_top
 BL c_entry
 B .
````
- _Resetというラベルをグローバル宣言。これによりほかのファイルからも参照可能になる
- 3-5行目のLDR,BL,Bからなる命令群に_Resetというラベルを付けている
- sp(stack pointer)レジスタの初期化。spにスタック領域の先頭アドレス(stack_top)をロードしている
- サブルーチン"c_entry"へ飛ぶ
- 無限ループで終了待ち


## test.ld
最後にリンカスクリプトを確認する。

```c:test.ld
ENTRY(_Reset)
SECTIONS
{
 . = 0x10000;
 .startup : { startup.o(.text) }
 .text : { *(.text) }
 .data : { *(.data) }
 .bss : { *(.bss COMMON) }
 . = ALIGN(8);
 . = . + 0x1000; /* 4kB of stack memory */
 stack_top = .;
}
```

- エントリーポイントとして_Resetを指定
- SECTIONS節でメモリ配置を定義
- ドットはロケーションカウンタ(カレントアドレス)を意味し、ロケーションカウンタ0x10000を代入
    - elfをバイナリエディタで見たときに、0x10000番地から非ゼロデータが書き込まれていたことに対応している
    - 0x10000番地からstartup,text,data,bssセクションを積み上げていく
- ```.startup : { startup.o(.text) }```は[^1]、startupセクション変数に対して、startup.oのtext領域を割り当て
- ```.text : { *(.text) }```および```.data : { *(.data) }```は、text,dataセクションにすべてのオブジェクトファイルのtext,data領域をそれぞれ割り当て
- ```.bss : { *(.bss COMMON) }```はbssセクションにすべてのオブジェクトファイルのbss領域および一般的なシンボルを割当て
- .bssセクション割当て後、```. = ALIGN(8);```でロケーションカウンタを次の8byte境界に乗せて、
- ```. = . + 0x1000;```で0x1000バイト=4KB加算したアドレスをstack_topとしている
    - スタック領域はアドレスを減算しながら積むため、スタックの先頭は0x1000でよい


[^1]: 元の記事では、```.startup . : { startup.o(.text) }```になっているが、.startupと:の間のドットの意味が良くわからない。ほかのリンカスクリプトでもこのような記述を見かけないのと、これを削除しても動作しているのでとりあえず無視しておく。