SNSへはこちら

ゼロからマイコンでLチカをするまで(3) - アセンブリの説明・バイナリ生成

本記事は ゼロからマイコンでLチカをするまで というシリーズの第3弾です。

今回は前回書いたリンカスクリプトを使ってコンパイルを行います。具体的には、割り込みベクタが前回書いたリンカスクリプト通りにきちんと配置されるのか確認をしていきます。

なお、gcc のコンパイルオプションについては次回説明します

割り込みベクタの配置確認

この間新たに作成したリンカスクリプトによって、きちんと 0x0800 0004Reset_Handler へのアドレスが配置されるかを見ていきます。
イキナリですが、以下のようなアセンブリソースを書いていきますね。src/start.s とします。

.section .text
.global Reset_Handler
.type Reset_Handler,%function

Reset_Handler:
    b .


.section .isr_vector,"a",%progbits
.global isr_vectors
isr_vectors:
    .word 0x00000000
    .word Reset_Handler

ん?何だこれ!?

ということでもちろん解説しますよ。
まぁまぁ、まずはコンパイルしてみましょうよ。取り敢えず実行ファイルを作ってしまいましょう。

$ arm-none-eabi-gcc -nostartfiles -mthumb -mcpu=cortex-m4 -T  linker.ld src/start.s

できたファイルの解析

さぁ、a.out ができましたかね。実行してみますか?いいえ、アーキテクチャが違うので実行できないはずです。

$ ./a.out
zsh: exec format error: ./a.out

どのようなファイルかというのは、file コマンドで確認できます。

$ file a.out
a.out: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped

なになに、32bit の実行可能ファイルらしい。そして ARM の文字がありますね。ARM マイコン用のものというわけです。
PC 上でやりたいことは、isr_vectors0x0800 0000 に配置されているかどうかです。それは objdump で可能になります。このツールは各アーキテクチャにおいて binutils に入っているもので、このマイコンの場合は arm-none-eabi-objdump となります。-D を付けると配置されたシンボル全てについて調べることができますので、見てみます。

$ arm-none-eabi-objdump -D a.out

a.out:     file format elf32-littlearm


Disassembly of section .isr_vector:

08000000 <isr_vectors>:
 8000000:   00000000    andeq   r0, r0, r0
 8000004:   08000009    stmdaeq r0, {r0, r3}

Disassembly of section .text:

08000008 <Reset_Handler>:
 8000008:   e7fe        b.n 8000008 <Reset_Handler>

Disassembly of section .ARM.attributes:

