这是2021暑假短学期计算机系统概论实验部分的最后一个lab,lab有两个选择:assembler或者executer,我在做lab时对LC3的数据通路还不太熟悉,故先选择写了assembler,后面有时间了把executer补上。

理论基础

简易汇编器的理论部分比较简单,就是汇编的两遍扫描(first pass and second pass),除此之外,好像并没有什么新奇的方法和知识(?)

First Pass

第一遍扫描的作用是建立符号表,也即标签(label)和地址的一一对应关系。

LABEL Address
Kenshin x300F
Is x4000
Handsome xEFFF

Second Pass

第二遍扫描就是对指令逐条分析并生成机器码。

实现细节

一些准备工作

考虑到要对LC3的每一条指令的汇编形式进行翻译,所以可以考虑使用switch-case语句作为翻译部分的主体结构,因此对于指令集可以考虑enum枚举类型。

  1. enum ins {
  2. BR = 0, ADD, LD, ST, JSR,
  3. AND, LDR, STR, RTI, NOT,
  4. LDI, STI, JMP, RET, LEA, TRAP
  5. };

同时建立指令名、伪指令、部分Trap命令字典用于索引。

  1. static char* dir[] = {
  2. ".ORIG", ".END", ".BLKW", ".STRINGZ", ".FILL"
  3. };
  4. static char* pseudo[] = {
  5. "HALT", "IN", "OUT", "GETC", "PUTS","PUTSP"
  6. };

对于符号表的储存,用结构数组,

  1. struct label {
  2. char symbol[60];
  3. int address;
  4. };
  5. struct label SymbolTable[MAXMEM];

开始处理!

预处理

首先对读入的汇编代码转化为大写形式,注意.STRINGZ指令内的字符串要保留原始状态不转换。

  1. for (int i = 0; i < asmLines; i++) {
  2. strcpy(temp, AsmCode[i]);
  3. LowerToUpper(temp);
  4. if (strstr(temp, ".STRINGZ") == NULL) { //not .STRINGZ instruction
  5. LowerToUpper(AsmCode[i]);
  6. }
  7. else {
  8. for (int j = 0; j < strlen(AsmCode[i]); j++) {
  9. if (AsmCode[i][j] != '\"') { //match with '"', do not convert
  10. if (AsmCode[i][j] >= 'a' && AsmCode[i][j] <= 'z') {
  11. AsmCode[i][j] = AsmCode[i][j] - 'a' + 'A';
  12. }
  13. }
  14. else break;
  15. }
  16. }
  17. } //convert to upper case for check

Important
现在遇到了最重要的一个问题,如何将一条指令里的字段读出来并且进行分析?以LDRADD指令为例,简要介绍一下我的处理方法。

  1. HERE LDR R0, R1, #0
  2. ADD R0, R1 ,#5

可以注意到指令的操作数之间以,`分割,因此可以考虑对一条指令用strtok`函数进行分割,对于取出的各字段(标签、指令头、操作数)再进行分析。也即,

LABEL Instruction arg1 arg2

例如LDR指令在用strtok进行切割后,就会产生HERE,LDR,R0,R1,#0五个字段,ADD指令在切割后会产生ADD,R0,R1,#5四个字段,再具体分析即可。

建立符号表

进行第一遍扫描,借用中心思想,可以将一条指令切割出的第一个字段进行分析,若和字典中任何词条匹配,说明不是标签,反之则是标签。这里还要正确计算.STRINGZ.BLKW占多个位置时的标签地址。

  1. //part of first pass
  2. for (i = 1; i < asmLines - 1; i++) {
  3. if (IfRealLabel(AsmCode[i])) {
  4. strcpy(copy, AsmCode[i]);
  5. CodeToken = strtok(copy, " ");
  6. SymbolTable[j].address = LC;
  7. strcpy(SymbolTable[j].symbol, CodeToken); //construct symbol table
  8. j++;
  9. }
  10. }

