1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

一般化されたアセンブラ 'axx General Assembler'

Last updated at Posted at 2024-02-21

GENERAL ASSEMBLER 'axx.py'

pythonで書いたので、ニックネームはPaxxです。

動作試験環境

Arch linux terminal

本文

axx.pyはアセンブラを一般化したジェネラル(一般)アセンブラです。理論上、任意のプロセッサアーキテクチャを処理できます。個々のプロセッサアーキテクチャを処理するために、それ用のパターンファイル(プロセッサ記述ファイル)が必要です。自由なインストラクションを定義できますが、ターゲットプロセッサのアセンブリ言語に準じてパターンファイルを作ると、記法は少し違うものの、そのプロセッサのアセンブリ言語を処理できます。一応。要するに、インストラクションの文法規則と、それに基づくバイナリ生成だけなのです。

実行プラットフォームも特定の処理系に依存しません。DOSファイルの行末のchr(13)も無視するようにしています。pythonが動く処理系だったら動作すると思います。

このヴァージョンはアセンブラの核となる部分だけなので、専用のアセンブラの備えている最適化、高機能マクロ、デバッガなどの実用的な機能はサポートしていません。実用的な機能について、マクロはプリプロセッサを使ってください。とりあえず、リンカ・ローダは、バイナリファイルとラベル(シンボル)ファイルを管理するプログラムを使ってください。IDEではないため、デバッガは外部デバッガを使ってください。最適化は未対応です。基本的な機能はあると思うので、応用してください。今のバージョンは実用性が足りません。

パターンファイルとソースファイルが分離されているため、コード化の手間を考えなければ、あるインストラクションセットのソースから別のプロセッサの機械語を生成することも可能です。共通の言語から、異なるプロセッサの機械語コードを生成することも可能です。パターンデータのbinary_listに複数の命令コードを書くとマクロとして機能しますが、あまりスマートではありません。それにより、簡単なコンパイラも書けます。

パターンデータは、制御構文を代入と3項演算子以外は持ちません。アセンブリ言語に限らず、バイナリ生成に使えます。

アセンブリ言語はプロセッサのインストラクションコードと1対1に対応しているので、axxが実現しました。

axxは第1引数から、アセンブラのパターンデータを読み込み、パターンデータに基づき第2引数のソースファイルをアセンブルします。第2引数を省略したら、端末(標準入力)からソースを入力します。

結果は、標準出力にテキストとして出力され、-oオプションで指定された引数があれば、カレントディレクトリにバイナリファイルが出力されます。-eオプションは、.exportで指定されたラベルをセクション/セグメント情報とともにTSV形式でファイルに出力します。

パターンファイルはチューリングincompleteです。

axxでは、アセンブリ言語ソースファイルや標準入力から入力するラインをアセンブリラインと名付けます。

アセンブリ言語の定義(Geminiから)

>アセンブリ言語の定義を述べてください。

アセンブリ言語とは?
アセンブリ言語とは、コンピュータのハードウェアを直接操作するための低水準プログラミング言語です。機械語(コンピュータが直接理解できる0と1の羅列)に非常に近い言語であり、各命令がCPUの特定の動作に対応しています。

アセンブリ言語の特徴
低水準言語: 機械語に近く、ハードウェアを直接操作できるため、非常に高速なプログラムを作成できます。
ハードウェア依存性: 異なる種類のCPUごとに命令セットが異なるため、特定のCPUに合わせたプログラミングが必要となります。
人間が読み書きしやすい: 機械語と比較して、人間が理解しやすいニーモニックと呼ばれる略語で命令を表します。
メモリ管理: プログラマーがメモリを直接管理する必要があるため、高度なプログラミングスキルが求められます。
アセンブリ言語の用途
組み込みシステム: マイクロコントローラーなど、リソースが限られた環境でのプログラミング
オペレーティングシステムの開発: ハードウェアに直接アクセスする必要がある部分の開発
デバイスドライバの開発: ハードウェアとソフトウェアを繋ぐ部分の開発
パフォーマンスクリティカルな部分: 高速な処理が求められる部分の最適化
アセンブリ言語のメリットとデメリット

