読者です 読者をやめる 読者になる 読者になる

【アセンブリ】システムコールsyscallの使い方【MIPS】

アセンブリ

システムコール(syscall)ってなに

SPIMでオペレーティングシステム的なサービスを実行するための命令。

syscallを使う手順

サービスの要求の流れ

  1. レジスタ$v0に使いたいサービスのシステムコールコードを格納
  2. 引数をレジスタ$a0から$a3浮動小数点数の値は$f12)にロード
  3. 値を返すシステムコールは結果をレジスタ$v0浮動小数点数の値の場合は$f0)に収める
  4. syscallで実行!

*システムコールコードはP.781を参照

やってみる

Patterson&Hennessyにあるサンプルプログラムを改変しながら読み解いてみる

 .data
str:
    .asciiz     "the answer = "
    .text
    li          $v0, 4      # 手順1:print_stringのシステムコールコードは4
    la          $a0, str    # 手順2:asciizで保存したプリントする文字列のアドレスを$a0に格納
    syscall                 # 手順4:文字列をプリント(サービス実行!)
    
    li          $v0, 1      # 手順1:print_intのシステムコールコードは1
    li          $a0, 5      # 手順2:プリントする整数5を$a0に格納
    syscall                 # 手順4:整数をプリント

これで単純に実行したら

Instruction references undefined symbol at 0x00400014

ってエラーが出た。ステップ実行で確認してみる。

どうやらjalでmainがないよって言われてる。jalなんてこのプログラム内にはないけど。

これはアセンブリファイルの内容に依らず呼ばれるものなのかもしれない。ということでmainを追加して動くプログラムにしようと試みる。

 .data
str:
    .asciiz     "the answer = "
    .text
main:                       # ここを追加
    li          $v0, 4      # 手順1:print_stringのシステムコールコードは4
    la          $a0, str    # 手順2:asciizで保存したプリントする文字列のアドレスを$a0に格納
    syscall                 # 手順4:文字列をプリント(サービス実行!)
    
    li          $v0, 1      # 手順1:print_intのシステムコールコードは1
    li          $a0, 5      # 手順2:プリントする整数5を$a0に格納
    syscall                 # 手順4:整数をプリント

これでなんとか動いた。でも文字が出力された後に以下のエラーが出てる…。

Attempt to execute non-instruction at 0x0040003c

なんなんだ。

必殺ステップ実行。

なんか抜け出す的なそういう処理が最後に必要なのかなとか思い勘で下記のように最後の行にj $raを追加

 .data
str:
    .asciiz     "the answer = "
    .text
main:
    li          $v0, 4      # 手順1:print_stringのシステムコールコードは4
    la          $a0, str    # 手順2:asciizで保存したプリントする文字列のアドレスを$a0に格納
    syscall                 # 手順4:文字列をプリント(サービス実行!)
    
    li          $v0, 1      # 手順1:print_intのシステムコールコードは1
    li          $a0, 5      # 手順2:プリントする整数5を$a0に格納
    syscall                 # 手順4:整数をプリント
    j           $ra         # ここを追加

するとエラーなく通るようになった!!!やった!!!!

どうやら、main関数(サブルーチン、手続きともいう)から元に復帰する必要があるのでj $ra$raの値を返してやってmainを抜け出す必要があるらしい。

Cでもmain関数が正常に終了したらreturn 1;とかで値を返してやらないといけないけど、これと同じようなする。

じゃあ なんで返す値がレジスタ$raなのか ってところが疑問になる。

調べた限り jal命令にはでは$raは関数が呼ばれた時点で戻ってくるべきアドレスを自動で代入してくれる便利な機能があるらしい。

上で一度エラーでひっかかったときjal命令が呼ばれていることを確認したけど、これはソースコードにはなかった。まとめると、、

  • jal mainって命令はどのプログラムでも最初に自動的に実行される
  • そしてjalが呼ばれた瞬間に$raには戻るべきアドレス(返すべき値)が自動的に代入される。

みたいなことらしい。よって、$raはうかつに自分でいじっちゃいけないよう。

ということで

MIPSアセンブラによるシステムコールの使い方でした。

アセンブラ深い。低レイヤだからって学ぶのが簡単ってわけはなく、高いレイヤになるほどそのシステムの複雑さはうまく抽象化されてラッピングされてるんだなぁとアセンブラの勉強を進めるたびに思う。。。

githubリポジトリはこちら。今回使用したプログラムはex-syscall.sです。

GitHub - totzyuta/system-programming: [Assembly] Programs of System Programming class.

ということでprintf関数をスクラッチから実装することを目標に学習を進めます。

totutotu.hatenablog.com

というか、アセンブリシンタックスハイライトできないかなー