UNIX 6thに足し算システムコールを追加してみた
はじめに
先日「ぐだ生」(@magoroku15さんのUstream. この間までunix v7を取り上げていた)を見ていたところ「新人には足し算システムコールをとりあえず作らせる」という旨の発言がありました。
それを聞いて「よっしゃ、やってみるか」と思ったのが今回の始まりです。
参考リンク
- http://d.hatena.ne.jp/oraccha/20101101/1288582382
- こちらを参考にsimh+PDP11+UNIX v6環境を構築しました
- http://toyoshim.blogspot.com/2010/12/unix-6th-edition.html
- http://d.hatena.ne.jp/takahirox/20110220/1298201314
- 以前書いたエントリ。システムコールハンドラへの引数の渡り方などが参考になるかも
システムコールを追加
/usr/sys/ken/sysent.cの編集
# chdir /usr/sys/ken # ed sysent.c 2131 62 0, &nosys, /* 49 = x */ s/nosys/myadd/ s/x/myadd/ s/0/2/ . 2, &myadd, /* 49 = myadd */ w 2135 q
system callのテーブルにmyadd(今回追加する足し算システムコール)が追加できました(2910行目参照)。sys 61(10進数で49)が実行されるとmyadd( )という名前の関数がシステムコールハンドラとして呼び出されます。
引数は2個取るようにしました。myadd( )でu.u_arg[ 0 ], u.u_arg[ 1 ]で引数を扱えます。
/usr/sys/ken/myadd.cの追加
システムコールハンドラmyadd( )を追加します。sys 61を実行するとこのmyadd( )が呼び出されます。
# chdir /usr/sys/ken # ed myadd.c ? a \#include "../param.h" \#include "../user.h" \#include "../reg.h" myadd( ) { int val1, val2 ; int result ; val1 = u.u_arg[ 0 ] ; val2 = u.u_arg[ 1 ] ; result = val1 + val2 ; u.u_ar0[ R0 ] = result ; } . w 212 q
引数を受け取り、足し算の結果をユーザレジスタのR0に格納しているだけです。
/usr/sys/runの編集&コンパイル
先ほど作成したmyadd( )がライブラリに追加されるようにrun(コンパイルを行うスクリプト)を編集します。新たにmyadd.cというファイルを作成するのではなく、既存の*.cファイルにmyadd( )を追加する場合はrunの編集は必要ないと思います。
# chdir /usr/sys # ed run 900 3 ar r ../lib1 s/1/1 *.o/p ar r ../lib1 *.o 8 ar r ../lib2 s/2/2 *.o/p ar r ../lib2 *.o w 908 q
編集が終わったらコンパイルを行います。
# chdir /usr/sys # sh run
コンパイルが無事終わったらCtrl-E -> quitで抜けて再ログインします。
コンパイルまわりは参考リンクで挙げた某語録を多分に参考にしています。ありがとうございます。
libcにシステムコールを呼び出す手続きを追加する
システムコールを追加しただけでは、cのソースから呼べない(?)のでlibcにmyaddシステムコールを呼ぶ手続きを追加します。
/usr/source/s4, s5にlibcのアセンブラコードが置かれています。既存のものを参考にmyaddシステムコールを呼ぶアセンブラコードを追加します。
# chdir /usr/source/s5 # ed myadd.s ? a .globl _myadd _myadd: mov 2(sp), 0f mov 4(sp), 0f+2 mov r5, -(sp) mov sp, r5 sys 61; 0:..; .. mov (sp)+, r5 rts pc . w 131 q
引数をsys命令の後ろに置いてからsys 61(=myadd)を実行しています。本当はsys myaddと書きたかったのですが……(詳細は後述)
myadd.sを作成したらrunファイルを編集して、myadd.sをアセンブルし、/lib/libc.aにmyadd.oを追加するようにします。
# chdir /usr/source/s5 # ed run 1081 36 as write.s; mv a.out write.o a as myadd.s; mv a.out myadd.o . 40 ar r /lib/libc.a s/.a/.a *.o/p ar r /lib/libc.a *.o w 1114 q
# chdir /usr/source/s5 # sh run
無事runの実行が完了したら、libcにmyadd.oが追加されたことを確認しましょう。
# ar t /lib/libc.a (省略) cerror.o myadd.o
以上でlibcへの追加は完了です。
実行確認
# chdir / # mkdir work # chdir work # ed hoge.c ? a int main( ) { int result ; result = myadd( 1, 2 ) ; printf( "1 + 2 = %d\n", result ) ; result = myadd( 3, 4 ) ; printf( "3 + 4 = %d\n", result ) ; return 0 ; } . w 176 q
テストコードhoge.cを作成し終わったらコンパイルして実行します。
# cc hoge.c # a.out 1 + 2 = 3 3 + 4 = 7
成功です! 無事足し算が行われていることが確認できました。
せっかくなのでアセンブラコードを吐いて眺めてみます。
# cc -S hoge.c # cat hoge.s (省略) mov $2,(sp) mov $1,-(sp) jsr pc,*$_myadd tst (sp)+ mov r0,-10(r5) mov -10(r5),(sp) mov $L2,-(sp) jsr pc,*$_printf (省略)
引数(1, 2)をスタックに積んでからjsrで_myadd(/lib/libc.a)に飛んでいるのがわかります。関数を呼ぶ前に引数をスタックに積むのはcの(?)仕様です。_myaddから帰ってくると、結果が格納されているr0とL2(printfで出力する文字列)をスタックに積んでからjsrで_printfに飛んでいます。やはり正しく足し算システムコールが呼び出されているようです。
(余談)
当初はシステムコールを呼び出すcの関数、例えばwaitシステムコールを呼ぶwait( )、からsys waitというアセンブラコードが吐かれるのかと思っていたのですが、jsrでlibcの_waitに飛んでいるだけでした。これは今でも変わらないそうです。@oracchaさんに教えていただきました。
アセンブラにsys myaddを解釈させようとして失敗した
sys myaddと書いてアセンブルできるように、myaddシンボルをアセンブラに解釈させたかったのですが失敗しました。
以前アセンブラを追ったときに、as19.s, as29.sがシステムコールのテーブルを持っているというのを見つけていたので、見よう見まねで以下のように変更してみました。
# chdir /usr/source/as # ed as19.s 6705 109 <signal\0\0>; 01;0000060 a <myadd\0\0\0>; 01;0000061 . w 6740 q # ed as29.s 3903 76 01;0000060 /signal a 01;0000061 /myadd . w 3921 q
これでrunを実行するとエラー。原因よくわかっていません。今度asを真面目に追ってみるかも。(.dataセグメントが溢れた? as19.s, as29.sには全てのsystem call symbolが入っていないし。溢れないように厳選している?)