メリット              デメリット
高速な実行	           プログラミングが複雑で時間がかかる
メモリ効率が良い          ハードウェアに依存するため移植性が低い
ハードウェアを細かく制御できる   デバッグが難しい
システムの深い理解につながる    人材不足

アセンブリ言語の学習
アセンブリ言語は、コンピュータの仕組みを深く理解したい方や、高度なプログラミングスキルを身につけたい方におすすめです。ただし、学習曲線が急峻で、習得には時間がかかります。

まとめ
アセンブリ言語は、コンピュータの動作原理を深く理解し、高度な制御を実現するための言語です。現代では高水準言語が主流ですが、特定の分野では依然として重要な役割を果たしています。アセンブリ言語を学ぶことで、プログラミングの幅を広げ、より高度なシステム開発に貢献することができます。

より詳しく知りたい場合は、以下のキーワードで検索してみてください。

アセンブリ言語
ニーモニック
アセンブラ
機械語
組み込みシステム
オペレーティングシステム
デバイスドライバ
何か他に質問はありますか?

解説

install and execution(assemble)

# install
git clone https://github.com/fygar256/general-assembler-axx.git
cd general-assembler-axx
chmod +x axx.py
sudo cp axx.py /usr/bin/axx

# execution(assemble)
axx patternfile.axx [source.s] [-o outfile.bin] [-e expfile.tsv] [-i impfile.tsv]

patternfile.axx --- パターンファイル
source.s --- アセンブリソース
outfile.bin --- rawバイナリ出力ファイル
expfile.tsv --- セクション・ラベル情報エクスポートファイル
impfile.tsv --- セクション・ラベル情報インポートファイル

パターンファイルの解説

パターンファイルは、プロセッサ記述ファイルで、個々のプロセッサに対応するため、ユーザー定義です。機械語やアセンブリ言語に対する一種のメタ言語です。

パターンファイルの定義が難しいと感じるならば、最小限のオペランドだけ式の評価に渡して、文字列リテラルで書けばいいです。

パターンファイルの中のパターンデータは次のように並んでいます。

instruction  :: error_patterns  :: binary_list 
instruction  :: error_patterns  :: binary_list 
instruction  :: error_patterns  :: binary_list 
:
:

instructionは省略不可です。error_patternsは省略可です。binary_listは省略不可です。
instruction、error_patterns、binary_listは、::で区切ってください。

for ex. (x86_64)

RET :: 0xc3

コメント

