ARMアセンブリプログラミングの準備

第I部では,デスクトップPCによく用いられるi386アーキテクチャの CPUの機械語・アセンブリ言語について学んできた.ここからは, i386とは少し毛色の違う,ARMアーキテクチャのCPUの機械語・アセンブリ言語について学ぶ. ARMアーキテクチャのCPUは組み込みシステムによく用いられており, 特にスマートフォンのCPUはARMアーキテクチャが一般的である. 組み込みシステムでは部品点数を減らすためにシステムオンチップ (SoC) (CPUと様々な周辺装置をひとつにまとめたチップ)を使うことが普通だが, ARMはSoC用CPUの主流のアーキテクチャであり,様々な組み込み機器に使われている.

複数のアーキテクチャの機械語を学んで比較することで, 様々なアーキテクチャが共通して持つ特徴を実感することができるだろう.さらに, 共通の特徴が分かれば,特定のアーキテクチャに固有の特徴も知ることができる.

本科目第II部・第III部では,ARMアーキテクチャのCPUを搭載したシステムであるRaspberry Pi を使う.第III部ではOSを使わずにRaspberry Piを直接制御するが, その前に第II部では,ARMアセンブラ自体を学ぶために,Raspberry Pi用Linux OS (Raspbian) の上で,第I部と同様の課題をARMアセンブラを使ってプログラムする.

第II部のためのGitリポジトリ

以降,第I部で使ったGitリポジトリ q1-i386-チーム名 とは別のリポジトリを使用する.

注意:

  • 第I部のリポジトリの中にはcloneしないこと!(リポジトリが混じってしまった場合,すべて削除して,cloneをやり直すことになる.)
  • git cloneを全員が同時に行うとサーバの応答が遅くなるので,授業中の指示に従って順番に行うこと.
$ cd ~/どこか/git                                   -- ローカルリポジトリ置き場に移動
$ ls -F                                            -- 場所が間違っていないか確認
q0-warmup-hogehoge/  q1-i386-team135/
$ git clone https://github.com/kut-info-pl2/q2-raspbian-チーム名.git
-- 略 --
$ ls -F
q0-warmup-hogehoge/  q1-i386-team135/  q2-raspbian-team135/
$ cd q2-raspbian-team135                          -- 作業コピーに移動する

Raspberry Piのログイン

第II部では,Raspberry Pi用Linux (Raspbian) の上で動くプログラムを作成する. ネットワーク経由で利用できるRaspberry Piを用意してあるので, SSHを使ってRaspberry Piにリモートログインして利用する.

第II部の実験用に5台のRaspberry Piを用意している. 学籍番号を5で割った余りの番号の機体(余りが0の場合は5号機)を使うこと (以下の対応表を参照).

Raspberry Pi機体番号 IPアドレス 学籍番号の末尾
1 172.21.43.171 1, 6
2 172.21.43.172 2, 7
3 172.21.43.173 3, 8
4 172.21.43.174 4, 9
5 172.21.43.175 5, 0
6 172.21.43.176 教員・TA用

それぞれのRaspberry Piには,予めWS室と同じユーザ名でユーザを登録している. ただし,パスワードはWS室環境と連動していない. 初期パスワードは???(授業中に言います)???になっているので,まず,ログインしてパスワードを変更する.

1.Raspberry Piにリモートログインする.

$ ssh 172.21.43.XXX

これまでログインしたことのない機械にsshでログインしようとすると,ログイン先を信頼できる相手として登録するかどうかを聞かれる.ここではyesと答えればよい.

2.パスワードを変更する.以下>で始まる行はRaspberry Pi上で行う作業であることを表す.

> passwd
(現在のパスワードを入力した後,自分で決めたパスワードを入力する)

3.exitコマンドで一度Raspberry Piからログアウトして,再度ログインしてみる.

> exit               -- Raspberry Piからログアウト
$ ssh 172.21.43.XXX  -- 再ログイン

上記がうまくいったら,再度Raspberry Piからログアウトする.

クロス開発

