汇编器源码剖析
来源:互联网 发布:对外经济贸易大学知乎 编辑:程序博客网 时间:2024/06/08 08:36
本文我们对一汇编器源代码进行剖析,了解汇编器实现原理,进而我们根据样例,自己实现一个汇编器。实现自己版本的汇编器放在另一篇中,本文主要是对别人的源码进行剖析。
本文源代码是来自Kevin Lynx的《基于栈的虚拟机的实现》中关于实现一个堆栈虚拟机中附带了汇编器的实现,源码下载地址如下:source code。由于本人对汇编器比较感兴趣,所以对其进行如下剖析。
汇编器主要是一个sasm.c源文件。
其中,一开始定义了一个const char* op_desc[],op_desc是一个数组,其元素类型是const char*,即op_desc是一个字符串数组,其用于存储汇编操作符。
/* map to op_type */const char *op_desc[] = { "HALT", "IN", "OUT", "ADD", "SUB", "MUL", "DIV", "DUP", "LD", "ST", "LDC", "JLT", "JLE", "JGT", "JGE", "JEQ", "JNE", "JMP", 0};
下面我们对各个操作符进行逐一介绍:
操作符
操作数个数
说明
HALT
0,HALT
终止
IN
0,IN
从标准输入中读入整型值并压栈
OUT
0,OUT
从栈中弹出,从标准输出
ADD
0,ADD
从栈中弹出a,弹出b,计算b+a,并将结果压入栈中
SUB
0,SUB
从栈中弹出a,弹出b,计算b-a,并将结果压入栈中
MUL
0,MUL
从栈中弹出a,弹出b,计算b*a,并将结果压入栈中
DIV
0,DIV
从栈中弹出a,弹出b,计算b/a,并将结果压入栈中
DUP
0,DUP
压入栈顶值的拷贝
LD
0,LD
从栈中弹出地址,并压入改地址里的整数值
ST
0,ST
从栈中弹出值,再弹出地址,并将该值存储到该地址中
LDC
1,LDC value
压入value
JLT
1,JLT loc
弹出value,检测value是否小于0,如果小于,则pc=loc
JLE
1,JLE loc
弹出value,检测value是否小于等于0,如果小于等于,则pc=loc
JGT
1,JGT loc
弹出value,检测value是否大于0,如果大于,则pc=loc
JGE
1,JGE loc
弹出value,检测value是否大于等于0,如果大于等于,则pc=loc
JEQ
1,JEQ loc
弹出value,检测value是否等于0,如果等于,则pc=loc
JNE
1,JNE loc
弹出value,检测value是否不等于0,如果不等于,则pc=loc
JMP
0,JMP
不用弹出value,pc=loc
sasm.c文件中包含了sm.h头文件,sm.h文件中定义了实际的指令op_type,该指令是枚举类型,op_type与op_desc的对应关系如下:
op_type
op_desc
opHalt
HALT
opIn
IN
opOut
OUT
opAdd
ADD
opSub
SUB
opMul
MUL
opDiv
DIV
opDup
DUP
opLd
LD
opSt
ST
opLdc
LDC
opJlt
JLT
opJle
JLE
opJgt
JGT
opJge
JGE
opJeq
JEQ
opJne
JNE
opJmp
JMP
opInvalid
无效参数
指令为一个结构体,其定义如下:
typedef struct Instruction{ int op; int arg;} Instruction;
op为操作符,arg为op对应的操作数,op可能是无参数操作码,所以arg可能无用。
sasm.c中get_op函数用于更具入参const char* s返回对应的实际的op_type,即是从op_desc到op_type的映射。
/* get op from its string desc */int get_op( const char *s ){ int i = 0; for( ; op_desc[i] != 0; ++i ) { if( strcmp( op_desc[i], s ) == 0 ) { return i; } } return opInvalid;}
get_op函数根据const char* s的值,依次检测与op_desc中的字符串是否匹配,如果匹配则返回对应的索引值,如果不匹配则返回opInvalid,索引值i即是对应于op_type枚举值。
/* get the op code arg(operand) count */int get_operand_count( int op ){ int ret; switch( op ) { case opLdc: case opJlt: case opJle: case opJgt: case opJge: case opJeq: case opJne: case opJmp: ret = 1; break; default: ret = 0; } return ret;}
get_operand_count函数返回op需要的操作数个数。操作符的操作数个数可以参考上表列举的。这里,op对应的操作数个数只有两种情况:0和1。
void read_asm(){ char line[256]; char op_str[32]; unsigned short op; int arg_c; int arg; unsigned short loc; while( !feof( fp_in ) ) { fgets( line, sizeof( line ) - 1, fp_in ); sscanf( line, "%d%s", (int*)&loc, op_str ); op = (unsigned short) get_op( op_str ); arg_c = get_operand_count( op ); if( arg_c > 0 ) { char *s = strstr( line, op_str ); s = &s[strcspn( s, " \t" )+1]; arg = atoi( s ); } else { arg = 0; } i_mem[loc].op = op; i_mem[loc].arg = arg; }}
read_asm函数用于读取汇编代码。在读取汇编代码的过程中,根据当前行的op_str值和get_op函数得到操作符op,并由get_operand_count函数来决定是否读取对应的操作数,将读取的指令复制给的指令结构体。
read_asm包含了两个字符串处理函数:strstr和strcspn:
函数
原型
功能
参考
strstr
char* strstr(char* str1, char* str2);
从str1中查找是否有字符串str2,如果有则返回str1中str2起始位置的指针;如果没有,则返回0
百度百科
CPLUSPLUS
ZHWEN
strcspn
size_t strcspn(const char* s1, const char* s2);
顺序在s1中搜寻与s2中字符的第一个相同字符,包括结束符0,如果存在,则返回该字符在s1中出现的位置;如果不存在,则返回s1的长度
百度百科
CPLUSPLUS
ZHWEN
关于字符串的函数,还有:strpbrk、strspn、strncmp等。
void output(){ int loc = 0; int arg_c; for( ; i_mem[loc].op != opHalt; ++ loc ) { char op = (char) i_mem[loc].op; fwrite( &op, sizeof( char ), 1, fp_out ); /* op */ arg_c = get_operand_count( op ); if( arg_c > 0 ) { int arg = i_mem[loc].arg; fwrite( &arg, sizeof( arg ), 1, fp_out ); /* arg */ } }}
output函数将指令结构体数组中的指令逐个扫描,取操作码op将其输出,并根据op由get_operand_count函数得到op对应的操作数个数,如果有操作数,则将对应的操作数输出。
测试函数:
int main( int argc, char **argv ){ if( argc < 2 ) { fprintf( stderr, "Usage:%s <filename>\n", argv[0] ); exit( -1 ); } fp_in = fopen( argv[1], "r" ); if( fp_in == 0 ) { fprintf( stderr, "Open %s failed\n", argv[1] ); exit( -1 ); } { char output[256] = { 0 }; int l = strcspn( argv[1], "." ); strncpy( output, argv[1], l ); strcat( output, ".sm" ); fp_out = fopen( output, "wb" ); if( fp_out == 0 ) { fprintf( stderr, "Open %s failed\n", output ); exit( -1 ); } } read_asm(); output(); fclose( fp_in ); fclose( fp_out ); return 0;}
main函数先检测argc,截取argv[1]的文件名,并补上.sm后缀名,.sm文件是二进制文件,用于虚拟机的执行。输入输出文件都没问题后,调用read_asm和output函数将汇编代码输入,在输入的过程中利用get_op函数将汇编代码转换为二进制代码,然后利用output函数将二进制代码输出到文件中。最后,将输入输出文件都关闭。
总结
本文是对一汇编器源码进行了剖析,代码中的数据结构主要是:
op_desc:字符串数组,定义了汇编代码的指令字符串
op_type:枚举类型,标识实际的指令,由于是枚举类型,其值为0、1、2、……,指令本身的值即为0、1、2、……
Instruction:指令结构体,用来定义一个指令结构体数组。其包含两个元素:op和arg,op表示操作符,arg表示操作数,由于op最多只有一个操作数,所以arg满足要求。
代码中的一些函数:
get_op:根据字符串形式的汇编代码得到实际的指令值int型,实际也对应于枚举类型的op_type。
get_operand_count:根据实际的指令码op得到其对应的操作数个数。
read_asm:读取汇编代码,在读取汇编代码的过程中将其转换为对应的二进制代码,一个指令码占1个字节,如果有参数,其参数占4个字节大小。将指令码和操作数保存到Instruction结构体数组i_mem。
out_put:将i_mem中的指令,如果指令有操作数,则将操作数一并输出。
main:检测参数是否合法,如果合法且输入输出文件无误,则将汇编代码读入并转化,并将二进制代码输出到文件中。
以上是对该汇编器的源码剖析,接下来我们按照汇编器的实现原理,编写一个自己的汇编器,另外对反汇编器的源码进行学习,并实现一个自己版本的反汇编器。
- 汇编器源码剖析
- 汇编器源码剖析
- 反汇编器源码剖析
- AS汇编器源码剖析-第4章-编译一行汇编
- XVID 编码器源码剖析(包含SSE2汇编)
- AS汇编器源码剖析-第1章-Arm操作码
- AS汇编器源码剖析-第2章-Armoprand操作数
- AS汇编器源码剖析-第3章-指令字符的识别
- AS汇编器源码剖析-第5章-用eclipse编译调试AS
- 《stl源码剖析》剖析
- 《STL源码剖析》-内存适配器(二)配置器剖析
- Fasm汇编器源码学习中
- 【源码】ArrayList源码剖析
- 【源码】LinkedList源码剖析
- 【源码】HashMap源码剖析
- 【源码】HashMap源码剖析
- 【源码】Hashtable源码剖析
- 【源码】LinkedHashMap源码剖析
- Impala:新一代开源大数据分析引擎
- Android的SQLite学习及使用方法
- Java数据结构--跟着api学数据结构--LinkedList
- st2---cocos2dx
- Word Break II
- 汇编器源码剖析
- emacs在mac上使用
- 百度实习面试
- LeetCode——Count and Say
- Leetcode: Trapping Rain Water
- git reset-----git 本地仓库回退
- 锐捷5.20代理检测之ACK响应报文检测
- c和指针 03
- misc_register、 register_chrdev 的区别总结