1.简介
MIX是The Art of Computer Programming书中所用的汇编语言。
寄存器:有9个寄存器,A、X、J、I1、I2、I3、I4、I5、I6。I1~I6为变址寄存器。参见后面的图3。
MIX中一个字由5个字节和一位符号位组成。
MIX中的字符包括A-Z的大写字母,0-9的数字和部分特殊符号。
指令格式(这里是说机器指令):
| 0 | 1 | 2 | 3 | 4 | 5 |
| 地 址 | index | mod | 操作码 |
这是定长指令格式,所有指令都是6字节,其中第字节为符号,
0 1 2号字节共同组成地址。
我觉得不用太关心机器指令,不过书上说了很多。
在实例1中具体说明。
2.实例讲解
实例1,代码在第一卷1.3.2节140页,算法在1.2.10节93页。
代码:
X EQU 1000 ORIG 3000 START STJ EXIT INIT ENT3 0,1 JMP CHANGEM LOOP CMPA X,3 JGE *+3 CHANGEM ENT2 0,3 LDA X,3 DEC3 1 J3P LOOP EXIT JMP * END START
先说初始化,书上省略了的,寄存器I1为数组长度n。寄存器I3用于存k,寄存器I2存j。
n是提前给了初值的,k和j是运行的时候再初始化的。
ORIG 3000
表示程序是从3000的地址开始运行的,
X EQU 1000
表示数据是从1000号单元开始存放的,这里省略了数据的初始化。
即提前在1000、1001、1002...1000+N号单元存放了一组数据,这个程序是要找出其中的最大值。
STJ EXIT
STJ就是store rJ,即储存寄存器J到EXIT所在的地址,J即jump,是一个跳转用的寄存器,
EXIT是个标签,怎么能存到标签呢,实际是存到那个机器指令的地址字段,如图1:
这张图是一个模拟器的,到后面会说。
执行到JMP *的时候就会跳转到寄存器J的地址。
这是子程序的开头所以要那么做,
在intel汇编里面一般是利用栈实现的:
在子程序开头
push ebp
mov ebp,esp
然后在子程序结尾进行相反的操作。
MIX是用寄存器J实现的。
ENT3 0,1,
ENT3就是enter3,就是存入寄存器3,把什么存进去呢,
1是寄存器I1,
0是机器码的mod,
所以ENT3 0,1是把I1赋值给I3
这个0其实没多大用,但如果写ENT3 1意思是I3赋值为1,所以必须要那个mod
我们刚才说了初始化寄存器I1为数组长度n,I3寄存器为变量k
那么ENT3 0,1就是k=n
JMP CHANGEM 就是一个无条件跳转,大家应该都懂的
ENT2 0,3
就是把寄存器I3赋值给寄存器I3,I2为变量j,
那么 ENT2 0,3就相当与j=k
LDA X,3
显然是load to A,把什么存到寄存器A呢,
3是寄存器I3,前面说过,I1~I6是变址寄存器,
所以是A=X[I3] 把I3当作数组下标,I3就是k,A为最大值m
所以 LDA X,3就是m=X[k]
DEC3 1
这句就是I3寄存器的值减1,DEC不难理解,这里单独的数字就不是寄存器I1了。
J3P LOOP
jump if I3 positive,如果I3为正数,跳到LOOP
CMPA X,3
CMPA是与寄存器A比较,X,3为X[I3],即比较m与X[k]
JGE *+3
*是当前指令的地址,跳转到3条指令后
实例2以第三卷的5.2.1节直接插入排序。大家应该都会80x86汇编,我就仿照MIX的代码用x86汇编写一个做对照。
先看C语言的代码复习一下插入排序的原理:
代码:
#include <stdio.h> #define N 6 int main() { int a[N]={3,2,1,6,5,4}; int i,j,temp; for(j=1;j<N;j++) { temp=a[j]; i=j-1; while(temp<a[i]&&i>=0) { a[i+1]=a[i]; i--; } a[i+1]=temp; } for(i=0;i<N;i++) printf("%d\t",a[i]); return 0; }
代码:
.386 .model flat, stdcall option casemap :none include windows.inc include kernel32.inc includelib kernel32.lib include user32.inc includelib user32.lib .data arr db 3,2,1,6,5,4 ;数组 count equ $-arr ;数组长度 szBuffer db 100 dup(0) szFormat db "排序后数组为:%d,%d,%d,%d,%d,%d",0 szTitle db "直接插入排序",0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code start: mov si,1 ;j=1 S2: mov al,arr[si] ;temp=a[j] mov di,si ;i=j-1 dec di ; S3: cmp al,arr[di] ;cmp temp,a[i] jge S5 mov ah,arr[di] ;a[i+1]=a[i] mov arr[di+1],ah dec di ;i-- cmp di,0 ; jge S3 S5: inc di ;这两行相当于mov arr[di+1],al,但那样有bug mov arr[di],al inc si cmp si,count jl S2 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;输出 xor eax,eax mov si,count-1 S6: mov al, arr[si] ;逆序压栈 push eax dec si cmp si,0 jge S6 invoke wsprintf,addr szBuffer,addr szFormat invoke MessageBox,NULL,addr szBuffer,addr szTitle,MB_OK invoke ExitProcess,NULL end start
完整的MIX的代码我就不给了,在书上65-66页。基本上是一条汇编对应一条MIX指令,大家可以自己对照着看。
3.模拟器
代码总是要能亲自运行一下才好。
原书作者认为intel的汇编为了保证兼容性而太过复杂,效率不高,所以自己发明了MIX汇编。
可是mix的机器是不存在的,所以只能用模拟器来运行。
模拟器可以在http://www.gnu.org/software/mdk/mdk.html下载,只能在linux下安装。
安装过程很简单,就是打开终端依次输
./configure
make
sudo make install
这三个命令就可以了。
然后弄emacs的语法加亮,这一步跳过,主要是为了好看。
输入emacs ~/.emacs
打开emacs的配置文件,然后输入以下内容:
(setq load-path (cons "/usr/local/share/mdk" load-path))
(autoload 'mixal-mode "mixal-mode" t)
(add-to-list 'auto-mode-alist '("\\.mixal\\'" . mixal-mode))
(autoload 'mixvm "mixvm" "mixvm/gud interaction" t)
如图2,不要忘了括号,按Ctrl+X、Ctrl+S保存
下面来看一个简单的例子,所谓的helloworld
代码:
*标签 指令(instuction) 操作符(operand) 注释 * TERM EQU 19 控制台(terminal) ORIG 2000 基地址 START OUT MSG(TERM) 在TERM(即控制台)输出MSG HLT MSG ALF "HEllO" ALF ",PEDI" ALF "Y. " END START
编译: mixasm hello
编译后为hello.mix
执行:[l@localhost mix-1.2.6]$ mixvm -r hello
Program loaded. Start address: 2000
Running ...
HELLO,PEDIY.
... done
调试:mixvm hello
或者用图形化界面调试更直观 gmixvm
如图3
点File->Load加载hello.mix
最左边一列是地址,第二列是机器指令。
对hello.mixal再解释一下:
每一行从左到右依次为 标签 指令(instuction) 操作符(operand) 注释,
用TAB对齐,比如没有标签第一列就必须留空,因为不对齐就编译不过,这跟其他语言是不一样的,为了让大家看清对齐的格式,我再放张图。
ORIG 2000是 定义基地址,类似于windows pe程序里常见的0x00400000,2000是随便写的,也可以指定其他值。
HLT就是就是停止。
前面说过,MIX只有大写字母和部分符号,如果在程序里写小写的hello,结果输出还会是大写的,如果用!等一些不支持符号就不能编译。
用ALF定义字符串,
因为MIX的机器指令是定长的,所以不能直接
ALF “HELLO,PEDIY.”
指令除了符号位只有5个字节,那么只能存5个字符,
于是有了以上古怪的写法。