あせんぶら きほんの き de 7M4VKQ 0、おはなしのきほん  次のことはアセンブラ(やその他の言語)の話をするときに欠かせない基礎知識です。 きちっと理解しておきましょう。 ・2進数、8進数、10進数、16進数  まず始めに断っておきますが、コンピューターは「0と1しか」扱えません。ただ、人間の数を表す方法が違うだけです。それから、数字のあらわしかたは、処理系(プログラミングする環境)によって全然違うことがあるので注意しましょう。  10進数は、そのまま書き表します。例えば、「1111」とあったら、これは、「せんひゃくじゅういち」を表します。(Mac等では10進数であることを強調するために、頭に#をつけることがあります。)  2進数は後ろにBをつけて表します。たとえば「1111B」は10進数でいう「15」を表します。2進数の1桁のことを1ビットといいます。  8進数はうしろにOとかQをつけて表します。たとえば「1111Q」は10進数でいう「585」を表します。  16進数は後ろにHをつけて表します。たとえば「1111H」というのは、10進数でいう「4369」を表します。プログラム中では、先頭に英語の桁が来たときは「0ABCDH」のようにあたまに0を補うことが多いです。(特に断り無く16進数が書いてあることもあります)  なぜ、16進数が使われるのでしょうか?それは、2進数と仲がいいからです。  例えば、「101110110010010B」という2進数を10進数に変換するよりは、16進数にするほうが楽だからです。やり方としては、2進数を4桁ずつに区切ってそれぞれが一桁の16進数になるようにしてやればよいのです。つまり、    0101 1101 1001 0010Bが    5    D    9    2 H  のように。 ・補数  ある基準となる数から任意のかずNを引いたときに得られるかずを「補数」といいます。  コンピューターはある数に対する2の補数をある数のマイナスとして考えています。  たとえば、0101B(=5)の2の補数は、1011Bとなります。なぜ補数を使うかというと、引き算を足し算で代用できる(足し算と引き算を区別しなくてもよい)からです。たとえば、1111B−0101Bの計算は、1111B+1011Bと考えることができるわけです。そして、1111B+1101Bは11010Bとなり、この場合計算の結果桁数が増えたので、けっかはプラスで、あたまの1をとって+1010Bということになります。  このような表しかたをする場合、一番先頭のビットが1のときにマイナスとして考えます。 ・型  先ほど、0101Bの2の補数1011Bは、−(0101B)のいみといいましたが、1101Bは素直に読むと13とも読めてしまいます。一つの数に二つの意味があるわけです。そこで必要なのが、「型」の考え方です。  型にはいろいろな種類がありますが、ひとまず覚えておくべき型は次のものです。  (64ビットなど高級なマシンでは定義が違うこともあるので注意)  signed char −127から128までの整数が入っています  (unsigned) char 0から255までの整数が入っています。  (signed) int −32,767から32,768までの整数が入っています。 unsigned int 0から65,535までの整数が入っています。 (signed) long −2,097,151から2,097,152までの整数が入っています。 unsigned long 0から4,194,304までの整数が入っています。  そして、これらの型を宣言してやることで、コンパイラに、「ここにはいっているのはふごうはかんがえないよ」とか教えてあげることができるわけです。(ここらへんはCでしっかりやるはず) ・メモリと数値  コンピュータは、計算するデータや結果をしまうために、「メモリー」というものを持っています。  メモリーとは、細かく区切られた箱がたくさん並んでいるようなものです。  一つの箱にはたいてい8ビット(=1バイト)の情報が入っています。たとえば、 アドレス 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 データ  11 28 36 1D 62 77 7D 88 37 46 11 88 24 04 55 AA 18 1A 00 7A 23 6C  一つ一つの箱には、「アドレス」という番号がついています。  例えば、アドレス03番地の8ビットのデータは、1Dというように、アドレスを使えばデータを取り出すことができます。  また[メモリーの番地]と書くことで、書かれているメモリーの番地のデータを表すことができます。 ・ラベル  プログラミング言語では、メモリーのある番地に名前をつけることができます。これを「ラベル」といいます。  たとえば、上の例で、07ばんちに”とくてん”というラベルをつけたとすると、プログラミング言語のなかでは、「”とくてん”にはいっている8びっとの値はいくら?」とするだけで、07番地に入っている値を知ることができます。  ラベルは、プログラミング言語の方が、勝手に具体的なアドレスに変換してくれます。よって、プログラムを作る人は、プログラムやデータを入れる具体的なアドレスを気にすること無くプログラムを作ることができるわけです。 ・リトルエンディアンとビックエンディアン  メモリーの内容がうえのようだったとすると、すると、例えばアドレス09にある32ビットのデータをとり出そうとすると、CPUによって違いが現れます。 x86系等−>24881146 モトローラなどその他のCPU−>46118824  x86とそうでないでは、順番が逆になっていることに気付かれるとおもいます。  そして、x86のように逆から取り出していく方式を「リトルエンディアン」または、バックワードストレージといい、先頭から取り出していく方式を「ビックエンディアン」といいます。 ・ポインタ  ポインタというのは、メモリ上のある位置を示すものです。  ポインタを使うのは、プログラム中で具体的な場所を決定できないときです。  たとえば「平野さん」のいえに、ラブレターを送らせる場合を考えましょう。  ポインターを使わないで、「100番地のあるいえに、このラブレターを送って」といったとします。  これで「平野さん」が100番地に住んでいてくれれば問題ないのですが。もし、「平野さん」が別の番地に引っ越してしまったとすると、たいへんなことになります。  ましてや別の夫婦が100番地に引っ越していたとすると、送られて来たラブレターによって家庭の崩壊がおきるかも知れません。  そこで、つねに「平野さん」の住所を示すポインター「[平野]」を作り、「平野さん」には、引っ越すときにはこのポインターを新しい番地に書き替えてもらうことにします。  そして、「[平野]にあるいえに、このラブレターを送って」といえば、家庭の崩壊などの問題もなくなるわけです。 ・ハンドル  ハンドルというのは、ポインタのポインタです。ポインタに番号をつけたと考えることもできるでしょう  ハンドルの代表的な例として、「ファイルハンドル」があります。  MS−DOSを例にとると、たとえば、「J:\JR1ZQL\HIRANO.TXTというファイルを使わせて」とDOSにたいして語りかけたとすると、DOSが「よっしゃ、ファイルハンドルは72番だ」などといってきます。そして、DOSがファイルハンドル72番にあたるところにファイルを読んでいる(ディスク上の)場所などを書き込んで、こんどからは、「72番のファイルの中身を100バイトここに書き込んで」とたのむだけで、ファイルの中身を読んだりできるようになります。  ハンドルを使わなければ、このようなシステムは作れないのは、理解できると思います。さもなくば、DOSをつかわず直接には何処にあるかわからないファイルを探すことになってしまいます。 ・セグメント  セグメントとは、メモリーをいくつかにわけたもののことです。  たとえば、物理アドレス(メモリー上の本当のアドレス)30000H〜40000Hを8Hという番号のセグメントにしたとします。そうすると、物理アドレスでいう30010Hは8:00010Hと表すことができます、こういうあらわしかたを「セグメントアドレス」といい、00010Hの部分を、「オフセットアドレス」といいます。  なぜセグメントを使うかというと、ぷろぐらむを、物理アドレスの上では、何処においても実行することができるからです。  セグメント無くしては、マルチタスクというのは、実現できないのです。  =それでは、あせんぶらのはなしをしていきましょう= 1、「こんぴゅーた」はなにをしてるの? ・コンピュータとは?  今私たちの回りには、いろいろなコンピューターが有ります。  そして、このコンピューターのほとんどは、「ノイマン式」といわれるものです。  これは、プログラムといわれる一種の手順書を順番に実行していくものです。  そして、そのコンピューターの読んでいる手順書を直接書いているのが「アセンブラ」というわけなのです。  つまり「アセンブラの命令=CPUの命令」と思ってくれて結構です。 ・コンピューターはどんな事をしてるの?  CPUの命令はだいたい次のような形であらわされています。  ld a,b  ここで、”ld”の部分をオペコードといい、”a,b”の部分をオペランドといいます。  例えばこのような命令ではレジスタaにレジスタbのデータを入れます。  コンピューターは、おおざっぱにいうと、CPU、主記憶装置(=メモリー)、補助記憶装置(=ディスクなど)、入出力装置(キーボード、ディスプレイなど)で構成されています。  CPUは、「レジスタ」と「バス」と命令を解析して実行するところなどででできています。  レジスタとは、CPUの内部にある数バイトのデータの入れ物で、CPUのなかにあるので、データを入れるところでは、コンピューターの中ではもっとも速くアクセスできます。  このレジスタにデータを入れて計算をしてメモリーに置くのがコンピューターの基本的な動作です    そのデータは何処から持ってくるかというとまず一つは、「即値」といって次のように、直接命令の中にデータを書いてしまう方法があります。  ld a,100h ;レジスタaに100hを代入  もう1つはメモリーからデータを持ってくる方法があります。そのメモリーのアドレスの指定のしかたには、次の3つがあります。 ・直接アドレス指定 ・間接アドレス指定 ・相対アドレス指定 「直接アドレス指定」とは、オペランドに直接アクセスするアドレスを記述する方法です。 例えば、  ld a,[1000h]  これは、アドレス1000hのデータをレジスタaに代入されます。 「間接アドレス指定」とは、オペランドにアクセスするデータが入ったアドレスを書きます。  ld a,[b]  これは、レジスタbに書かれているアドレスにはいっているデータをレジスタaに代入します。 「相対アドレス指定」とは、ある位置からのアドレスを指定してアクセスする方法です。例えば、  ld a,[ff00h+b]  これは、ff00hにレジスタbの値を足しただけの値のメモリーの内容をレジスタaに代入します。  この2つの方法のことを「アドレス修飾」といい、アドレス修飾に使われるレジスタのことを「指標レジスタ」といいます。 2、8086のなかみ  今、この部にいる人のほとんどは、8086との互換性のあるCPUをつかったコンピューターを使っていると思われるので、これから先は8086に限定して話をすすめます。  8086とは、CPUです。このCPUは、V30や80486やPentiumやCeleron等のもとになっているCPUなので、たとえPenVだろうと、8086のソフトウェアを使うことができるのです。  8086は、次のようなもので構成されています、 ・20本のアドレスバス ・16本のデータバス ・16ビットの汎用レジスタ×8個 ・16ビットのセグメントレジスタ×4個 ・16ビットのインストラクションポインタ ・16ビットのフラグレジスタ  アドレスバスとは、メモリーやI/Oポートの場所を示すのにつかわれます。20本有るということは、20ビットで表せる範囲、つまり、1,048,576バイトものメモリを扱えることになります。  データバスとは、文字通りデータをはこぶもので、16ビット有るということは、8086は、基本的には0〜65,535までの数字を扱うことができるのです。  汎用レジスタとは、AX、BX、CX、DX(この4つのレジスタをスクラッチパッドレジスタともいいます)、SI、DI(この2つのレジスタを、インデックスレジスタともいいます)、BP(ベースポインターともいう)、SP(スタックポインターともいう)の8個があります。汎用レジスタは、計算やデータの一時保存などに使われるほか、それぞれのレジスター固有の使われ方をします。    AXレジスタのAは、アキュムレータ(加算器という意味)のAで、このレジスタは、一般的な計算に用いられます。  BXレジスタのBは、ベースのBでこのレジスタは、SI、DI、BP、SPとともに指標レジスタとして使われます。  CXレジスタのCは、カウンターのCで、ストリング動作(後述)やLOOPの回数などをいれます。  DXレジスタのDは、データのDで、いろいろなことに使われます。  SIレジスタは、ソースインデックスと呼ばれ、指標レジスタとして使われるほか、ストリング動作において、転送もととなるデータを指すときにも使います。  DIレジスタは、ディスティネ−ションインデックスとも呼ばれ、指標レジスタとして使われるほか、ストリング動作において、転送先となる場所を指します。  BPレジスタは、指標レジスタとして使われます。  SPレジスタは、スタックの現在の場所を示すのに使われます。  また、AL、BL、CL、DL、AH、BH、CH、DHというレジスターもあって、ALならAXの下位8ビット、AHならAXの上位8ビットというふうに、レジスターの一部を表すレジスターもあります。  セグメントレジスタは、メモリの番地を示すのに使われます。  CS、DS、ES、SSの4つがあり、それぞれ使い方が決まっています。  8086は、メモリーの管理に独特の方法を使っているのでこれらのレジスタが必要になります。  なぜ独特の方法が使われているかというと、8086は8ビットCPUの名残を引きずっていて、レジスタが16ビットしかないからです。  16ビットでしかアドレスを表せないと、2の16乗(=65,636)の範囲、つまり、64Kバイトのメモリしか使えないことになります。そこで、セグメントレジスタレジスタの登場というわけです。つまり、メモリー空間を、64Kバイトずつのセグメントという単位に分割して、別々にアクセスすればいいわけです。  実際にアクセスされるメモリー(=物理アドレス)は、[セグメントレジスタの値]×16+[命令のアドレス]となります。ここで×16はビットを、左に4つシフトすることと考えられるので、実際にアクセスできるメモリーの範囲は、(16+4)+16=20ビットで表せる範囲、つまり1Mバイトということになります。  それからこういう場合のメモリーのアドレスの書き方は、セグメント:オフセットアドレスという風にかきます  ここで注意すべきことは、同じアドレスのメモリーでもいろいろな表し方ができるということです。  たとえば、セグメントアドレス1234:0005hと1230:0045hは、どちらも同じく、物理アドレス12345hを指します。  それから、FFFF:0010hのように計算した場合に100000hを越えてしまうようなセグメントアドレスを指定した場合は、余った桁は無視され、あたかも、物理アドレスFFFFFhと00000hが繋がっているように動作します。  CSレジスタは、実行中のコードの場所、DSはデータの場所、SSはスタックの場所を示すのに使われます。  ESは予備のセグメントレジスタで、命令中で特に指定しない限り使われません。ただしストリング動作での、転送先のアドレスを指定するときにデフォルトとして使われます。  また、CSやSSを使ってもデータの参照をすることができますが、逆に、DSやESで示しているコードを実行したりスタックとして使ったりすることはできません。 インストラクションポインタ(IP)とは、現在CPUが実行しているところのオフセットアドレスを保持します。  フラグレジスタとは、CPUにおけるフラグを保存しておくためのレジスタです。  フラグは、コントロールフラグとステータスフラグがあります。  コントロールフラグには、ディレクションフラグ(DF、後述)、トラップフラグ(TF、主にデバッガがつかう)、インタラプトフラグ(IF、1なら割り込みを受け付ける)の3つがあります。特殊なプログラムでない限りあまり気に止めることはないと思います。  ステータスフラグには次のようなものがあります。  キャリーフラグ(CF)。くり上がりや、くり下がりの有る計算をしたときと、ローテートやシフトを行ったときセットされます。  補助キャリーフラグ(AF)。下位8ビットから上位8ビット、または、下位4ビットから上位4ビットへのくり上がりや、くり下がりを行ったときにセットされる(らしい。こんなの有るなんて気付かなかった)  パリティフラグ(PF)。計算の結果、1になっているビットが偶数個のときにセットされます。  ゼロフラグ(ZF)。計算の結果、0になるとセットされる。  サインフラグ(SF)。計算の結果の一番上のけたが1のときにセットされる。  オーバーフローフラグ(OF)。桁あふれが起こったときにセットされる。 フラグレジスタの残りの部分は、8086では使われていません。 3、8086の命令セット (凡例) reg 汎用レジスタ(ax、al、bhなど) reg 16ビットの汎用レジスタ(ax、bpなど) acc AXまたはAL imm 即値(1000h、ラベルなど) mem 有効アドレス([1000h]、[bx]、[ラベル]など) seg セグメントレジスタ(cs、dsなど) 説明はBASIC流に解釈してください。 =一般の命令= ・MOV データを転送する mov reg,reg mov mem,reg mov reg,mem mov mem,imm mov reg,imm mov seg,reg16 mov seg,mem mov reg16,seg mov mem,seg  前=後 ・ADD 足し算をする add reg,reg add reg,mem add reg,imm add mem,reg add mem,imm  前+後=前 ・SUB 引き算をする sub reg,reg sub reg,mem sub reg,imm sub mem,reg sub mem,imm  前−後=前 ・(I)MUL 掛け算をする mul reg mul mem imul reg imul mem AX×書かれたもの(16ビット)=DX:AX(DXに上位、AXに下位) または, AL×書かれたもの(8ビット)=AX Iをつけると符号付きとして計算する。 ・(I)DIV 割り算をする div reg div mem idiv reg idiv mem DX:AX(DXが上、AXが下)÷書かれたもの(16ビット)=AX 余り DX または、 AX÷書かれたもの(8ビット)=AL 余り AH Iをつけると符号付きで計算する。 ・INC 1たす(「インクリメント」する) inc reg inc mem 書かれたもの+1=書かれたもの ・dec 1引く(「デクリメント」する) dec reg dec mem 書かれたもの−1=書かれたもの ・cmp 比較する じっさいにはsubのデータを保存しないものです。 ふつうはこの後に条件付きのジャンプ命令がきます。 ・条件付きのジャンプ命令 フラグレジスタの内容によって分岐するかしないかが決まる。 直前にcmp命令を実行して次のようならgotoと考えるとよい  (前)?(後ろ)  ja >  jnbe =  jae =>  jnb =>  jb <  jnae <  jbe <=  jna <=  je =  jg >  jnle > *jge =>  jnl => *jl <  jnge < *jle <=  jng <=  jne <>  (*があるのは+−を考慮する)  jcxz CX=0  jz ZF=1  jnz ZF=0  jo OF=1  jno OF=0  jnp PF=0  jpo PF=0  js SF=1  jns SF=0  jc CF=1  jnc CF=0 ・jmp(f) gotoする  jmp(f) imm  jmp(f) reg 条件判断して飛ぶ命令は飛び先のアドレスの指定にレジスタを使えない。 ※r86では、他のセグメントに飛ぶときはfをつける。 ・int ソフトウェア割り込みを起こす  多くの場合DOSを呼び出すのに使う。  レジスタにデータを設定してint 21hとすることでDOSの機能を使うことができる。  int imm ・call(f) サブルーチンを呼び出す  call(f) imm  call(f) reg  call(f) mem ・ret(f) サブルーチンからreturn  ret(f) ・test ビットテスト ANDと同じ計算をして結果を捨てる。次のようにあるビットが足っているかを調べるのに使う。 test al,00001000b jnz alの4ビット目が立っている ・clc キャリーフラグのリセット  clc ・stc キャリーフラグのセット  stc 4、よくつかうDOSサービス サービス番号−機能 レジスタに入れる数、戻り値 サービス番号をahにいれてint 21h 02h−1文字の出力 DL−>1バイトの文字 06h−1文字の直接入出力 DL=255のとき AL<−入力された文字 ※BASICのINKEY$のように働くので何も入力されていないと0が返る それ以外 DL−>出力する文字 09h−文字列の出力 DS:DX−>半角”$”で終わる文字列のアドレス 0Ah−文字列の入力 DS:DX−>パラメーターブロックへのアドレス パラメーターブロックの構造 db ばっふぁの大きさ(1バイト) db 入力されたバイト数(1バイト) db 入力された文字列(〜255バイト) 0Ch−キーボードバッファーをクリアした後に入力 AL−>バッファーをクリアした後に実行すべきDOSサービス(01h、06h、07h、08h、0Ahのどれか) 3Ch−ファイルの作成 DS:DX−>ファイル名、CX−>属性、AX<−ハンドル 3Dh−ファイルのオープン  DS:DX−>ファイル名、AL−>ファイルアクセスコード(0=読み込み専用、1=書き込み専用、2=読み書き可能)AX<−ハンドル 3Eh−ファイルのクローズ  BX−>ハンドル 3Fh−ファイルの読み込み  DS:DX−>ばっふぁのアドレス、BX−>ハンドル、CX−>読み込むバイト数、AX<−実際に読み込んだバイト数 40h−ファイルの書き込み (同上) 41h−ファイルの削除  DS:DX−>ファイル名 42h(00h)−ファイルポインタの移動  AL−>00h、BX−>ハンドル、CX:DX−>ファイルの先頭から数えたファイルポインタを移動したい場所 42h(01h)−ファイルポインタの移動  AL−>01h、BX−>ハンドル、CX:DX−>現在のファイルポインタの位置から数えたファイルポインタを移動したい位置 4Ch−プログラムの終了 AL−>ERRORCODE 5、プログラムの書き方 ここでは、LSI−C(試食版)に付属のr86を使うと仮定します。 まず、テキストエディタでソースコードを書きます。そのさいソースコードは次のような形式にしてください。(このとき、拡張子は”A86”にします) cseg rs 100h (アセンブラのプログラム) end 必ずDOSのサービスで終了してください。さいごのendでは終了してくれません。 できたなら、 r86 (ソースファイルの名前) としてこんぱいるし、エラーがなかったら lld -Fc (オブジェクトファイルの名前) としてリンクします。 そうすると、拡張子がEXEになったファイルができるので、その拡張子をCOMにします。 <例> C:\LSIC-86\BIN\>COPY CON TEST1.A86 CSEG RS 100H MOV AH,9 MOV DX,MES1 INT 21H INT 20H MES1: DB "Hello,World!",0DH,0AH,"$" END ^Z 1 file(s) copied C:\LSIC-86\BIN\>R86 TEST1 C:\LSIC-86\BIN\>LLD -Fc TEST1 C:\LSIC-86\BIN\>REN TEST1.EXE TEST1.COM C:\LSIC-86\BIN\>TEST1 Hello,World! C:\LSIC-86\BIN\> ちなみに実行されたときの引き数を知りたいときは、[80H]にひきすうの文字数が、[82H]から引き数が入っています。 6、おすすめソフトウェア(ぜんぶhttp://www.vector.co.jp/に上がっているはず) ・ExDeb−デバッガー、 ・LSIC86(試食版)−Cコンパイラ、アセンブラであるr86や、リンカのLLDなどが入っている。ラジオ部員なら落とすべし! ・TBC−BASICのコンパイラ、ただしN88との互換性はない。(移植にはちょっち手間がかかる) ・Interrupt List−Ralf Brownさん作。intで呼び出すサービスがひたすら書いてある。英語だけどものすごく勉強になる。イチ押し! 7、あとがき  ただいまAM1:00です。昨日済ますつもりが今日になってしまった。  半分死にかけでかいているので大嘘が書いてあるかも。  とくに命令とDOSサービスの説明はうろ覚えなので間違ってるところ有ると思います。(きちっと調べたい人は、上に書いてあるInterrrupt List読んでください。)  ほんとは、もっとサンプル付けたかったのですが、今から説明書いていくわけにいかないですね。(まじかる☆アンティークのPCM吸い出すのとか、DQ3パラメーターエディタとか、いろいろ作った。)  お勧めソフトはとっても重要です。プログラミングするなら落としたほうがよいでしょう。