第I部では,プログラムの編集やビルドを行う計算機と,できたプログラムを実行する計算機が同じだった.このような開発形態をセルフ開発と言う.一方,第II部以降では,編集やビルドを行う計算機(WS室のPC)と,できたプログラムを実行する計算機(Raspberry Pi)が異なる.このような開発形態をクロス開発と言う.スマートフォンやゲーム専用機なども含め,組み込みシステム用のソフトウェアの開発は,クロス開発の形で行うことが多い(スマートフォンや家電機器の上でソースコードの編集をしたいとは誰もあまり思わないであろう.ファイルシステム,エディタ,バージョン管理システム等の環境が整った計算機の方が快適に開発を行える).

クロス開発を行うためには,クロス開発用のアセンブラやリンカ(まとめてツールチェーンと言う)が必要になる.クロス開発用のアセンブラやリンカは,それ自体は開発用計算機(WS室のPC)の上で動作するが,出力するのは実行用計算機(Rapberry Pi)で動作する機械語コードである.

WS室のLinux環境には,Raspberry Pi用のアセンブラとリンカが以下の名前でインストールされている.

arm-none-eabi-as
arm-none-eabi-ld

当たり前だが,第I部で使ってきたnasmldは,WS室のPC(i386 CPU)用の実行ファイルを作るので,作った実行ファイルは Raspberry Pi上では実行できない. 逆に,arm-none-eabi-asarm-none-eabi-ldで作った実行可能ファイルは,Raspberry Pi上でしか実行できない.実行したいときは,作った実行可能ファイルをRaspberry Piに転送して実行する必要がある.

典型的なクロス開発の手順は次のようになる.まず,端末を二つ起動し,片方でRaspberry Piにリモートログインしておく.そのうえで,次の手順を繰り返す.

  1. 手元のPCでプログラムを開発(または修正)する.
  2. 手元のPCでプログラムをビルドし実行可能形式ファイルを作る.
  3. scpコマンド (secure remote file copy) を使って実行可能形式ファイルをRaspberry Piに転送する.
  4. Raspberry Pi上で実行可能形式ファイルを実行し結果を確認する.

(つまり,プログラムの実行だけをRaspberry Pi上で行い,他の作業はすべて手元のPCで行う.従って,ファイルの転送は,手元のPCからRaspberry Piへの一方向だけでよい.)

最初のプログラム

演習2.1-1 chap1ディレクトリ内で以下のアセンブリ言語プログラムを打鍵入力し,hello.sというファイル名で保存しなさい。その後,下記の手順でアセンブルし,実行しなさい。

        .section .text
        .global _start
_start:
        @ write
        mov   r7, #4            @ writeのサービス番号
        mov   r0, #1            @ 標準出力
        ldr   r1, =msg          @ 文字列の開始番地
        ldr   r2, =msglen       @ 文字列の長さ
        swi   #0

        @ exit
        mov   r7, #1            @ exitのサービス番号
        mov   r0, #0            @ 終了コード
        swi   #0

        .section .data
msg:
        .ascii "I'm fine.\n"
        .equ msglen, . - msg

アセンブルと実行の手順を以下に示す.scpコマンドでは,Raspberry PiのIPアドレスの後にコロン (:) を忘れないように注意(コロンがないとファイル名だと認識してしまい,Raspberry Piに転送しない).

$ arm-none-eabi-as hello.s -o hello.o
$ arm-none-eabi-ld -T ../raspbian.lds -m armelf hello.o -o hello
$ scp hello 172.21.43.XXX:      -- 末尾の : を忘れずに

Raspberry Piにリモートログインした端末で,以下を実行する.

> ./hello
I'm fine.

実行が確認できたら実行可能ファイルを削除しておくと,その後の間違い(転送に失敗したのに気づかないなど)を防止できる.

> rm hello      -- ファイルhelloを削除

重要: 上記のリンカオプション -T ../raspbian.lds は,リポジトリのルートディレクトリにある raspbian.lds をリンカに読み込ませるためのものである. chap1ディレクトリ内でビルドを行うという前提なので,親ディレクトリを表す ../ を付けて ../raspbian.lds と書いている. 第II部ではこの後,chap2, chap3 などのサブディレクトリ内でプログラム作成とビルドを行うが,raspbian.lds の相対パスは ../raspbian.lds で変わらないはずである. 独自に別のディレクトリで作業する場合などは適宜書き換えること.

(参考:raspbian.ldsはリンカの設定ファイル(リンカスクリプト)である.arm-none-eabi-ldは本来第III部で行うベアメタルプログラミング用であり,それをRaspbian用に流用するために,raspbian.ldsで設定変更を行っている.)


