本記事は ゼロからマイコンでLチカをするまで というシリーズの第3弾です。
今回は前回書いたリンカスクリプトを使ってコンパイルを行います。具体的には、割り込みベクタが前回書いたリンカスクリプト通りにきちんと配置されるのか確認をしていきます。
なお、gcc
のコンパイルオプションについては次回説明します。
割り込みベクタの配置確認
この間新たに作成したリンカスクリプトによって、きちんと 0x0800 0004
に Reset_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_vectors
が 0x0800 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チカするので乞うご期待!