00000000 <.ARM.attributes>:
   0:   00001b41    andeq   r1, r0, r1, asr #22
   4:   61656100    cmnvs   r5, r0, lsl #2
   8:   01006962    tsteq   r0, r2, ror #18
   c:   00000011    andeq   r0, r0, r1, lsl r0
  10:   2d453705    stclcs  7, cr3, [r5, #-20]  ; 0xffffffec
  14:   0d06004d    stceq   0, cr0, [r6, #-308] ; 0xfffffecc
  18:   02094d07    andeq   r4, r9, #448    ; 0x1c0

いろいろ出てきますが、08000000 <isr_vectors>: 付近を見てください。アドレス 8000004 (16進数で書かれている) に 08000008 があると思います。そしてアドレス 08000008 には Reset_Handler があります!確かに望み通り配置できましたね。やりました。ところで isr_vectors の先頭 00000000 ってなんなのでしょう??
答えはリファレンスマニュアルに書いてあります。簡単に言うと、

  • 0x0000 0000 にはスタックポインタの初期値を配置する(次回以降説明)
  • 0x0000 0004 にはリセット時に実行する関数アドレス + 1 を配置する(前回説明しました)

であればいいです。ちなみに、リセット時に実行する関数アドレス + 1 ですが、これはコンパイラ(正確にはアセンブラ)がいい感じにやってくれるのでコードを書く上では少し気にしてやるだけでいいです。上のアセンブリコードにはどこにも + 1 って書いてないですよね。
どう気にするかはこの先に書いてありますので、順に読んでいってください。

アセンブリファイルの説明

上のコードについての説明です。以下に再掲します。

.section .text
.global Reset_Handler
.type Reset_Handler,%function

Reset_Handler:
    b .


.section .isr_vector,"a",%progbits
.global isr_vectors
isr_vectors:
    .word 0x00000000
    .word Reset_Handler

冒頭の解説

まずは .section .text です。これは「.text セクションに以下を配置しろよ」という命令です。ドットで始めるものは擬似命令あるいはディレクティブというもので、アセンブル時に実行されます。マイコン動作には影響しません。

続いて .global ですが、これは「後置したシンボルを他のファイルから見えるようにする」という意味です。少し分かりづらいですかね?だったら、以下のように考えるといいです。
.global しない関数は C言語で言う static 関数、.global した関数は普通の関数です。static な関数は他のファイルから見えませんよね?
そういうことです。なんかCコンパイラの気持ちも少しわかったような気がしませんか?
これ地味にハマりポイントでして、何も知らずにこの .global を忘れてコンパイルすると「Reset_Handler ねぇんだけど」と怒られてしまうのです。

.type Reset_Handler,%function は「後置したシンボルを関数とみなす」という意味です。他のプロセッサでしたらこのディレクティブは意味がありませんが、ARM の場合は意味があります。
ARM と一口に言っても大きく分けて2種類の命令体型があります。まず1つは 32bit 命令で構成される ARM 命令。主にこれは汎用 CPU で使用されます。もう1つは Thumb 命令。これは「ARM 命令は1命令 32bit で長いよねー、ROM の無駄遣いだよねー」という見解から生まれたもので(多分)、ARM Cortex-M に代表される組込み用の CPU で使用される、16bit を1単位とした命令セットです。STM32 の場合は Thumb 命令です。
これらの命令モードは関数ジャンプ時に切り替えることができます。ARM 命令をサポートする CPU では、関数ジャンプ時に「ここは ARM で」「あそこは Thumb で」などとできる、ということです。一方で Thumb 命令のみをサポートする組込み用 CPU では ARM 命令を解釈・実行することはできません
モード切替は関数アドレスの下位1bitで見るという事になっています。命令は(というか 16bit 値や 32bit 値は)アドレスの下位1ビットが0でなけれはならないということをうまく利用しています。そして Thumb モードでは下位1bit が 1 である必要があるのです。これが関数アドレス + 1 と書いた理由です。
説明が長くなりましたが、要するに関数には全て .type Hoge,%function しておけば万事オーケーということです。

Reset_Handlerの解説

さて、関数 Reset_Handler の説明に入るわけですが、書いた命令はただ1つ、b . です。b というのは分岐(branch)命令で、後置したシンボルのアドレスに無条件でジャンプするという命令。ドットは命令のアドレスを指すので、要はただの中身のない無限ループです。兵庫県警に捕まりそうですが、大丈夫かなぁ()

isr_vectorsの解説

下の isr_vectors.word によって 32bit値を置いています。これはいいですよね。

問題は .section .isr_vector,"a",%progbits です。これはなんだ。ARM の記事によると.text, .rodata, .data, .bss 以外のセクションには後ろにごちゃごちゃ必要とのことです。"a"配置可能(allocatable)を示し、通常は全て必要です。これ以外に "ax" と書くことがあり、これは配置可能に実行可能(executable)という属性を追加したものです。x はプログラムコードを書いたときに付けます。
そして %progbits は「データがあるよ」ということを意味します。詳しくはこちらをどうぞ

つまらないとおもうんですよ、今回は。なので上の説明を踏まえていろいろ遊んでみましょうか。

変なセクションを作ってみる

じゃあ .section 使ってみたいよ、と思うでしょう。思いますよね?(念押し)
なら作ってしまえばいいのです。勉強になるしね。先程のコードの下に、適当に以下を挿入してみます(やや汚いですが)。

.section unko
Unko:
    .word 1

コンパイルして objdump すると、こんな表記が見られますw

Disassembly of section unko:

00000000 <Unko>:
   0:   00000001    andeq   r0, r0, r1

もちろん、リンカスクリプトで unko なんてセクションは記述していないので、このアドレスは不正です。

命令を追加してみる

Reset_Handler に命令を追加してみましょう。例えば「何もしない」という命令の nop にしましょうか。

Reset_Handler:
    nop @ ついか!
    bl .

するとこんな感じになります。

08000008 <Reset_Handler>:
 8000008:   46c0        nop         ; (mov r8, r8)
 800000a:   e7fe        b.n 800000a <Reset_Handler+0x2>

今回でLチカしたかったのですが、どうも説明が長くなってしまってまたも何もできませんでした!すみません!!
次回は絶対にLチカするので乞うご期待!