|
| 1 | +require 'erb' |
| 2 | +require 'fileutils' |
| 3 | + |
| 4 | +require_relative 'x86/isa' |
| 5 | + |
| 6 | +module CodeGen |
| 7 | + module X86 |
| 8 | + # The root directory of the project. |
| 9 | + ROOT = File.expand_path(File.join(__dir__,'..')) |
| 10 | + |
| 11 | + # The `lib/` directory of the project. |
| 12 | + LIB_DIR = File.join(ROOT,'lib') |
| 13 | + |
| 14 | + # The `lib/ronin/asm/x86/` directory. |
| 15 | + DIR = File.join(LIB_DIR,'ronin','asm','x86') |
| 16 | + |
| 17 | + # The `lib/ronin/asm/x86/instructions` directory. |
| 18 | + INSTRUCTIONS_DIR = File.join(DIR,'instructions') |
| 19 | + |
| 20 | + # |
| 21 | + # Represents an instruction file within |
| 22 | + # `lib/ronin/asm/x86/instructions/`. |
| 23 | + # |
| 24 | + class InstructionFile |
| 25 | + |
| 26 | + # The path to the `instruction.rb.erb` template file. |
| 27 | + TEMPLATE_PATH = File.join(__dir__,'x86','instruction.rb.erb') |
| 28 | + |
| 29 | + # The parsed ERB template. |
| 30 | + TEMPLATE = ERB.new(File.read(TEMPLATE_PATH), trim_mode: '-') |
| 31 | + |
| 32 | + # The x86 instruction metadata. |
| 33 | + # |
| 34 | + # @return [XML::Instruction] |
| 35 | + attr_reader :instruction |
| 36 | + |
| 37 | + # |
| 38 | + # Initializes the instruction file. |
| 39 | + # |
| 40 | + # @param [XML::Instruction] instruction |
| 41 | + # The x86 instruction metadata object. |
| 42 | + # |
| 43 | + def initialize(instruction) |
| 44 | + @instruction = instruction |
| 45 | + end |
| 46 | + |
| 47 | + # |
| 48 | + # Generates the Ruby file for the instruction. |
| 49 | + # |
| 50 | + # @param [XML::Instruction] instruction |
| 51 | + # The x86 instruction metadata object. |
| 52 | + # |
| 53 | + # @see #generate |
| 54 | + # |
| 55 | + def self.generate(instruction) |
| 56 | + new(instruction).generate |
| 57 | + end |
| 58 | + |
| 59 | + # |
| 60 | + # The base name for the instruction Ruby file. |
| 61 | + # |
| 62 | + # @return [String] |
| 63 | + # |
| 64 | + def filename |
| 65 | + "#{@instruction.name}.rb" |
| 66 | + end |
| 67 | + |
| 68 | + # |
| 69 | + # The absolute path for the instruction Ruby file. |
| 70 | + # |
| 71 | + # @return [String] |
| 72 | + # |
| 73 | + def path |
| 74 | + File.join(INSTRUCTIONS_DIR,filename) |
| 75 | + end |
| 76 | + |
| 77 | + # |
| 78 | + # Generates the instruction Ruby file. |
| 79 | + # |
| 80 | + def generate |
| 81 | + File.write(path,TEMPLATE.result(binding)) |
| 82 | + end |
| 83 | + |
| 84 | + private |
| 85 | + |
| 86 | + # |
| 87 | + # Outputs either `if` or `elsif` depending on whether the `index` is `0` |
| 88 | + # or greater than `0`. |
| 89 | + # |
| 90 | + # @param [Integer] index |
| 91 | + # @return ["if", "elsif"] |
| 92 | + # |
| 93 | + def if_elsif(index) |
| 94 | + if index == 0 then 'if' |
| 95 | + else 'elsif' |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + # |
| 100 | + # Builds a boolean expression that matches the operand types in an |
| 101 | + # `@operands` array. |
| 102 | + # |
| 103 | + # @param [Array<Operands>] operands |
| 104 | + # @return [String] |
| 105 | + # |
| 106 | + def operands_match_expression(operands) |
| 107 | + if operands.empty? |
| 108 | + "@operands.empty?" |
| 109 | + else |
| 110 | + [ |
| 111 | + "@operands.length == #{operands.length}", |
| 112 | + *operands.map.with_index { |operand,index| |
| 113 | + "@operands[#{index}].type == #{operand.type.inspect}" |
| 114 | + } |
| 115 | + ].join(' && ') |
| 116 | + end |
| 117 | + end |
| 118 | + |
| 119 | + # |
| 120 | + # Converts an operand index back into Ruby code. |
| 121 | + # |
| 122 | + # @param [ISA::OperandIndex] object |
| 123 | + # @return [String] |
| 124 | + # |
| 125 | + def operand_index(operand_index) = "@operands[#{operand_index.to_i}]" |
| 126 | + |
| 127 | + # |
| 128 | + # Formats a hex byte. |
| 129 | + # |
| 130 | + # @param [Integer] byte |
| 131 | + # @return [String] |
| 132 | + # |
| 133 | + def hex_byte(byte) = format("0x%.2x", byte) |
| 134 | + |
| 135 | + # |
| 136 | + # Formats an integer as binary. |
| 137 | + # |
| 138 | + # @param [Integer] integer |
| 139 | + # @param [Integer, nil] bits |
| 140 | + # @return [String] |
| 141 | + def binary(integer, bits: ) = format("0b%.#{bits}b", integer) |
| 142 | + |
| 143 | + # |
| 144 | + # Formats an argument for an Assembler method call. |
| 145 | + # |
| 146 | + # @param [ISA::OperandIndex, Integer] object |
| 147 | + # @return [String] |
| 148 | + # |
| 149 | + def value_arg(object) |
| 150 | + case object |
| 151 | + when ISA::OperandIndex then operand_index(object) |
| 152 | + else object.inspect |
| 153 | + end |
| 154 | + end |
| 155 | + |
| 156 | + # |
| 157 | + # @param [Array<CodeOffset, DataOffset, Immediate, ModRM, Opcode, |
| 158 | + # Prefix, RegisterByte, VEX, EVEX>] encoding |
| 159 | + # @return [String] |
| 160 | + # |
| 161 | + def encoder_method_call(encoding) |
| 162 | + case encoding |
| 163 | + when ISA::Encoding::CodeOffset |
| 164 | + "encoder.write_code_offset(#{value_arg(encoding.value)}, #{encoding.size.inspect})" |
| 165 | + when ISA::Encoding::DataOffset |
| 166 | + "encoder.write_data_offset(#{value_arg(encoding.value)}, #{encoding.size.inspect})" |
| 167 | + when ISA::Encoding::Immediate |
| 168 | + "encoder.write_immediate(#{value_arg(encoding.value)}, #{encoding.size.inspect})" |
| 169 | + when ISA::Encoding::ModRM |
| 170 | + "encoder.write_modrm(#{value_arg(encoding.mode)}, #{value_arg(encoding.reg)}, #{value_arg(encoding.rm)}, @operands)" |
| 171 | + when ISA::Encoding::Opcode |
| 172 | + "encoder.write_opcode(#{hex_byte(encoding.byte)}, #{value_arg(encoding.addend)})" |
| 173 | + when ISA::Encoding::Prefix |
| 174 | + "encoder.write_prefix(#{hex_byte(encoding.byte)}, #{encoding.mandatory.inspect})" |
| 175 | + when ISA::Encoding::RegisterByte |
| 176 | + "encoder.write_register_byte(#{value_arg(encoding.register)}, #{value_arg(encoding.payload)})" |
| 177 | + when ISA::Encoding::VEX |
| 178 | + "encoder.write_vex(#{encoding.type.inspect}, #{encoding.w.inspect}, #{encoding.l.inspect}, #{binary(encoding.m_mmmm, bits: 5)}, #{binary(encoding.pp, bits: 2)}, #{encoding.r.inspect}, #{encoding.x.inspect}, #{encoding.b.inspect}, #{value_arg(encoding.vvvv)})" |
| 179 | + when ISA::Encoding::EVEX |
| 180 | + "encoder.write_evex(#{binary(encoding.mmm, bits: 3)}, #{binary(encoding.pp, bits: 2)}, #{encoding.w.inspect}, #{value_arg(encoding.ll)}, #{value_arg(encoding.vvvv)}, #{encoding.v.inspect}, #{binary(encoding.rr, bits: 2)}, #{encoding.B.inspect}, #{encoding.x.inspect}, #{value_arg(encoding.b)}, #{value_arg(encoding.aaa)}, #{value_arg(encoding.z)}, #{encoding.disp8xN.inspect})" |
| 181 | + end |
| 182 | + end |
| 183 | + |
| 184 | + end |
| 185 | + |
| 186 | + # |
| 187 | + # Represents the `lib/ronin/asm/x86/instructions.rb` file. |
| 188 | + # |
| 189 | + class InstructionsFile |
| 190 | + |
| 191 | + # The path to the `instructions.rb.erb` template file. |
| 192 | + TEMPLATE_PATH = File.join(__dir__,'x86','instructions.rb.erb') |
| 193 | + |
| 194 | + # The parsed ERB template. |
| 195 | + TEMPLATE = ERB.new(File.read(TEMPLATE_PATH), trim_mode: '-') |
| 196 | + |
| 197 | + # The x86 instruction metadata objects. |
| 198 | + # |
| 199 | + # @return [Array<XML::Instruction>] |
| 200 | + attr_reader :instructions |
| 201 | + |
| 202 | + # |
| 203 | + # Initializes the instructions file object. |
| 204 | + # |
| 205 | + # @param [Array<XML::Instruction>] instructions |
| 206 | + # The x86 instruction metadata objects. |
| 207 | + # |
| 208 | + def initialize(instructions) |
| 209 | + @instructions = instructions |
| 210 | + end |
| 211 | + |
| 212 | + def self.generate(instructions) |
| 213 | + new(instructions).generate |
| 214 | + end |
| 215 | + |
| 216 | + # |
| 217 | + # The absolute path for the `lib/ronin/asm/x86/instructions.rb` file. |
| 218 | + # |
| 219 | + # @return [String] |
| 220 | + # |
| 221 | + def path |
| 222 | + File.join(DIR,'instructions.rb') |
| 223 | + end |
| 224 | + |
| 225 | + # |
| 226 | + # Generates the `lib/ronin/asm/x86/instructions.rb` file. |
| 227 | + # |
| 228 | + def generate |
| 229 | + File.write(path,TEMPLATE.result(binding)) |
| 230 | + end |
| 231 | + |
| 232 | + end |
| 233 | + end |
| 234 | +end |
| 235 | + |
| 236 | +if $0 == __FILE__ |
| 237 | + instructions = CodeGen::X86::ISA.load |
| 238 | + |
| 239 | + # Ensure `lib/ronin/asm/x86/instructions/` exists. |
| 240 | + FileUtils.mkdir_p(CodeGen::X86::INSTRUCTIONS_DIR) |
| 241 | + |
| 242 | + # Generate the individual instruction Ruby files within |
| 243 | + # `lib/ronin/asm/x86/instructions/`. |
| 244 | + instructions.each do |instruction| |
| 245 | + CodeGen::X86::InstructionFile.generate(instruction) |
| 246 | + end |
| 247 | + |
| 248 | + # Generate the `lib/ronin/asm/x86/instructions.rb` file. |
| 249 | + CodeGen::X86::InstructionsFile.generate(instructions) |
| 250 | +end |
0 commit comments