パターンファイル内に、/*を書くとその行の/*以降がコメントになります。今の所、*/で閉じることはできません。その行の/*以降だけに有効です。

大文字・小文字の区別、変数

パターンファイルのinstructionの大文字は文字定数として扱われます。小文字にすると、1文字の変数として扱われます。アセンブルラインからその位置に当たるシンボルの持つ値が変数に代入されます。!小文字とすると、その位置の式の値、!!小文字にするとその位置の因子の値が代入され、error_patternsとbinary_listから参照されます。代入されてない変数は全て初期値の0です。error_patternsとbinary_listからの参照のときは、!は必要ありません。全て同様に値が参照されます。

アセンブリラインからは、ラベルやセクション名以外は、大文字でも小文字も同じとして受け付けます。

特殊な変数は'$$'で、現在のロケーションカウンタを表します。

エスケープキャラクタ

instruction内でエスケープキャラクタ\が使えます。

error_patterns

error_patternsは、変数と比較演算子を使い、エラーの出る条件を指定します。

エラーパターンは複数指定可で、','で区切って記述します。例えば、次のようです。

a>3;4,b>7;5

この例では、a>3のとき、エラーコード4を返し、b>7のときエラーコード5を返します。

binary_list

binary_listは、出力するコードを','で区切って指定します。例えば、0x03,dとすると、0x3の次にdが出力されます。

8048を例に取ります。パターンファイルに

ADD    A,R!n ::  n>7;5 :: n|0x68

があるとし、アセンブリラインにadd a,rnを渡すと、n>7のときエラーコード5(Register out of range)を返し、add a,r1で、0x69のバイナリが生成されます

binary_listの要素が空だと、アライメントをします。冒頭から、,で始まったり、0x12,,0x13などとすると、空の部分が丁度のアドレスまでパディングされます。

binary_listの要素の先頭に;がつくと、その要素が0だった場合、出力されません。

symbol

.setsym :: symbol :: n

と書くと、symbolが値nで定義されます。

シンボルは、アルファベット、数字、いくつかの記号列です。

symbol1でsymbol2を定義するのは以下のように書きます。

.setsym ::symbol1 ::1
.setsym ::symbol2 ::#symbol1

symbol定義のz80の例を挙げます。パターンファイル内に

.setsym ::B ::0
.setsym ::C ::1
.setsym ::D ::2
.setsym ::E ::3
.setsym ::H ::4
.setsym ::L ::5
.setsym ::A ::7
.setsym ::BC ::0x00
.setsym ::DE ::0x10
.setsym ::HL ::0x20
.setsym ::SP ::0x30

と書いておくと、シンボルB,C,D,E,H,L,A,BC,DE,HL,SPを、それぞれ0,1,2,3,4,5,7,0x00,0x10,0x20,0x30として定義します。シンボルには、大文字小文字の区別はありません。

パターンファイル中に同じシンボルの定義が複数あると、新しいものが古いものを更新します。すなわち、

.setsym ::B::0
.setsym ::C::1
ADD A,s

.setsym ::NZ::0
.setsym ::Z::1
.setsym ::NC::2
.setsym ::C ::3
RET s

この場合、ADD A,CのCは1、RET CのCは3になります。

・記号、数字、アルファベットが混在するシンボルの例

.setsym ::$s5:: 21

シンボルのクリアは.clearsymでします。

.clearsym::ax

上の例はaxというシンボルを未定義にします。

全クリアは引数を指定しないでします。

.clearsym

パターンファイル内から、シンボルに使う文字セットを決めることができます。

.symbolc::<characters>

とすると、数字とアルファベット大文字小文字以外の文字を<characters>で指定できます。

デフォルトは、アルファベット+数字+'_%$-~&|'です。

パターンの順番

パターンファイルは上から順に評価されますので、先に置かれたほうが優先します。特殊のパターンを先に、一般のパターンを後に置きます。下のように。

LD A,(HL)
LD A,e

二重大括弧

instructionの中の省略可能なものは二重大括弧で括れます。z80のinc (ix)命令を示します。

INC	(IX[[+!d]]) ::				    0xdd,0x34,d

この場合、小文字の変数の初期値は0なので、inc (ix+0x12)と、省略しなかった場合は0xdd,0x34,0x12が、inc (ix)と、省略した場合は0xdd,0x34,0x00が出力されます。

パディングのバイトコード指定

パターンファイルから、

.padding 0x12

と、するとパディングするバイトコードは0x12になります。デフォルトは0x00です。

include

このようにするとファイルをインクルードできます。

.include "file.axx"

アセンブリファイルの解説

label

アセンブルラインからは、ラベルは以下の方法で定義することができます。

label1:
label2: .equ 0x10
label3: nop

ラベルは、数字以外の.かアルファベットかいくつかの記号から始まる、アルファベットと数字といくつかの記号列です。

ラベルでラベルを定義することは以下のようにします。

label4: .equ label1

パターンファイル内から、ラベルに使う文字セットを決めることができます。

.labelc::<characters>

とすると、数字とアルファベット大文字小文字以外の文字を<characters>で指定できます。

デフォルトは、アルファベット+数字+アンダースコア+ピリオドです。

ラベル参照のあとに:をつけると、未定義ラベルエラーのチェックをします。:を使うアセンブリ言語では、label参照のあとにスペースを入れてください。

ORG

ORGは、アセンブルラインから、

.org 0x800
または、
.org 0x800,p

とします。.orgはロケーションカウンタの値を変更します。,pがついていれば、以前のロケーションカウンタの値が.orgで指定した値より小さいと、.orgで指定した値までパディングします。

アライメント

アセンブルラインから、

.align 16

とすると、16でアライメントします(16の倍数アドレスまで.paddingで指定されたバイトコードでパディングします)。引数を省略すると、直前の.alignで指定した数値あるいはデフォルト値でアライメントをします。

浮動小数点、数の表記

例えば、浮動小数点をオペランドに含むプロセッサがあるとし、 MOVF fa,3.14 で、faレジスタに3.14がロードされ、そのオペコードは01とします。その場合、パターンデータは、

MOVF FA,!d ::0x01,d>>24,d>>16,d>>8,d

となり、アセンブルラインに、movf fa,0f3.14を渡すと、バイナリ出力は、0x01,0xc3,0xf5,0x48,0x40と なります。

2進数は'0b'のプリフィックスを付けて下さい。

16進数は'0x'のプリフィックスを付けて下さい。

浮動小数点float(32bit)は'0f'のプリフィックスを付けて下さい。

浮動小数点double(float 64bit)は、'0d'のプリフィックスを付けて下さい。

文字列

.asciiで、文字列の、.asciizで、末尾に0x00を伴う文字列のバイトコードを出力します。

.ascii "sample1"
.asciiz "sample2"

export

下のようにすると、labelをsection/segment情報とともにexportできます。.export命令で指定されたlabelだけがexportされます。

.export label

section

下のようにすると、section/segmentを指定できます。

section .text
または
segment .text

いまのところ、sectionとsegmentは同じ意味です。

section sort

例えば、

section .text
ld a,9
section .data
.asciiz "test1"
section .text
ld b,9
section .data
db 0x12

などとすると、その通りに配置されてしまうので、section sortを使って、整列させてください。

section .text
ld a,9
ld b,9
section .data
.asciiz "test1"
db 0x12

コメント

アセンブリラインのコメントは;です。

式、演算子

アセンブリラインの式も、パターンデータの式も、同じ関数を呼び出しているので、働きは、ほとんど同じです。アセンブリラインからは小文字の変数は参照できません。

演算子の優先順位

演算子と優先順位はpythonを基にして次の通り

(expression)    括弧で囲った式
#               symbolの値を返す演算子
-,~             負、ビットNOT
@               後に続く値の最高位ビットが右から何ビット目にあるかを返す単項演算子
:=              代入演算子
**              べき乗
*,//         乗算、整数除算
+,-             加算、減算
<<,>>           左シフト、右シフト
&               ビットAND
|               ビットOR
^               ビットXOR
'               符号拡張
<=,<,>,>=,!=,== 比較演算子
not(x)          論理NOT
&&              論理AND
||              論理OR
x?a:b           3項演算子

代入オペレータとして:=があります。d:=24とすると、変数dに24が代入されます。代入オペレータが持つ値は、代入された値です。

前置オペレータ#は、後に続くシンボルの値を取ります。

前置オペレータ@は、後に続く値の最高位ビットが、右から何番目にあるかを返します。これをHebimarumattaオペレータと名付けます。

2項演算子'a'24とすると、aの24ビット目のビットを符号ビットにして符号拡張(Sign EXtend)します。これをSEXオペレータと名付けます。

2項演算子**は、べき乗です。

3項演算子?:は、x?a:bで、xが真のときa,偽のときbを返します。

バイナリ出力の例

.setsym:: BC:: 0x00
.setsym:: DE:: 0x10
.setsym:: HL:: 0x20
LD    s,!d::  (s&0xf!=0)||(s>>4)>3;9 :: s|0x01,d&0xff,d>>8

で、ld bc,0x1234, ld de,0x1234, ld hl,0x1234が、それぞれ、0x01,0x34,0x12、0x11,0x34,0x12、0x21,0x34,0x12を出力します。

いくつかのプロセッサのいくつかの命令のテスト

テストですので、バイナリは実際のコードとは違います。

test.axx
/* test
.setsym ::a:: 7
.setsym ::b:: 1
.setsym ::%% ::7
.setsym ::||:: 8
LD s,x :: 0x1,y,s,x

/* ARM64
.setsym ::r1 :: 2
.setsym ::r2 :: 3
.setsym ::r3 :: 4
.setsym ::lsl:: 6
ADD w, x, y z #!d :: 0x88,d
ADD x, y, !e :: 0x91,x,y,e

/* A64FX
.setsym ::v0 :: 0
.setsym ::x0 :: 1
ST1 {x.4S},[y] :: 0x01,x,y,0

/* MIPS
.setsym ::$s5 ::21
.setsym ::$v0 ::2
.setsym ::$a0 ::4
ADDI x,y,!d :: (e:=(0x20000000|(y<<21)|(x<<16)|d&0xffff))>>24,e>>16,e>>8,e

/* x86_64
.setsym ::rax:: 0
.setsym ::rbx:: 3
.setsym ::rcx ::1
.setsym ::rep ::0xf3

MMX A,B ::  ,0x12,0x13
LEAQ r,[s,t,!d,!e] :: 0x48,0x8d,0x04,((@d)-1)<<6|t<<3|s,e
LEAQ r, [ s + t * !!h + !!i ] :: 0x48,0x8d,0x04,((@h)-1)<<6|t<<3|s,i
[[z]] MOVSB :: ;z,0xa4
TEST :: 0x12,,0x13

/* ookakko test
LD (IX[[+!d]]),(IX[[+!e]]):: 0xfd,0x04,d,e 
NOP :: 0x01

x86_64のLEAQ r,[s+t*h+i]などの表記は、LEAQ r,[s+t*!!h+!!i]と書いてください。!!hのところを!hと書くと、パターンマッチの際、アセンブリラインの式の評価関数が、leaq rax,[rbx+rcx*2+0x40]の、2から後が!hにあたり、そこから先の、2+0x40を式として解釈してしまい、hに2+0x40が代入され、あとの+!!iがあまり、構文解析エラーとなってしまうからです。!!hは因子、!hは式を表すからです。これは、式の中のエスケープキャラクタを処理できないためです。

test.s
leaq rax , [ rbx , rcx , 2 , 0x40]
leaq rax , [ rbx + rcx * 2 + 0x40]
movsb
rep movsb
addi $v0,$a0,5
st1 {v0.4s},[x0]
add r1, r2, r3 lsl #20

実行例

$ axx.py test.axx test.s
0000000000000000: leaq rax , [ rbx , rcx , 2 , 0x40]  0x48 0x8d 0x04 0x4b 0x40
0000000000000005: leaq rax , [ rbx + rcx * 2 + 0x40]  0x48 0x8d 0x04 0x4b 0x40
000000000000000a: movsb  0xa4
000000000000000c: rep movsb  0xf3 0xa4
000000000000000e: addi $v0,$a0,5  0x20 0x82 0x00 0x05
0000000000000012: st1 {v0.4s},[x0]  0x01 0x00 0x01 0x00
0000000000000016: add r1, r2, r3 lsl #20  0x88 0x14

error

・labelが、パターンファイル内のシンボルと被るとエラーになります。
・同じlabelを二度以上定義するとエラーになります。
・構文解析ができないとエラーになります。
・未定義ラベルを参照するとエラーになります
・error_patternsの条件を一つでも満たすとエラーになります。その場合、エラーコード0,1,2,5,6に対し、それぞれ(Value out of range,Invalid syntax,Address out of range,Register out of range,Port number out of range)のメッセージが出ます。エラーの種類が足りなかったら、ソースにエラーメッセージを足してください。

コメント

・Sorry for original notation.

・エラーチェックが甘いです。

・無茶なこと言われましたが、量子コンピュータとLISPマシンには対応していません。
 量子コンピュータのアセンブリ言語は量子アセンブリと言われ、アセンブリ言語ではありません。
 LISPマシンのプログラムは、アセンブリ言語ではありません。

・ホームメイドプロセッサから、スーパーコンピュータまでどうぞ。にゃは。

・axxを評価、拡張、修正をしてください。構造が難解ですが、Pythonで書かれているため、拡張が容易です。ご自由にご拡張ください。

・式の中にエスケープキャラクタを入れるのは難しい。

・マクロ機能はプリプロセッサを使ってください。高機能マクロがつくと嬉しい。

・今の所、リンカ・ローダは、オプション-iを指定したら、TSVファイルからラベルがインポートされ、オプション-eを指定したら、.exportで指定したlabelがそのlabelが属するsection/segmentとともに、TSVでファイルにエクスポートされるので、それを利用する。

・一応、パターンファイルから、アセンブルファイルのラベルを参照できるようにしました。これを使うと、簡単なコンパイル言語が記述できるようになりますが、構造が非常に複雑になるので、やめた方がいいです。

未実装の項目

・リンカに対応できるようにする。

・実用化させたい。僕はlinuxしか持ってないので、linuxで。linuxの特殊解は、ELFのリロケータブルオブジェクトファイルに対応して、ldでリンクできるようにする。

・パターンファイルの評価の順番が難しい。

・8ビット未満の、例えばビットスライスプロセッサや、機械語のワードがバイト単位でないプロセッサのアセンブルもできますが、axxは8ビット単位の出力です。そういうプロセッサは、オブジェクトファイルフォーマットに依存します。

・コアができたので、axxにパターンファイルを用意して、リンカと高機能マクロと最適化機能とIDEのラッパーをつけると立派なシステムになると思いますが、個人ではそんな大きなプロジェクトは完遂するのが難しいので、誰か作ってください。実用化されると幸いです。

・生成AIさん、この記事は複雑だけど、ちゃんと解析してください。仮定法の誤解があったり、文脈依存の解釈が間違っている。

パターンファイル(プロセッサ記述ファイル) Feature not available now.

・パターンファイルをもっと記述的なメタ言語にすると、可読性が高い、評価の順番に依存しなくなる、制御構文が書きやすい、プロセッサ記述ファイルのデバッグがやりやすい。けれども、パターンデータのほうが直観的に記述できる。メタ言語を更に一般化して、パターンファイルに記述的なメタ言語を使い、binary_listに文字列リテラルと文字列演算+数値演算をもたせ、制御構文も持たせると、中間言語を生成したり、アセンブリ言語同士のコンバータができる。その時、binary_listは、object_listに、パターンファイルはprocessor_specification_fileに名前を変える。evalが使えるかな。メタ言語は、パターンデータから、複数行に亘る記述言語になる。実現可能である。axxを元にして誰か作りゆう中らしい。もうあるのかな。式にエスケープキャラクタが必要なので、パターンマッチングのアルゴリズムが違うらしい。パターンファイルでも、a='MOV b,c'として、文字変数(今は小文字のアルファベットだが、普通に言うシンボルに拡張すれば)に命令(文字列)をもたせて、binary_listに記述するようにすると、スマートにマクロが書けるようになる。b=rep(a,10)で、aを10回出力とか、align(n)とか。ループ構造を許すと、axx.py内部で処理するとき、無限ループになったら、デバッグが超ややこしくなるが、パターンファイルだけの評価をつけると、デバッグも簡易になり、ループ構造、分岐構造も許される。チューリング完全になる。自己参照のチェックが必要。expand(a)で、展開。例えば、a='b c d' b='MOV AX,e' c='JMPC d'を、'MOV AX,e JMPC d'に。 cはオペランドdを一つ取るので、a='b c d'の、cの評価のとき、dを取ってくる。expression(a)で、式の評価、label:で、ラベル定義。プロセッサ記述ファイルと、アセンブリファイルでは、ラベルを別にしておくと、同じラベルが両方にあっても気にしなくてすむ。記述的なメタ言語にするとなると、ドラスティックな書き換えが必要。アセンブラのプロセッサ特性記述ファイルが複雑になると、General Disassemblerとのファイルの互換性を取るのが難しくなる。

お願い

バグを見つけた方がいっしゃいましたら、どう動かないかお知らせ願えると幸いです。

Version

GitHubリポジトリ(ソース・サンプルコード)

謝辞

問題を出してくれて、ヒントをくれた、師匠の浜田純市さんと東京電子設計と、協力してくれた電気通信大学と、計算機科学者さんと、Googleと、Qiitaと、そして、忘れられない誰か達に感謝を述べさせていただきます。ありがとうございます。

一句

冬銀河自由に描く星座かな 公太郎

1
0
12

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?