扫描生成机器码

继续沿用中心思想,如3~13行所示,取出指令头和操作数以备分析。

  1. for (i = 1; i < asmLines - 1; i++, k++) {
  2. PC = LC + 1;
  3. //get string of instruction
  4. strcpy(copy, AsmCode[i]);
  5. opcode = strtok(copy, " ,");
  6. if (IfRealLabel(AsmCode[i])) {
  7. opcode = strtok(NULL, " ,");
  8. }
  9. do {
  10. arg[j] = strtok(NULL, " ,");
  11. j++;
  12. } while (arg[j - 1] != NULL); //get all arguments
  13. j = 0; //set counter to 0
  14. }

主体分析用switch-case进行分析,对取出的指令头和操作数逐个分析并转化成对于机器码,在一条完整指令分析完毕后将机器码字段进行连接形成完整指令,这里只列出两条指令的分析代码段,完整C文件会贴在文末。

  1. //part of second pass
  2. switch (WhichIns(opcode)) {
  3. case BR:
  4. opcode = strtok(opcode, "BR"); //get nzp bits
  5. if (opcode == NULL || strcmp(opcode, "NZP") == 0) strcpy(nzpBits, "111");
  6. else if (strcmp(opcode, "NZ") == 0) strcpy(nzpBits, "110");
  7. else if (strcmp(opcode, "NP") == 0) strcpy(nzpBits, "101");
  8. else if (strcmp(opcode, "ZP") == 0) strcpy(nzpBits, "011");
  9. else if (strcmp(opcode, "N") == 0) strcpy(nzpBits, "100");
  10. else if (strcmp(opcode, "Z") == 0) strcpy(nzpBits, "010");
  11. else if (strcmp(opcode, "P") == 0) strcpy(nzpBits, "001");
  12. if (*arg[0] != '#') {
  13. PCoffset9 = GetLabelAddress(arg[0]) - PC;
  14. arg[0] = DecToBinString(PCoffset9, 9);
  15. }
  16. else arg[0] = DecToBinString(StringToNum(arg[0]), 9);
  17. strcat(MachCode[k], "0000");
  18. strcat(MachCode[k], nzpBits);
  19. strcat(MachCode[k], arg[0]);
  20. break;
  21. case ADD:
  22. arg[0] = RegToBinString(arg[0]);
  23. strcpy(dst, arg[0]);
  24. arg[1] = RegToBinString(arg[1]);
  25. strcpy(src1, arg[1]);
  26. strcat(MachCode[k], "0001");
  27. strcat(MachCode[k], dst);
  28. strcat(MachCode[k], src1);
  29. if (strncmp(arg[2], "R", 1) == 0) { //ADD DR, SR1, SR2
  30. arg[2] = RegToBinString(arg[2]);
  31. strcpy(src2, arg[2]);
  32. strcat(MachCode[k], "000");
  33. strcat(MachCode[k], src2);
  34. }
  35. else { //ADD DR SR1, imm5
  36. arg[2] = DecToBinString(StringToNum(arg[2]), 5);
  37. strcpy(imm5, arg[2]);
  38. strcat(MachCode[k], "1");
  39. strcat(MachCode[k], imm5);
  40. }
  41. break;
  42. /*other cases*/
  43. }

反思与总结

由于是简易汇编器(实际上就只会翻译),目前只能对一对.ORIG.END构成的LC3汇编代码进行翻译,无法对有.EXTERNAL的多文件进行翻译。也不能处理含注释的汇编代码,代码实现也肯定有很多丑陋的地方。总的来说,还是一个很粗糙的东西。
不过在写的过程中对C处理字符串的库函数有了进一步的了解,对汇编过程有了进一步的理解。除此之外,在观摩了@sloth大蟒蛇re正则表达式实现的汇编器(代码行数是我的1/3!)后,我决定弃暗投明,这就去学大蟒蛇!
main.c