汇编器源码剖析
?本文我們對一匯編器源代碼進行剖析,了解匯編器實現(xiàn)原理,進而我們根據(jù)樣例,自己實現(xiàn)一個匯編器。實現(xiàn)自己版本的匯編器放在另一篇中,本文主要是對別人的源碼進行剖析。
???????? 本文源代碼是來自Kevin Lynx的《基于棧的虛擬機的實現(xiàn)》中關(guān)于實現(xiàn)一個堆棧虛擬機中附帶了匯編器的實現(xiàn),源碼下載地址如下:source code。由于本人對匯編器比較感興趣,所以對其進行如下剖析。
???????? 匯編器主要是一個sasm.c源文件。
???????? 其中,一開始定義了一個const char* op_desc[],op_desc是一個數(shù)組,其元素類型是const char*,即op_desc是一個字符串數(shù)組,其用于存儲匯編操作符。
/* 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};? ? ? ? ?下面我們對各個操作符進行逐一介紹:
?
| 操作符 | 操作數(shù)個數(shù) | 說明 |
| HALT | 0,HALT | 終止 |
| IN | 0,IN | 從標準輸入中讀入整型值并壓棧 |
| OUT | 0,OUT | 從棧中彈出,從標準輸出 |
| ADD | 0,ADD | 從棧中彈出a,彈出b,計算b+a,并將結(jié)果壓入棧中 |
| SUB | 0,SUB | 從棧中彈出a,彈出b,計算b-a,并將結(jié)果壓入棧中 |
| MUL | 0,MUL | 從棧中彈出a,彈出b,計算b*a,并將結(jié)果壓入棧中 |
| DIV | 0,DIV | 從棧中彈出a,彈出b,計算b/a,并將結(jié)果壓入棧中 |
| DUP | 0,DUP | 壓入棧頂值的拷貝 |
| LD | 0,LD | 從棧中彈出地址,并壓入改地址里的整數(shù)值 |
| 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的對應關(guān)系如下:
| 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 | 無效參數(shù) |
?? ? ? ? ?指令為一個結(jié)構(gòu)體,其定義如下:
typedef struct Instruction{int op;int arg;} Instruction;? ? ? ? ?op為操作符,arg為op對應的操作數(shù),op可能是無參數(shù)操作碼,所以arg可能無用。
???????? sasm.c中g(shù)et_op函數(shù)用于更具入?yún)onst 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函數(shù)根據(jù)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函數(shù)返回op需要的操作數(shù)個數(shù)。操作符的操作數(shù)個數(shù)可以參考上表列舉的。這里,op對應的操作數(shù)個數(shù)只有兩種情況: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函數(shù)用于讀取匯編代碼。在讀取匯編代碼的過程中,根據(jù)當前行的op_str值和get_op函數(shù)得到操作符op,并由get_operand_count函數(shù)來決定是否讀取對應的操作數(shù),將讀取的指令復制給的指令結(jié)構(gòu)體。
???????? read_asm包含了兩個字符串處理函數(shù):strstr和strcspn:
| 函數(shù) | 原型 | 功能 | 參考 |
| 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中字符的第一個相同字符,包括結(jié)束符0,如果存在,則返回該字符在s1中出現(xiàn)的位置;如果不存在,則返回s1的長度 | 百度百科 CPLUSPLUS ZHWEN |
? ? ? ? ?關(guān)于字符串的函數(shù),還有: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函數(shù)將指令結(jié)構(gòu)體數(shù)組中的指令逐個掃描,取操作碼op將其輸出,并根據(jù)op由get_operand_count函數(shù)得到op對應的操作數(shù)個數(shù),如果有操作數(shù),則將對應的操作數(shù)輸出。
?
???????? 測試函數(shù):
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函數(shù)先檢測argc,截取argv[1]的文件名,并補上.sm后綴名,.sm文件是二進制文件,用于虛擬機的執(zhí)行。輸入輸出文件都沒問題后,調(diào)用read_asm和output函數(shù)將匯編代碼輸入,在輸入的過程中利用get_op函數(shù)將匯編代碼轉(zhuǎn)換為二進制代碼,然后利用output函數(shù)將二進制代碼輸出到文件中。最后,將輸入輸出文件都關(guān)閉。
?
?????????總結(jié)
???????? 本文是對一匯編器源碼進行了剖析,代碼中的數(shù)據(jù)結(jié)構(gòu)主要是:
???????? op_desc:字符串數(shù)組,定義了匯編代碼的指令字符串
???????? op_type:枚舉類型,標識實際的指令,由于是枚舉類型,其值為0、1、2、……,指令本身的值即為0、1、2、……
???????? Instruction:指令結(jié)構(gòu)體,用來定義一個指令結(jié)構(gòu)體數(shù)組。其包含兩個元素:op和arg,op表示操作符,arg表示操作數(shù),由于op最多只有一個操作數(shù),所以arg滿足要求。
???????? 代碼中的一些函數(shù):
???????? get_op:根據(jù)字符串形式的匯編代碼得到實際的指令值int型,實際也對應于枚舉類型的op_type。
???????? get_operand_count:根據(jù)實際的指令碼op得到其對應的操作數(shù)個數(shù)。
???????? read_asm:讀取匯編代碼,在讀取匯編代碼的過程中將其轉(zhuǎn)換為對應的二進制代碼,一個指令碼占1個字節(jié),如果有參數(shù),其參數(shù)占4個字節(jié)大小。將指令碼和操作數(shù)保存到Instruction結(jié)構(gòu)體數(shù)組i_mem。
???????? out_put:將i_mem中的指令,如果指令有操作數(shù),則將操作數(shù)一并輸出。
???????? main:檢測參數(shù)是否合法,如果合法且輸入輸出文件無誤,則將匯編代碼讀入并轉(zhuǎn)化,并將二進制代碼輸出到文件中。
?
???????? 以上是對該匯編器的源碼剖析,接下來我們按照匯編器的實現(xiàn)原理,編寫一個自己的匯編器,另外對反匯編器的源碼進行學習,并實現(xiàn)一個自己版本的反匯編器。
總結(jié)