设计功能描述 使用Java编写的将MiniSys汇编程序转换到 Minisys 体系机器码的汇编器,同时兼具链接功能。
模块功能
main.java:程序入口,实现命令行,-l表示是否与bios,中断程序链接。例: $ java -jar minisys-java.jar -l
Assembler.java:实现对数据段、指令段的汇编,先对指令进行宏展开,将指令与label分离并储存label以及其对应的地址,遍历所有指令进行汇编,最后输出AsmProgram(包含了处理后的数据段、指令段数据)。
MiniInstructions.java:实现57条指令,保存其相应的正则格式,调用toBinary方法即可实现指令转二进制。
Converter.java:将输入的AsmProgram转为相应的Coe文件,也实现了将数据段、指令段的Coe文件合并生成可以直接在开放板上运行的serial。
Linker.java:根据MiniSys的MEM布局(BIOS 区域、用户程序区域、中断处理程序入口、中断处理程序)对二进制数据实现内存的布局、地址的重定位。
MacroExpansionRules:实现宏指令,可以将push、pop、jg、jge、jl、jle、move宏指令转为常用指令。
Instruction.java:实现了指令类的底层逻辑,为了实现对57条指令的兼容,包含了自定义正则、指令componentList自定义、toBinary自定义(每种指令的构成、正则格式都是不同的,这样实现简单易用)。
Register.java:实现了寄存器的转换,可以对32个通用寄存器进行映射。
Utils:通用工具类,实现了label转二进制(Assember里已经保存了label的数据),变量转二进制以及各种通用的十六转二进制,十进制转二进制,计算偏移地址,获取数据类型大小等工具。
设计的主要特色 宏指令支持 通过对宏指令转换为MiniSys支持的57条常用指令,实现对push、pop、jg、jge、jl、jle、move宏指令的支持。
内存的布局、地址的重定位 链接器根据MiniSys的MEM布局(BIOS 区域、用户程序区域、中断处理程序入口、中断处理程序)对二进制数据实现内存的布局、地址的重定位,并生成可以直接在开放板上使用的serial文件。
易用性 在Instruction类实现中,由于正则、componentList、toBinary都可以自定义。因此,仅需在MinisysInstructions类中加入新指令对应的正则、指令格式、二进制转译规则,即可实现对新指令的支持。
体系结构
汇编器的结构如图所示:Assembler接收一个包含汇编代码的文件(.asm格式)。首先,进行宏指令扩展,并在此过程中处理标签(label),将处理后的结果存储在TextSegLabel中。随后,逐行处理指令,并将处理后的信息整合到AsmProgram中。AsmProgram包括数据段(DataSeg)和指令段(TextSeg)。数据段保存了所有变量的名称、类型以及相应的值,而指令段保存了所有指令的详细信息。
设计与特色概述 指令类的底层逻辑:Instruction.java
该类涵盖了当前指令的各个方面,包括指令类型、描述、指令名称、指令正则模式,以及构成列表(List<InstructionComponent>)。在这个上下文中,InstructionComponent用于定义指令的构成。具体而言,MIPS指令在图示中规定了其格式和含义,而InstructionComponent则指明了从lBit到rBit位置的二进制数据的类型type。toBinary方法定义了如何生成二进制val值,若不需要生成,则为null。每个指令包含多个InstructionComponent,如图中所示,其中第一个和最后一个是固定的,而中间的三个通过toBinary方法生成相应的二进制val值。举例来说,通过正则表达式解析输入的字符串,然后提取值并将其转换为二进制,最后填入相应的位置。这种设计能够有效地实现指令的解析和二进制表示,使其在汇编器中发挥作用。
1 2 3 4 5 6 7 8 public String toBinary () { for (InstructionComponent component : components) { if (component.getVal().trim().isEmpty()) { throw new IllegalStateException ("尝试将不完整的指令转为2或16进制。" ); } } return components.stream().map(InstructionComponent::getVal).reduce(String::concat).orElse("" ); }
在这个类中,实现了toBinary方法,其主要功能是提取之前InstructionComponent中存储的已转换为二进制的val值,并将它们合并成一个二进制流。值得注意的是,此处的toBinary方法与前文中Component中的toBinary方法并不相同。这里的toBinary方法专注于将Component中通过toBinary生成的二进制值(如果toBinary为null,则直接使用val)整合成一个二进制流。
通用指令的实现:MiniInstructions.java 该类为Instruction的上层类,构建了57条MIPS通用指令。
1 2 3 4 5 6 7 newInstruction("add" , "按字加法" , "(rd)←(rs)+(rt)" , paramPattern(3 ), new InstructionComponent []{ new InstructionComponent (31 , 26 , "op" , null , InstructionComponentType.FIXED, "000000" ), new InstructionComponent (25 , 21 , "rs" , m -> Register.regToBin(m.group(2 )), InstructionComponentType.REG, "" ), new InstructionComponent (20 , 16 , "rt" , m -> Register.regToBin(m.group(3 )), InstructionComponentType.REG, "" ), new InstructionComponent (15 , 11 , "rd" , m -> Register.regToBin(m.group(1 )), InstructionComponentType.REG, "" ), new InstructionComponent (10 , 6 , "shamt" , null , InstructionComponentType.FIXED, "00000" ), new InstructionComponent (5 , 0 , "func" , null , InstructionComponentType.FIXED, "100000" )
根据上图所示,通过newInstruction方法构建新指令。此方法接受指令名称、指令描述、指令正则和指令构成列表等参数,其中指令构成列表的格式按照MIPS指令的规范输入。这使得我们能够轻松地构建新的指令。值得注意的是,toBinary方法,在已经输入了val或者type为FIXED的情况下会为null。而在其他情况下,它定义了如何处理指令正则匹配后的字符串。
1 2 3 4 new InstructionComponent (31 , 26 , "op" , null , InstructionComponentType.FIXED, "100100" ),new InstructionComponent (25 , 21 , "rs" , m -> Register.regToBin(m.group(3 )), InstructionComponentType.REG, "" ),new InstructionComponent (20 , 16 , "rt" , m -> Register.regToBin(m.group(1 )), InstructionComponentType.REG, "" ),new InstructionComponent (15 , 0 , "offset" , m -> Utils.varToAddrBin(m.group(2 ), 16 , true ), InstructionComponentType.OFFSET, "" )
例如,在处理add指令时,正则匹配后的参数为寄存器,因此,我们需要对matcher m的group进行regToBin的操作。如果是匹配后是offset的参数,则需要varToAddrBin或labelToBin,immediate则为literalToBin。同理,可以得到其他56条指令。
Register.java:寄存器的实现 1 2 3 4 5 6 7 8 9 10 public static String regToBin (String reg) { reg = reg.replace("$" , "" ).trim(); int regNumber; if (reg.matches("\\d+" )) { regNumber = Integer.parseInt(reg); } else { regNumber = indexOfRegister(reg); } return Utils.decToBin(regNumber, 5 ,false ); }
实现寄存器功能并不复杂。通过接收输入的寄存器名称(reg),我们可以轻松找到相应的寄存器索引(Index),然后通过decToBin方法将其转换为二进制表示,最终进行输出。
MacroExpansionRules.java:宏指令的实现 1 2 expansionRules.put("push" , new MacroExpansionRule ("^push\\s+(\\$\\w{1,2})$" , new String []{"addi $sp, $sp, -4" , "sw ${RegExp.$1}, 0($sp)" })); expansionRules.put("pop" , new MacroExpansionRule ("^pop\\s+(\\$\\w{1,2})$" , new String []{"lw ${RegExp.$1}, 0($sp)" , "addi $sp, $sp, 4" }));
1 2 3 4 5 6 7 8 9 10 11 12 13 private static String replaceGroups (Matcher matcher, String input) { if (input.contains("${RegExp.$3}" )) { input = input.replace("${RegExp.$3}" , getGroup(matcher, 3 )); } if (input.contains("${RegExp.$2}" )) { input = input.replace("${RegExp.$2}" , getGroup(matcher, 2 )); } if (input.contains("${RegExp.$1}" )) { input = input.replace("${RegExp.$1}" , getGroup(matcher, 1 )); } return input; }
宏指令的展开过程首先涉及对相关指令的参数获取。在获取到这些参数之后,通过对这些参数的合成操作,生成新的指令序列,这些指令即为通用的MIPS指令。最终,将这一新生成的指令序列返回,这相当于将原始宏指令在语义上替换为相应的MIPS通用指令。
Assembler.java:汇编功能的主要模块 该模块主要涉及两个关键方面的处理,即数据段和指令段,这两者在该模块中进行详细处理。
数据段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 String startAddr = asm.get(0 ).split("\\s+" ).length != 1 ? asm.get(0 ).split("\\s+" )[1 ] : "0" ;if (asm.get(0 ).split("\\s+" ).length > 2 ) { throw new RuntimeException ("数据段首声明非法" ); } List<DataSegVarComp> comps = new ArrayList <>(); vars = new ArrayList <>();String name = null ;int i = 1 ;int addr;if (startAddr.startsWith("0x" )){ addr=Integer.parseInt(startAddr.substring(2 ),16 ); }else { addr=Integer.parseInt(startAddr); }AtomicInteger nextAddr = new AtomicInteger (addr);
首先,我们考虑数据段。数据段的处理分为三个状态:初始化、新增变量、变量中继。在初始化状态中,首先判断文件的第一行是否包含.data,然后获取起始地址startAddr和下一个地址nextAddr。
1 2 Matcher varStartMatcher = VAR_START_PATTERN.matcher(asm.get(i));Matcher varContdMatcher = VAR_CONTD_PATTERN.matcher(asm.get(i));
如上图所示,通过正则表达式判断当前状态是新增变量还是变量中继状态。在新增变量状态中,即处理最初的变量声明,将上一次获取的信息存入变量集合vars中(如果上一次没有信息,则跳过)。然后,创建一个DataSegVar对象,将正则匹配得到的参数填入name和type字段,而最后一个参数value由于asm文件中可能使用逗号分隔多个变量,需要使用parseInitValue方法来处理。parseInitValue方法能够解析并存储数据到DataSegVar中。下图即为parseInitValue的调用,输入type和Matcher的最后一个参数,获得到的数据将其存入DataSegVar中。
1 2 3 4 parseInitValue(type, varStartMatcher.group(3 )).forEach(val -> { finalComps.add(new DataSegVarComp (type, val.trim())); nextAddr.addAndGet(size * (Objects.equals(type, "ascii" ) ? val.length() : 1 )); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 private static List<String> parseInitValue (String type, String init) { assert !(!Objects.equals(type, "ascii" ) && init.contains("\"" )) : "字符串型数据只能使用.ascii类型" ; init = init.trim(); assert init.charAt(0 ) != ',' && init.charAt(init.length() - 1 ) != ',' : "数据初始化值头或尾有非法逗号" ; if (!Objects.equals(type, "ascii" )) { return Stream.of(init.split("\\s*," )).toList(); } else { boolean inQuote = false ; boolean nextEscape = false ; List<String> res = new ArrayList <>(); StringBuilder buf = new StringBuilder (); char prev = '\0' ; for (int i = 0 ; i < init.length(); i++) { char ch = init.charAt(i); if (!inQuote && Character.isWhitespace(ch)) { continue ; } if (ch == '"' ) { if (nextEscape) { assert inQuote : "有非法字符出现在引号以外" ; buf.append('"' ); nextEscape = false ; } else { inQuote = !inQuote; } } else if (ch == '\\' ) { assert inQuote : "有非法字符出现在引号以外" ; if (nextEscape) { buf.append('\\' ); nextEscape = false ; } else { nextEscape = true ; } } else if (ch == ',' ) { if (inQuote) { buf.append(',' ); nextEscape = false ; } else { assert prev != ',' : "数据初始化值存在连续的逗号分隔" ; res.add(buf.toString()); buf = new StringBuilder (); } } else { assert inQuote : "有非法字符出现在引号以外" ; if (nextEscape) { buf.append(StringProcessor.unraw(ch)); } else { buf.append(ch); } nextEscape = false ; } prev = ch; } res.add(buf.toString()); return res; } }
以上为parseInitValue的具体实现,此函数接受两个参数:“type”和“init”,type是变量类型,init是变量的初始值。 “parseInitValue”函数首先声明,如果变量类型不是“ascii”,则初始值不应包含双引号字符。判断初始值是否以逗号开头或结尾。 如果变量类型不是“ascii”,则函数会用逗号分隔初始值,并返回一个修剪后的值数组。 如果变量类型为“ascii”,则函数将进入更复杂的解析过程。 它初始化几个变量以跟踪解析状态,包括它当前是否在带引号的字符串(“inQuote”)内,是否应转义下一个字符(“nextEscape”)、结果数组(“res”)、当前字符串的缓冲区(“buf”)和前一个字符的缓冲区。 然后,函数在初始值中的每个字符上进入一个循环。根据当前字符和解析状态,它会更新状态变量并将字符添加到缓冲区或结果数组中。 该函数使用“assert”函数来确保初始值的语法正确,如果遇到非法字符或序列,则会引发错误。
举例说明:如果输入的是 a: .ascii "hello","world"
则name为a,type为ascii,第3个参数会交给parseInitValue处理成包含”hello”、”world”的List。
变量中继状态处理除了不会新建一个DataSegVar与变量开始状态基本一致。
指令段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public static List<String> expandMacros (List<String> asm, List<Integer> lineno) { List<String> expandedAsm = new ArrayList <>(asm); String[] macros = MacroExpansionRules.expansionRules.keySet().toArray(new String [0 ]); int bias = 0 ; for (int i = 0 ; i < asm.size(); i++) { String v = asm.get(i); String labelPreserve = "" ; Pattern labelPattern = Pattern.compile("^(\\w+:)\\s*([\\w\\s$]+)$" ); Matcher lableMatcher = labelPattern.matcher(v); if (lableMatcher.matches()) { labelPreserve = lableMatcher.group(1 ); v = lableMatcher.group(2 ).trim(); } for (String macro : macros) { Pattern pattern = MacroExpansionRules.expansionRules.get(macro).pattern; Matcher m = pattern.matcher(v); if (m.matches()) { String[] replacer = MacroExpansionRules.expansionRules.get(macro).replace(m); replacer[0 ] = labelPreserve + " " + replacer[0 ]; expandedAsm.remove(i + bias); expandedAsm.addAll(i + bias, Arrays.asList(replacer)); lineno.remove(i + bias); lineno.addAll(i + bias, new ArrayList <>(Collections.nCopies(replacer.length, lineno.get(i + bias)))); bias += replacer.length - 1 ; break ; } } } return expandedAsm; }
在开始指令段的处理之前需要对其进行宏指令扩展,如上图所示,expandMacros
函数用于在汇编语言源代码中扩展宏。该函数接收两个参数:asm
(汇编语言指令的数组)和lineno
(对应于指令的行号数组)。
函数开始时,创建了asm
数组的副本,并初始化了几个变量,包括macros
(从expansionRules
对象中获取的键的数组),以及bias
(用于跟踪由于替换指令为扩展宏而引起的偏移)。
然后,函数遍历asm
数组中的每个指令。对于每个指令,它检查是否匹配LabelPattern
正则表达式。如果匹配,它会保留标签部分并修剪指令的其余部分。
接下来,函数检查指令是否匹配expansionRules
对象中任何宏的模式。如果匹配,它获取匹配宏的替换器,将保留的标签添加到替换器中的第一条指令前,然后在asm
数组中用扩展宏替换原始指令。它还更新lineno
数组以反映宏扩展后的新行号,并更新bias
以考虑数组长度的变化。
最后,函数返回所有宏都已扩展的asm
数组。前面宏指令已经介绍过,这里调用replace函数,并传入正则匹配后的参数即可返回替换的指令。再将这些处理后的指令插入到原指令中并更新lineno。
处理宏指令后正式加入指令段处理,和数据段的处理相同,先判断是否存在格式问题,再初始化开始地址startAddr和下一个地址nextAddr。与之不同的是,需要对label进行预处理:
1 2 3 4 5 6 7 8 9 10 if (labelMatcher.matches()) { labels.add(new TextSegLabel (labelMatcher.group(1 ), insLineno, Utils.getOffsetAddr(startAddr, (insLineno - 1 ) * Utils.sizeof("ins" )))); if (!labelMatcher.group(2 ).trim().isEmpty()) { insLineno++; } instructions.add(labelMatcher.group(2 )); } else { insLineno++; instructions.add(v); }
遍历所有的指令,对于符合label正则的,将label的地址、label的行数、label的名字
存入labels变量中。在进行这几部预处理后,指令段进入逐行转译阶段,逐行阶段由parseOneLine方法处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public static Instruction parseOneLine (String asm, int lineno) { Pattern pattern = Pattern.compile("^\\s*(\\w+)\\s*(.*)" ); Matcher matcher = pattern.matcher(asm); if (!matcher.matches()) { System.out.print("\"没有找到指令助记符,在代码第 " + lineno); } String symbol = matcher.group(1 ); int instructionIndex = -1 ; for (int i = 0 ; i < minisysInstructions.size(); i++) { if (minisysInstructions.get(i).getSymbol().equals(symbol)) { instructionIndex = i; break ; } } asm = Utils.serialString(matcher.group(2 )); pc += Utils.sizeof("ins" ); Instruction res = Instruction.newInstance(minisysInstructions.get(instructionIndex)); res.setSrc(symbol+" " +asm); for (InstructionComponent component : res.getComponents()) { if (component.getVal().trim().isEmpty()) { res.setComponent(component.getDesc(), component.toBinary(res.getInsPattern().matcher(asm))); } } return res; }
如上图所示,即为parseOneLine的具体实现,函数首先使用正则表达式从asm
字符串中提取助记符(指令的符号名称)。接下来,函数通过在MinisysInstructions
数组中查找其索引来检查助记符的有效性。
然后,函数使用serialString
函数从asm
字符串中删除所有空格,并将程序计数器(pc
)增加一个指令的大小。
接着,函数开始组装Instruction
对象。它根据在MinisysInstructions
数组中找到的指令创建Instruction
类的新实例。函数然后遍历指令的每个组件。对于每个不是指令二进制中的FIXED
的组件(即需要填充的变量),使用setComponent
方法将组件从toBinary
中获得的值设置为其二进制表示。
最后,函数返回组装的Instruction
对象。需要注意的是该语句,也是Instruction的核心。
1 component.toBinary(res.getInsPattern().matcher(asm))
该语句中的toBinary即为上面InstructionComponent中提到的toBinary方法,其功能是生成二进制表示。具体实现如下
1 2 3 4 5 6 public String toBinary (Matcher m) { if (toBinary != null && m.matches()) return toBinary.apply(m); else return "" ; }
在Java中,调用java.util.function.Function变量时需要使用apply方法,这一机制允许对存储的参数执行预先定义的操作。举例来说,在处理add指令中,需要进行如下操作:对25位到21位的二进制进行转换,可以通过m -> Register.regToBin(m.group(2))
来实现(其中m表示正则匹配对象,通过m.group(2)获取第二个参数,并对其执行寄存器转二进制的操作)。
这种机制的优势在于,通过正则表达式可以灵活地获取指令的各个参数。通过调用apply方法,将正则匹配后的参数传递给函数变量,可以轻松实现二进制的转换。这使得指令的处理过程更加灵活、模块化,能够适应不同指令格式的需求。
Linker.java 链接器的实现 Minisys 体系使用哈佛结构,指令 MEM 有 64 KB,按字节编址。因此,其地址范围为 0x00000000 ~ 0x0000FFFF。指令 MEM 布局如下:
地址
作用
0x00000000 ~ 0x00000499
BIOS 区域。大小为 500 H = 1280 D Byte,最多存放 1280 / 4 = 320 条指令。
0x00000500 ~ 0x00005499
用户程序区域。大小为 5000 H = 20480 D Byte,最多存放 20480 / 4 = 5120 条指令。
0x00005500 ~ 0x0000EFFF
空。
0x0000F000 ~ 0x0000F499
中断处理程序入口。大小为 500 H = 1280 D Byte,最多存放 1280 / 4 = 320 条指令。
0x0000F500 ~ 0x0000FFFF
中断处理程序。大小为 B00 H = 2816 D Byte,最多存放 2816 / 4 = 704 条指令 。
通过前述计算,我们能够得知指令的最高限制和位置分布。在链接器中,通过countIns方法计算asm文件中的指令个数。如果计算得到的指令个数超过了最高限制,将触发错误处理。反之,若未超过限制,则通过添加nop(空指令)进行补充,以使指令数量达到相应的地址要求。实现大概如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int userASMInsCount = countIns(userASM); assertLength(userASMInsCount, 5120 , "用户程序段过长。" );int userNopPadding = 5120 - userASMInsCount; ....其他区域代码 allProgram.append("# ====== User Application START ======\n" ); allProgram.append("# User Application Length = " ).append(userASMInsCount).append("\n" ); allProgram.append(userASM).append("\n" ); allProgram.append("# User Application Padding = " ).append(userNopPadding).append("\n" ); allProgram.append("nop\n" .repeat(userNopPadding)); allProgram.append("# ====== User Application END ======\n" );
Converter.java:Coe文件的生成 对于指令段,由textsegToCoe方法生成。该方法中主要实现如下:
1 2 3 4 5 6 7 8 for (Instruction ins : textSeg.getIns()) { StringBuilder buf = new StringBuilder (); for (InstructionComponent comp : ins.getComponents()) { buf.append(comp.getVal()); } coe.append(Utils.binToHex(buf.toString(), false )).append(",\n" ); lineno++; }
该方法的主要操作就是将之前InstrumentComponent中toBinary方法得到的val进行合并生成二进制表示。
使用说明
请在jar文件路径下,创建bios的文件,将src\snippet中3个asm文件放进bios中
如果没有asm文件,无法使用链接,既无法加上 “-l” 参数。
可以使用自己的bios文件,但需要改成相应的名字。
src\snippet中3个文件分别用来:bios引导系统程序入口、interpret-entry中断程序的入口、interpret-handler中断程序处理 。
jar的项目结构如下:
1 2 3 4 5 6 7 jar文件路径 │ miniasm-java.jar │ └─bios minisys-bios.asm minisys-interrupt -entry.asm minisys-interrupt -handler.asm
指令格式如下所示,其中 “-l” 为可选选项,表示是否链接。
1 $ java -jar minisys-java.jar <in_file> <out_dir> -l
<in_file> 表示输入的文件路径,<out_dir>表示输出文件路径,输出的文件如果没创建则会新建。如果输入的文件和jar在同一路径,则可以直接使用$ java -jar minisys-java.jar in.asm out -l
in.asm
可替换自己的asm文件名字,输出的文件会保持在当前目录的out文件夹里面。也可以直接使用绝对路径。
测试用例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 .DATA 0x00001000 buf: .WORD 0x000000ff ,0x55005500 buf2: .byte 1 .ascii "hello" .TEXT 0x00003456 start: addi $t0 , $zero , 0 lw $v0 , 20 ($t0 ) addi $t0 , $t0 , 4 lw $v1 , 20 ($t0 ) add $v0 , $v0 , $v1 addi $t0 , $t0 , 4 sw $v0 , 20 ($t0 ) j start ```
输入数据如上,输出的解析结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 textSeg: { startAddr: 13400 , Instruction:{ addi $t0 ,$zero ,0 Hex:20080000 lw $v0 ,20 ($t0 ) Hex:8 d020014 addi $t0 ,$t0 ,4 Hex:21080004 lw $v1 ,20 ($t0 ) Hex:8 d030014 add $v0 ,$v0 ,$v1 Hex:00431020 addi $t0 ,$t0 ,4 Hex:21080004 sw $v0 ,20 ($t0 ) Hex:ad020014 j start Hex:08000 d16 }, Labels: { start } }dataSeg: { startAddr: 0x00001000 ,vars:{ name: buf addr:4096 { type: word val:0x000000ff type: word val:0x55005500 } name: buf2 addr:4104 { type: byte val:1 type: ascii val:hello } } }
可以看到dataSeg里面存储了startAddr数据以及vars,vars中存储了具体的变量type以及val。
对于textSeg里面也存储了startAddr,对于Instruction也成功转译为二进制。