上記のプログラムは,演習1.2-1と同じことをするプログラムを,ARM用のアセンブリ言語で記述したものである.見比べて違いを探してみよう.

  • コメントを表す文字は,i386アセンブラでは;だったが,ARMアセンブラでは@を使う.
  • レジスタの名前が違う.i386のレジスタはeaxebxと呼んでいたが,ARMのレジスタはr0r1のように r に数字を付けた名前で呼ばれる.
  • 4 や 1 のような即値の整数は,i386アセンブラではそのまま書いていたが,ARMアセンブラでは#を付けて表す.
  • msgmsglenには#ではなく=を付ける.よく見ると命令もmovではなくldrになっている.実はこれも定数をレジスタに代入(ロード)する命令である.msgmsglen もアセンブル時に具体的な数値に置換されるので,定数をレジスタにロードすることには変わりない.なぜ定数のロードにmovを使ったりldrを使ったりするかは後々分かるだろう.
  • int 0x80命令がswi #0命令になっている.これは単なる命令名の違いと,OSの仕様の違いによるものである.i386用のLinuxでは0x80番のソフトウェア割込みがシステムコールになっているが,ARM用のLinuxでは0番のソフトウェア割込みがシステムコールになっている.
  • "I'm fine."の後の改行コードの書き方が違う.これはアセンブラの細かな仕様の違いである. .equの構文も違う(.equの右に,定義するラベルとその値をコンマで区切って書く). 現在番地を表す変数は $ ではなく . である.

Makefile

クロスビルド用のMakefileを作る.クロスビルドといっても「アセンブルしてリンクする」という手順はセルフビルドと変わらない.使うアセンブラやリンカが異なるだけである.セルフビルド用のMakefileでマクロを使って,アセンブラやリンカをASLDという名前で定義しているのであれば,そこを書き換えるだけでよい. (nasmと違い,arm-none-eabi-as-o オプションで出力ファイル名を指定する必要があるので注意.)

AS = arm-none-eabi-as
LD = arm-none-eabi-ld
LDFLAGS = -T ../raspbian.lds -m armelf

演習 2.1-2 リポジトリのサブディレクトリchap1の中に,演習2.1-1で作ったプログラムと,それをビルドするためのMakefileを作りなさい.ただし,

  • 演習2.1-1のプログラムのソースファイル名はhello.sとする.
  • 引数なしでmakeを実行すると,演習2.1-1で作成したプログラムの実行可能ファイルが生成されるようにすること.実行可能ファイル名はhelloにすること.
  • make cleanを実行すると,実行可能ファイルや .o ファイル等が削除されるようにすること.

参考:makeによる実行ファイル転送

第I部7-a節の「疑似ターゲット」の項で,Makefileを使ってファイルの生成以外の作業も自動化できることを説明した. Raspberry Piへのファイルの転送もMakefileを使うと便利である.

悪い例:実行ファイル生成と同時に転送

まず,あまり良くない方法から示す.

例えば実行ファイルhelloを作る課題で, helloを作るルールの中でRaspberry Piに実行可能ファイルを転送するところまで含めて書いてしまえば良いと思うかも知れない.

# 悪い例
hello: hello.o
        $(LD) $(LDFLAGS) $+ -o $@       # hello を作る
        scp $@ 172.21.43.XXX:           # hello を 172.21.43.XXX に転送

しかしこれでは,実行ファイルを作りたいだけのときもscpコマンドが実行されてしまい,パスワードを問い合わせられたりして不便である.

良い例:疑似ターゲットを使って実行ファイルの生成と区別

実行ファイルを作るルールとそれを転送するルールの2つに分離すれば,この問題は解決する.

# 疑似ターゲットであることを明示
.PHONY: copy-hello

hello: hello.o
        $(LD) $(LDFLAGS) $+ -o $@       # hello を作る

copy-hello: hello
        scp $< 172.21.43.XXX:           # hello を 172.21.43.XXX に転送

helloをターゲットとする規則は単にhelloを作るだけになっている.copy-helloがhelloをRaspberry Piに転送する疑似ターゲットである. copy-helloの「ソースファイル」としてhelloを指定しているので,make copy-helloを実行すると,まずhelloを生成してから(あるいはhelloをビルドし直してから),転送が行われる.

$ make copy-hello    -- make helloを別途実行する必要はない

results matching ""

    No results matching ""