ecutools 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.bundle/config +3 -0
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +17 -0
- data/bin/ecutools +4 -0
- data/ecutools.gemspec +29 -0
- data/lib/assembler.rb +22 -0
- data/lib/cli.rb +28 -0
- data/lib/disassembler.rb +219 -0
- data/lib/helpers.rb +84 -0
- data/lib/instruction.rb +53 -0
- data/lib/version.rb +3 -0
- data/tests/52690021.asm +262151 -0
- data/tests/52690021.hex +0 -0
- data/xml/code/52690021.xml +19 -0
- data/xml/ram/52690021.xml +45 -0
- data/xml/rom/52370023.xml +641 -0
- data/xml/rom/52370024.xml +813 -0
- data/xml/rom/52690019.xml +25 -0
- data/xml/rom/52690021.xml +1156 -0
- data/xml/rom/52690022.xml +25 -0
- data/xml/rom/52690024.xml +896 -0
- data/xml/rom/52690122.xml +79 -0
- data/xml/rom/53050006.xml +583 -0
- data/xml/rom/53050007.xml +25 -0
- data/xml/rom/53050009.xml +950 -0
- data/xml/rom/53050011.xml +25 -0
- data/xml/rom/53050012.xml +803 -0
- data/xml/rom/53050109.xml +80 -0
- data/xml/rom/53600008.xml +25 -0
- data/xml/rom/53600009.xml +25 -0
- data/xml/rom/53600010.xml +848 -0
- data/xml/rom/53610009.xml +25 -0
- data/xml/rom/53610010.xml +877 -0
- data/xml/rom/53610012.xml +25 -0
- data/xml/rom/53610013.xml +886 -0
- data/xml/rom/54070007.xml +778 -0
- data/xml/rom/54550003.xml +546 -0
- data/xml/rom/55580005.xml +1161 -0
- data/xml/rom/55580006.xml +25 -0
- data/xml/rom/55580106.xml +75 -0
- data/xml/rom/55590006.xml +1135 -0
- data/xml/rom/55590007.xml +25 -0
- data/xml/rom/55590107.xml +69 -0
- data/xml/rom/56190002.xml +778 -0
- data/xml/rom/56880009.xml +858 -0
- data/xml/rom/56900007.xml +566 -0
- data/xml/rom/56900009.xml +946 -0
- data/xml/rom/56900010.xml +25 -0
- data/xml/rom/56920006.xml +546 -0
- data/xml/rom/56940007.xml +674 -0
- data/xml/rom/56940009.xml +478 -0
- data/xml/rom/56940010.xml +25 -0
- data/xml/rom/56950006.xml +696 -0
- data/xml/rom/57140001.xml +906 -0
- data/xml/rom/58020005.xml +732 -0
- data/xml/rom/58030005.xml +720 -0
- data/xml/rom/58640002.xml +880 -0
- metadata +147 -0
data/.bundle/config
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/bin/ecutools
ADDED
data/ecutools.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "ecutools"
|
8
|
+
s.version = ECUTools::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Javier Muniz"]
|
11
|
+
s.email = "javier@granicus.com"
|
12
|
+
s.summary = "Toolkit for disassembling and reverse engineering ECU ROMs"
|
13
|
+
s.homepage = "http://github.com/javiermuniz/ecutools"
|
14
|
+
s.description = "Toolkit for ECU disassembly and analysis"
|
15
|
+
|
16
|
+
s.rubyforge_project = "ecutools"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
|
24
|
+
s.add_development_dependency('thor', '>= 0.14')
|
25
|
+
s.add_development_dependency('nokogiri', '>= 1.5')
|
26
|
+
|
27
|
+
s.add_dependency('thor', '>= 0.14')
|
28
|
+
s.add_dependency('nokogiri', '>= 1.5')
|
29
|
+
end
|
data/lib/assembler.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module ECUTools
|
2
|
+
class Assembler
|
3
|
+
@@instructions = [
|
4
|
+
:invalid, :add, :add3, :and, :and3, :or, :or3, :xor,
|
5
|
+
:xor3, :addi, :addv, :addv3, :addx, :bc8, :bc24, :beq,
|
6
|
+
:beqz, :bgez, :bgtz, :blez, :bltz, :bnez, :bl8, :bl24, :bcl8, :bcl24, :bnc8, :bnc24,
|
7
|
+
:bne, :bra8, :bra24, :bncl8, :bncl24, :cmp, :cmpi, :cmpu, :cmpui, :cmpeq, :cmpz, :div, :divu, :rem,
|
8
|
+
:remu, :divh, :jc, :jnc, :jl, :jmp, :ld, :ld_d, :ldb, :ldb_d, :ldh, :ldh_d, :ldub,
|
9
|
+
:ldub_d, :lduh, :lduh_d, :ld_plus, :ld24, :ldi8, :ldi16, :lock, :machi, :machi_a, :maclo, :maclo_a,
|
10
|
+
:macwhi, :macwhi_a, :macwlo, :macwlo_a, :mul, :mulhi, :mulhi_a, :mullo, :mullo_a, :mulwhi,
|
11
|
+
:mulwhi_a, :mulwlo, :mulwlo_a, :mv, :mvfachi, :mvfachi_a, :mvfaclo, :mvfaclo_a, :mvfacmi,
|
12
|
+
:mvfacmi_a, :mvfc, :mvtachi, :mvtachi_a, :mvtaclo, :mvtaclo_a, :mvtc,
|
13
|
+
:neg, :nop, :not, :rac, :rac_dsi, :rach, :rach_dsi, :rte, :seth, :sll, :sll3, :slli, :sra, :sra3,
|
14
|
+
:srai, :srl, :srl3, :srli, :st, :st_d, :stb, :stb_d, :sth, :sth_d, :st_plus, :st_minus,
|
15
|
+
:sub, :subv, :subx, :trap, :unlock, :satb, :sath, :sat, :pcmpbz, :sadd, :macwu1, :msblo,
|
16
|
+
:mulwu1, :maclh1, :sc, :snc, :max ]
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/cli.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'thor'
|
3
|
+
require 'disassembler'
|
4
|
+
|
5
|
+
module ECUTools
|
6
|
+
class CLI < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
check_unknown_options!
|
10
|
+
|
11
|
+
default_task :disassemble
|
12
|
+
|
13
|
+
desc "disassemble <ROM>", "Disassembles a ROM into the current working directory"
|
14
|
+
long_desc <<-D
|
15
|
+
Dissassemble will take a hex or binary ROM and disassemble it into the proper M32R assembly code.
|
16
|
+
Tables and data will be disassembled as code, which will need to be sorted out by the user.
|
17
|
+
D
|
18
|
+
method_option "out", :type => :string, :aliases => "-o", :banner =>
|
19
|
+
"The output filename. If none is provided the rom name will be used with a .asm extension"
|
20
|
+
def disassemble(input)
|
21
|
+
outfile = "#{File.basename(input, File.extname(input))}.asm"
|
22
|
+
dasm = ECUTools::Disassembler.new(input, {:verbose => true})
|
23
|
+
dasm.analyze
|
24
|
+
dasm.write(outfile)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/disassembler.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'version'
|
2
|
+
require 'instruction'
|
3
|
+
require 'helpers'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module ECUTools
|
7
|
+
class Disassembler
|
8
|
+
include ECUTools::Helpers
|
9
|
+
|
10
|
+
def initialize(input = nil, options = {})
|
11
|
+
@options = options
|
12
|
+
@reference_addresses = {}
|
13
|
+
if(!input.nil?)
|
14
|
+
open input
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def verbose
|
19
|
+
@options[:verbose]
|
20
|
+
end
|
21
|
+
|
22
|
+
def open(file)
|
23
|
+
$stderr.puts "Disassembling binary..." if verbose
|
24
|
+
h = '[\d|[a-f]|[A-F]]'
|
25
|
+
@assembly = []
|
26
|
+
io = IO.popen("gobjdump -b binary --architecture=m32r --disassemble-all --disassemble-zeroes -EB #{file}")
|
27
|
+
while line = io.gets
|
28
|
+
match = /\s+(#{h}+):\s+(#{h}{2}) (#{h}{2}) (#{h}{2}) (#{h}{2})\s+(.+)/.match(line)
|
29
|
+
if match
|
30
|
+
@assembly << Instruction.new(match[1], [ match[2], match[3], match[4], match[5] ], match[6])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
$stderr.puts "Disassembly complete." if verbose
|
35
|
+
end
|
36
|
+
|
37
|
+
def write(file)
|
38
|
+
f = File.new(file,"w")
|
39
|
+
header = "; ecutools v#{ECUTools::VERSION}\n"
|
40
|
+
header << "; Generated assembly from ROM ID #{rom_id}\n"
|
41
|
+
header << "; Base Address: #{base_address}\n"
|
42
|
+
header << "; Disassembly:\n"
|
43
|
+
f.write(header)
|
44
|
+
$stderr.puts "Writing assembly..." if verbose
|
45
|
+
@assembly.each do |instruction|
|
46
|
+
f.write("#{instruction}\n")
|
47
|
+
end
|
48
|
+
$stderr.puts "Done." if verbose
|
49
|
+
end
|
50
|
+
|
51
|
+
def analyze
|
52
|
+
$stderr.puts "Analyzing assembly..." if verbose
|
53
|
+
annotate_tables
|
54
|
+
annotate_subroutines
|
55
|
+
annotate_code
|
56
|
+
$stderr.puts "Analyzation complete." if verbose
|
57
|
+
end
|
58
|
+
|
59
|
+
def annotate_tables
|
60
|
+
$stderr.puts "Annotating tables..." if verbose
|
61
|
+
tables = rom_xml.xpath('/rom/table')
|
62
|
+
count = 0
|
63
|
+
tables.each do |table|
|
64
|
+
elements = 1 # all tables start with one element
|
65
|
+
element_size = rom_xml.xpath("/rom/scaling[@name='#{table.attr('scaling')}']").attr('storagetype').value().gsub(/[^\d]+/,'').to_i / 8
|
66
|
+
address = from_hex table.attr('address')
|
67
|
+
is_data = false
|
68
|
+
|
69
|
+
table.xpath('table').each do |subtable|
|
70
|
+
elements = elements * subtable.attr('elements').to_i
|
71
|
+
is_data = true
|
72
|
+
end
|
73
|
+
|
74
|
+
possible_offsets = []
|
75
|
+
|
76
|
+
case elements
|
77
|
+
when 1,4
|
78
|
+
possible_offsets << 0
|
79
|
+
when 3..20
|
80
|
+
possible_offsets << 4
|
81
|
+
possible_offsets << 6
|
82
|
+
when 44
|
83
|
+
possible_offsets << 28 # wtf? maf scaling
|
84
|
+
else
|
85
|
+
possible_offsets << 7
|
86
|
+
possible_offsets << 10
|
87
|
+
possible_offsets << 16
|
88
|
+
end
|
89
|
+
|
90
|
+
possible_offsets.each do |offset|
|
91
|
+
offset_hex = (address - offset).to_s(16)
|
92
|
+
if verbose and @reference_addresses.include? offset_hex
|
93
|
+
$stderr.puts "WARNING: Reference hunt collision at 0x#{offset_hex} (#{@reference_addresses[offset_hex]} vs #{table.attr('name')})! Check code carefully!"
|
94
|
+
end
|
95
|
+
@reference_addresses[offset_hex] = table.attr('name')
|
96
|
+
end
|
97
|
+
|
98
|
+
storage_size = element_size * elements
|
99
|
+
|
100
|
+
storage_size.times do |n|
|
101
|
+
instruction = instruction_at(address + n)
|
102
|
+
instruction.comments[0] = table.attr('name') + "(0x#{table.attr('address')} -> 0x#{(address + storage_size - 1).to_s(16)}, #{storage_size} bytes)"
|
103
|
+
instruction.data = is_data
|
104
|
+
end
|
105
|
+
|
106
|
+
$stderr.puts "Annotated: #{table.attr('name')}" if verbose
|
107
|
+
count = count + 1
|
108
|
+
end
|
109
|
+
$stderr.puts "#{count} tables annotated." if verbose
|
110
|
+
end
|
111
|
+
|
112
|
+
def annotate_subroutines
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
def annotate_code
|
117
|
+
$stderr.puts "Annotating code..." if verbose
|
118
|
+
count = 0
|
119
|
+
found_rom_addresses = {}
|
120
|
+
found_ram_addresses = {}
|
121
|
+
@assembly.each do |instruction|
|
122
|
+
# annotate subroute prologue/epilogue
|
123
|
+
if instruction.assembly =~ /push lr/
|
124
|
+
c = 'begin subroutine'
|
125
|
+
c << " #{subroutine_descriptions[instruction.address]}" if subroutine_descriptions.include? instruction.address
|
126
|
+
instruction.comments << c
|
127
|
+
end
|
128
|
+
if instruction.assembly =~ /jmp lr/
|
129
|
+
instruction.comments << 'return'
|
130
|
+
end
|
131
|
+
|
132
|
+
# annotate subroutine calls
|
133
|
+
match = /bl 0x(\w+)/.match(instruction.assembly)
|
134
|
+
if match
|
135
|
+
address = match[1]
|
136
|
+
if subroutine_descriptions.include? address
|
137
|
+
instruction.comments << "Call #{subroutine_descriptions[address]}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# annotate table address references
|
142
|
+
address = /0x([\d|[a-f]|[A-F]]+)/.match(instruction.assembly)
|
143
|
+
if address and @reference_addresses.include? address[1]
|
144
|
+
instruction.comments << "contains possible reference to '#{@reference_addresses[address[1]]}'"
|
145
|
+
$stderr.puts "Annotated reference to: #{@reference_addresses[address[1]]} at #{instruction.address}" if verbose
|
146
|
+
count = count + 1
|
147
|
+
found_rom_addresses[@reference_addresses[address[1]]] = true
|
148
|
+
end
|
149
|
+
|
150
|
+
# annotate absolute RAM addressing ld24 r4,0x800700
|
151
|
+
match = /(\w+)\s+\w\w,0x(8\w\w\w\w\w)/.match(instruction.assembly)
|
152
|
+
if match
|
153
|
+
address = match[2]
|
154
|
+
display = address_descriptions[address]
|
155
|
+
|
156
|
+
if match[1] == "ld24"
|
157
|
+
op = "Assign pointer to"
|
158
|
+
else
|
159
|
+
op = "Unknown op on"
|
160
|
+
end
|
161
|
+
|
162
|
+
if !display.nil? and verbose
|
163
|
+
$stderr.puts "Found reference to absolute RAM address #{address} (#{display})"
|
164
|
+
end
|
165
|
+
instruction.comments << "#{op} RAM address 0x#{address}" + (display.nil? ? '' : " (#{display})")
|
166
|
+
found_ram_addresses[display] = true if !display.nil?
|
167
|
+
count = count + 1
|
168
|
+
end
|
169
|
+
|
170
|
+
# annotate relative RAM addressing
|
171
|
+
match = /(\w+)\s+.+?@\((-?\d+),fp\)/.match(instruction.assembly)
|
172
|
+
if match
|
173
|
+
address = absolute_address match[2].to_i
|
174
|
+
display = address_descriptions[address]
|
175
|
+
|
176
|
+
case match[1]
|
177
|
+
when "lduh"
|
178
|
+
op = "Load unsigned halfword from"
|
179
|
+
when "ldub"
|
180
|
+
op = "Load unsigned byte from"
|
181
|
+
when "ld"
|
182
|
+
op = "Load from"
|
183
|
+
when "ldb"
|
184
|
+
op = "Load byte from"
|
185
|
+
when "st"
|
186
|
+
op = "Store at"
|
187
|
+
when "stb"
|
188
|
+
op = "Store byte at"
|
189
|
+
when "sth"
|
190
|
+
op = "Store half word at"
|
191
|
+
when "bclr"
|
192
|
+
op = "Clear bit in"
|
193
|
+
else
|
194
|
+
op = "Unknown op on"
|
195
|
+
end
|
196
|
+
if !display.nil? and verbose
|
197
|
+
$stderr.puts "Found reference to relative RAM address #{address} (#{display})"
|
198
|
+
end
|
199
|
+
instruction.comments << "#{op} RAM address 0x#{address}" + (display.nil? ? '' : " (#{display})")
|
200
|
+
found_ram_addresses[display] = true if !display.nil?
|
201
|
+
count = count + 1
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
$stderr.puts "#{count} lines of code annotated." if verbose
|
207
|
+
if verbose
|
208
|
+
@reference_addresses.each_key do |key|
|
209
|
+
$stderr.puts "Unable to find any reference to table #{@reference_addresses[key]}" if !found_rom_addresses.include? @reference_addresses[key]
|
210
|
+
found_rom_addresses[@reference_addresses[key]] = true # stop multiple reports
|
211
|
+
end
|
212
|
+
address_descriptions.each_key do |key|
|
213
|
+
$stderr.puts "Unable to find any reference to RAM address #{address_descriptions[key]} (#{key})" if !found_ram_addresses.include? address_descriptions[key]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module ECUTools
|
2
|
+
module Helpers
|
3
|
+
def rom_id
|
4
|
+
return @rom_id if @rom_id
|
5
|
+
$stderr.puts "Getting ROM ID..." if verbose
|
6
|
+
@rom_id = read_bytes("5002a", 4).join
|
7
|
+
end
|
8
|
+
|
9
|
+
def read_bytes(address, number)
|
10
|
+
bytes = []
|
11
|
+
start = from_hex address
|
12
|
+
number.times do |n|
|
13
|
+
inst = instruction_at(start + n)
|
14
|
+
bytes << inst.bytes[(start+n) % 4]
|
15
|
+
end
|
16
|
+
bytes
|
17
|
+
end
|
18
|
+
|
19
|
+
def instruction_at(address, strict = false)
|
20
|
+
address = from_hex address
|
21
|
+
if strict and (address % 4) > 0
|
22
|
+
raise "Address #{address} does not fall on an instruction boundary and strict is enabled."
|
23
|
+
end
|
24
|
+
@assembly[(address - (address % 4)) / 4]
|
25
|
+
end
|
26
|
+
|
27
|
+
def from_hex(address)
|
28
|
+
if address.is_a? String
|
29
|
+
address.to_i(16)
|
30
|
+
else
|
31
|
+
address.to_i
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def base_address
|
36
|
+
return @base_address if @base_address
|
37
|
+
$stderr.puts "Getting base address..." if verbose
|
38
|
+
@assembly.each_with_index do |instruction,i|
|
39
|
+
if instruction.assembly == "st r3,@r0 \|\| nop"
|
40
|
+
match = /ld24 fp,(0x\w+)/.match(@assembly[i+1].assembly)
|
41
|
+
if match
|
42
|
+
@base_address = match[1]
|
43
|
+
@assembly[i+1].comments[0] = "Assign base address to #{@base_address}"
|
44
|
+
return @base_address
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
$stderr.puts "WARNING: Base address unknown! Setting to 0 (THIS IS WRONG!)" if verbose
|
50
|
+
@base_address = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def absolute_address(relative_address)
|
54
|
+
(base_address.to_i(16) + relative_address).to_s(16)
|
55
|
+
end
|
56
|
+
|
57
|
+
def address_descriptions
|
58
|
+
return @address_descriptions if @address_descriptions
|
59
|
+
@address_descriptions = {}
|
60
|
+
xml = Nokogiri::XML(File.open(File.dirname(__FILE__) + "/../xml/ram/#{rom_id}.xml"))
|
61
|
+
xml.xpath('/EvoScanDataLogger/vehicle/ecu/Mode2/DataListItem').each do |node|
|
62
|
+
@address_descriptions[node.attr('RequestID')[2..-1]] = node.attr('Display')
|
63
|
+
end
|
64
|
+
|
65
|
+
@address_descriptions
|
66
|
+
end
|
67
|
+
|
68
|
+
def subroutine_descriptions
|
69
|
+
return @subroutine_descriptions if @subroutine_descriptions
|
70
|
+
@subroutine_descriptions = {}
|
71
|
+
xml = Nokogiri::XML(File.open(File.dirname(__FILE__) + "/../xml/code/#{rom_id}.xml"))
|
72
|
+
xml.xpath('/rom/routine').each do |node|
|
73
|
+
@subroutine_descriptions[node.attr('address')] = node.attr('name')
|
74
|
+
end
|
75
|
+
|
76
|
+
@subroutine_descriptions
|
77
|
+
end
|
78
|
+
|
79
|
+
def rom_xml
|
80
|
+
return @rom_xml if @rom_xml
|
81
|
+
@rom_xml = Nokogiri::XML(File.open(File.dirname(__FILE__) + "/../xml/rom/#{rom_id}.xml"))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/instruction.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module ECUTools
|
2
|
+
class Instruction
|
3
|
+
|
4
|
+
def initialize(address, bytes, assembly)
|
5
|
+
@address = address
|
6
|
+
@bytes = bytes
|
7
|
+
@assembly = assembly
|
8
|
+
@comments = []
|
9
|
+
@data = !(/unknown/.match(assembly).nil?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
s = "0x#{@address}:\t#{@bytes.join(' ')}"
|
14
|
+
s << "\t#{@assembly}" if (!data)
|
15
|
+
s << "\t; #{@comments.join(', ')}" if @comments.length > 0
|
16
|
+
s
|
17
|
+
end
|
18
|
+
|
19
|
+
def address
|
20
|
+
@address
|
21
|
+
end
|
22
|
+
|
23
|
+
def data
|
24
|
+
@data
|
25
|
+
end
|
26
|
+
|
27
|
+
def data=(val)
|
28
|
+
@data=val
|
29
|
+
end
|
30
|
+
|
31
|
+
def assembly
|
32
|
+
@assembly
|
33
|
+
end
|
34
|
+
|
35
|
+
def assembly=(value)
|
36
|
+
@assembly = value
|
37
|
+
# TODO: Update bytes based on assembly instructions
|
38
|
+
end
|
39
|
+
|
40
|
+
def binary
|
41
|
+
@bytes.join.to_a.pack("H*")
|
42
|
+
end
|
43
|
+
|
44
|
+
def bytes
|
45
|
+
@bytes
|
46
|
+
end
|
47
|
+
|
48
|
+
def comments
|
49
|
+
@comments
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|