metasm 1.0.0
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/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- metadata +255 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/pic16c/opcodes'
|
|
8
|
+
require 'metasm/decode'
|
|
9
|
+
|
|
10
|
+
module Metasm
|
|
11
|
+
class Pic16c
|
|
12
|
+
def build_opcode_bin_mask(op)
|
|
13
|
+
# bit = 0 if can be mutated by an field value, 1 if fixed by opcode
|
|
14
|
+
op.bin_mask = Array.new(op.bin.length, 0)
|
|
15
|
+
op.fields.each { |f, (oct, off)|
|
|
16
|
+
op.bin_mask[oct] |= (@fields_mask[f] << off)
|
|
17
|
+
}
|
|
18
|
+
op.bin_mask.map! { |v| 255 ^ v }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def build_bin_lookaside
|
|
22
|
+
# sets up a hash byte value => list of opcodes that may match
|
|
23
|
+
# opcode.bin_mask is built here
|
|
24
|
+
lookaside = Array.new(256) { [] }
|
|
25
|
+
@opcode_list.each { |op|
|
|
26
|
+
|
|
27
|
+
build_opcode_bin_mask op
|
|
28
|
+
|
|
29
|
+
b = op.bin[0]
|
|
30
|
+
msk = op.bin_mask[0]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
for i in b..(b | (255^msk))
|
|
34
|
+
ext if i & msk != b & msk
|
|
35
|
+
|
|
36
|
+
lookaside[i] << op
|
|
37
|
+
end
|
|
38
|
+
}
|
|
39
|
+
lookaside
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/main'
|
|
8
|
+
|
|
9
|
+
module Metasm
|
|
10
|
+
class Pic16c < CPU
|
|
11
|
+
def initialize(endianness = :big)
|
|
12
|
+
super()
|
|
13
|
+
@endianness = endianness
|
|
14
|
+
init
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/pic16c/main'
|
|
8
|
+
|
|
9
|
+
module Metasm
|
|
10
|
+
class Pic16c
|
|
11
|
+
def addop(name, bin, *l)
|
|
12
|
+
o = Opcode.new name, bin
|
|
13
|
+
l.each { |ll|
|
|
14
|
+
if @props_allowed[ll]
|
|
15
|
+
o.props[ll] = true
|
|
16
|
+
else
|
|
17
|
+
o.args << ll
|
|
18
|
+
o.fields[ll] = @fields_off[ll]
|
|
19
|
+
end
|
|
20
|
+
}
|
|
21
|
+
@opcode_list << o
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def init
|
|
25
|
+
@fields_mask = {:f => 0x7f, :b => 0x7, :k => 0xff, :klong => 0x3ff, :d => 1 }
|
|
26
|
+
@props_allowed = {:setip => true, :saveip => true, :stopexec => true }
|
|
27
|
+
@fields_off = { :f => 0, :b => 7, :k => 0, :klong => 0, :d => 7, :d => 7 }
|
|
28
|
+
|
|
29
|
+
addop 'addwf', 0b00_0111_0000_0000, :f, :d
|
|
30
|
+
addop 'andwf', 0b00_0101_0000_0000, :f, :d
|
|
31
|
+
addop 'clrf', 0b00_0001_1000_0000, :f
|
|
32
|
+
addop 'clrw', 0b00_0001_0000_0000 # 00_0001_0xxx_xxxx
|
|
33
|
+
addop 'comf', 0b00_1001_0000_0000, :f, :d
|
|
34
|
+
addop 'decf', 0b00_0011_0000_0000, :f, :d
|
|
35
|
+
addop 'decfsz',0b00_1011_0000_0000, :f, :d
|
|
36
|
+
addop 'incf', 0b00_1010_0000_0000, :f, :d
|
|
37
|
+
addop 'incfsz',0b00_1111_0000_0000, :f, :d
|
|
38
|
+
addop 'iorwf', 0b00_0100_0000_0000, :f, :d
|
|
39
|
+
addop 'movf', 0b00_1000_0000_0000, :f, :d
|
|
40
|
+
addop 'movwf', 0b00_0000_1000_0000, :f
|
|
41
|
+
addop 'nop', 0b00_0000_0000_0000 # 00_0000_0xx0_0000
|
|
42
|
+
addop 'rlf', 0b00_1101_0000_0000, :f, :d
|
|
43
|
+
addop 'rrf', 0b00_1100_0000_0000, :f, :d
|
|
44
|
+
addop 'subwf', 0b00_0010_0000_0000, :f, :d
|
|
45
|
+
addop 'swapf', 0b00_1110_0000_0000, :f, :d
|
|
46
|
+
addop 'xorwf', 0b00_0110_0000_0000, :f, :d
|
|
47
|
+
|
|
48
|
+
addop 'bcf', 0b01_0000_0000_0000, :f, :b
|
|
49
|
+
addop 'bsf', 0b01_0100_0000_0000, :f, :b
|
|
50
|
+
addop 'btfsc', 0b01_1000_0000_0000, :f, :b, :setip
|
|
51
|
+
addop 'btfss', 0b01_1100_0000_0000, :f, :b, :setip
|
|
52
|
+
|
|
53
|
+
addop 'addlw', 0b11_1110_0000_0000, :k # 00_000x_0000_0000
|
|
54
|
+
addop 'andlw', 0b11_1001_0000_0000, :k
|
|
55
|
+
addop 'call', 0b10_0000_0000_0000, :klong, :setip, :stopexec, :saveip
|
|
56
|
+
addop 'clrwdt',0b00_0000_0110_0100
|
|
57
|
+
addop 'goto', 0b10_1000_0000_0000, :klong, :setip, :stopexec
|
|
58
|
+
addop 'iorlw', 0b11_1000_0000_0000, :k
|
|
59
|
+
addop 'movlw', 0b11_0000_0000_0000, :k # 00_00xx_0000_0000
|
|
60
|
+
addop 'retfie',0b00_0000_0000_1001, :setip, :stopexec
|
|
61
|
+
addop 'retlw', 0b11_0100_0000_0000, :k, :setip, :stopexec # 00_00xx_0000_0000
|
|
62
|
+
addop 'return',0b00_0000_0000_1000, :setip, :stopexec
|
|
63
|
+
addop 'sleep', 0b00_0000_0110_0011
|
|
64
|
+
addop 'sublw', 0b11_1100_0000_0000, :k # 00_000x_0000_0000
|
|
65
|
+
addop 'xorlw', 0b11_1010_0000_0000, :k
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/metasm/ppc.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/main'
|
|
8
|
+
require 'metasm/ppc/parse'
|
|
9
|
+
require 'metasm/ppc/encode'
|
|
10
|
+
require 'metasm/ppc/decode'
|
|
11
|
+
require 'metasm/ppc/decompile'
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/ppc/opcodes'
|
|
8
|
+
require 'metasm/decode'
|
|
9
|
+
|
|
10
|
+
module Metasm
|
|
11
|
+
class PowerPC
|
|
12
|
+
def build_opcode_bin_mask(op)
|
|
13
|
+
# bit = 0 if can be mutated by an field value, 1 if fixed by opcode
|
|
14
|
+
return if not op.bin.kind_of? Integer
|
|
15
|
+
op.bin_mask = 0
|
|
16
|
+
op.args.each { |f|
|
|
17
|
+
op.bin_mask |= @fields_mask[f] << @fields_shift[f]
|
|
18
|
+
}
|
|
19
|
+
op.bin_mask = 0xffff_ffff ^ op.bin_mask
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_bin_lookaside
|
|
23
|
+
lookaside = Array.new(256) { [] }
|
|
24
|
+
opcode_list.each { |op|
|
|
25
|
+
next if not op.bin.kind_of? Integer
|
|
26
|
+
build_opcode_bin_mask op
|
|
27
|
+
|
|
28
|
+
b = op.bin >> 24
|
|
29
|
+
msk = op.bin_mask >> 24
|
|
30
|
+
|
|
31
|
+
for i in b..(b | (255^msk))
|
|
32
|
+
next if i & msk != b & msk
|
|
33
|
+
lookaside[i] << op
|
|
34
|
+
end
|
|
35
|
+
}
|
|
36
|
+
lookaside
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def decode_findopcode(edata)
|
|
40
|
+
return if edata.ptr >= edata.data.length
|
|
41
|
+
di = DecodedInstruction.new(self)
|
|
42
|
+
val = edata.decode_imm(:u32, @endianness)
|
|
43
|
+
edata.ptr -= 4
|
|
44
|
+
di if di.opcode = @bin_lookaside[val >> 24].find { |op|
|
|
45
|
+
(op.bin & op.bin_mask) == (val & op.bin_mask)
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def decode_instr_op(edata, di)
|
|
50
|
+
before_ptr = edata.ptr
|
|
51
|
+
op = di.opcode
|
|
52
|
+
di.instruction.opname = op.name
|
|
53
|
+
val = edata.decode_imm(:u32, @endianness)
|
|
54
|
+
|
|
55
|
+
field_val = lambda { |f|
|
|
56
|
+
r = (val >> @fields_shift[f]) & @fields_mask[f]
|
|
57
|
+
case f
|
|
58
|
+
when :bd, :d, :ds, :dq, :si, :ui; r = Expression.make_signed(r<<@fields_shift[f], 16)
|
|
59
|
+
when :li; r = Expression.make_signed(r<<@fields_shift[f], 26)
|
|
60
|
+
else r
|
|
61
|
+
end
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
op.args.each { |a|
|
|
65
|
+
di.instruction.args << case a
|
|
66
|
+
when :ra, :rb, :rs, :rt; GPR.new field_val[a]
|
|
67
|
+
when :fra, :frb, :frc, :frs, :frt; FPR.new field_val[a]
|
|
68
|
+
when :ra_i16, :ra_i16s, :ra_i16q
|
|
69
|
+
i = field_val[{:ra_i16 => :d, :ra_i16s => :ds, :ra_i16q => :dq}[a]]
|
|
70
|
+
Memref.new GPR.new(field_val[:ra]), Expression[i]
|
|
71
|
+
when :bd, :d, :ds, :dq, :si, :ui, :li, :sh, :ma, :mb, :me, :ma_, :mb_, :me_; Expression[field_val[a]]
|
|
72
|
+
when :ign_bo_zzz, :ign_bo_z, :ign_bo_at, :ign_bo_at2, :ign_bi, :aa, :lk, :oe, :rc, :l; next
|
|
73
|
+
else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}"
|
|
74
|
+
end
|
|
75
|
+
}
|
|
76
|
+
di.bin_length += edata.ptr - before_ptr
|
|
77
|
+
|
|
78
|
+
decode_aliases(di.instruction)
|
|
79
|
+
|
|
80
|
+
di
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def decode_aliases(i)
|
|
84
|
+
case i.opname
|
|
85
|
+
when /^n?or\.?$/
|
|
86
|
+
if i.args[1] == i.args[2]
|
|
87
|
+
i.args.pop
|
|
88
|
+
i.opname = {'or' => 'mr', 'or.' => 'mr.', 'nor' => 'not', 'nor.' => 'not.'}[i.opname]
|
|
89
|
+
end
|
|
90
|
+
when /^addi/
|
|
91
|
+
if a = i.args[2].reduce and a.kind_of? Integer and a < 0
|
|
92
|
+
i.args[2] = Expression[-a]
|
|
93
|
+
i.opname = i.opname.sub('addi', 'subi')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
case i.opname
|
|
98
|
+
when /^(add|sub|xor|and|or|div|mul|nand)/
|
|
99
|
+
if i.args.length == 3 and i.args[0] == i.args[1]
|
|
100
|
+
i.args.shift
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# converts relative branch offsets to absolute addresses
|
|
107
|
+
# else just add the offset +off+ of the instruction + its length (off may be an Expression)
|
|
108
|
+
# assumes edata.ptr points just after the instruction (as decode_instr_op left it)
|
|
109
|
+
# do not call twice on the same di !
|
|
110
|
+
def decode_instr_interpret(di, addr)
|
|
111
|
+
if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.opcode.name[0] != ?t and di.opcode.name[-1] != ?a
|
|
112
|
+
arg = Expression[addr, :+, di.instruction.args.last].reduce
|
|
113
|
+
di.instruction.args[-1] = Expression[arg]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
di
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# TODO
|
|
120
|
+
def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs)
|
|
121
|
+
retaddrlist.to_a.map! { |retaddr| dasm.decoded[retaddr] ? dasm.decoded[retaddr].block.list.last.address : retaddr }
|
|
122
|
+
b = f.backtrace_binding
|
|
123
|
+
|
|
124
|
+
bt_val = lambda { |r|
|
|
125
|
+
bt = []
|
|
126
|
+
retaddrlist.to_a.each { |retaddr|
|
|
127
|
+
bt |= dasm.backtrace(Expression[r], retaddr,
|
|
128
|
+
:include_start => true, :snapshot_addr => faddr, :origin => retaddr)
|
|
129
|
+
}
|
|
130
|
+
b[r] = ((bt.length == 1) ? bt.first : Expression::Unknown)
|
|
131
|
+
}
|
|
132
|
+
wantregs = GPR::Sym if wantregs.empty?
|
|
133
|
+
wantregs.map { |r| r.to_sym }.each(&bt_val)
|
|
134
|
+
|
|
135
|
+
#puts "update_func_bind: #{Expression[faddr]} has sp -> #{b[:$sp]}" if not Expression[b[:$sp], :-, :$sp].reduce.kind_of?(::Integer) if $VERBOSE
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def backtrace_is_function_return(expr, di=nil)
|
|
139
|
+
expr.reduce_rec == :lr
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def backtrace_is_stack_address(expr)
|
|
143
|
+
Expression[expr].expr_externals.include? :sp
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def replace_instr_arg_immediate(i, old, new)
|
|
147
|
+
i.args.map! { |a|
|
|
148
|
+
case a
|
|
149
|
+
when Expression; a == old ? new : Expression[a.bind(old => new).reduce]
|
|
150
|
+
when Memref
|
|
151
|
+
a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset.kind_of? Expression
|
|
152
|
+
a
|
|
153
|
+
else a
|
|
154
|
+
end
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def disassembler_default_func
|
|
159
|
+
df = DecodedFunction.new
|
|
160
|
+
df.backtrace_binding = (0..31).inject({}) { |h, r| r != 1 ? h.update("r#{r}".to_sym => Expression::Unknown) : h }
|
|
161
|
+
df.backtracked_for = [BacktraceTrace.new(Expression[:lr], :default, Expression[:lr], :x)]
|
|
162
|
+
df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr|
|
|
163
|
+
if funcaddr != :default
|
|
164
|
+
btfor
|
|
165
|
+
elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip]
|
|
166
|
+
btfor
|
|
167
|
+
else []
|
|
168
|
+
end
|
|
169
|
+
}
|
|
170
|
+
df
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# hash opname => lambda { |di, *sym_args| binding }
|
|
174
|
+
def backtrace_binding
|
|
175
|
+
@backtrace_binding ||= init_backtrace_binding
|
|
176
|
+
end
|
|
177
|
+
def backtrace_binding=(b) @backtrace_binding = b end
|
|
178
|
+
|
|
179
|
+
def init_backtrace_binding
|
|
180
|
+
@backtrace_binding ||= {}
|
|
181
|
+
opcode_list.map { |ol| ol.name }.uniq.each { |op|
|
|
182
|
+
binding = case op
|
|
183
|
+
when 'mr', 'li', 'la'; lambda { |di, a0, a1| { a0 => Expression[a1] } }
|
|
184
|
+
when 'lis'; lambda { |di, a0, a1| { a0 => Expression[a1, :<<, 16] } }
|
|
185
|
+
when 'mtctr'; lambda { |di, a0| { :ctr => Expression[a0] } }
|
|
186
|
+
when 'mfctr'; lambda { |di, a0| { a0 => Expression[:ctr] } }
|
|
187
|
+
when 'mtlr'; lambda { |di, a0| { :lr => Expression[a0] } }
|
|
188
|
+
when 'mflr'; lambda { |di, a0| { a0 => Expression[:lr] } }
|
|
189
|
+
when 'lwzu'; lambda { |di, a0, m|
|
|
190
|
+
ret = { a0 => Expression[m] }
|
|
191
|
+
ptr = m.pointer.externals.grep(Symbol).first
|
|
192
|
+
ret[ptr] = m.pointer if ptr != a0
|
|
193
|
+
ret
|
|
194
|
+
}
|
|
195
|
+
when 'lwz'; lambda { |di, a0, m| { a0 => Expression[m] } }
|
|
196
|
+
when 'stwu'; lambda { |di, a0, m|
|
|
197
|
+
{ m => Expression[a0], m.pointer.externals.grep(Symbol).first => m.pointer }
|
|
198
|
+
}
|
|
199
|
+
when 'stw'; lambda { |di, a0, m| { m => Expression[a0] } }
|
|
200
|
+
when 'rlwinm'; lambda { |di, a0, a1, sh, mb, me|
|
|
201
|
+
mb, me = mb.reduce, me.reduce
|
|
202
|
+
cpmsk = (1<<@size) - 1
|
|
203
|
+
a1 = Expression[a1, :&, cpmsk]
|
|
204
|
+
rol = Expression[[a1, :<<, sh], :|, [a1, :>>, [@size, :-, sh]]]
|
|
205
|
+
if mb == me+1
|
|
206
|
+
msk = cpmsk
|
|
207
|
+
elsif mb < me+1
|
|
208
|
+
msk = (((1 << ((me+1)-mb)) - 1) << (@size-(me+1)))
|
|
209
|
+
else
|
|
210
|
+
msk = (((1 << (mb-(me+1))) - 1) << (@size-mb)) ^ cpmsk
|
|
211
|
+
end
|
|
212
|
+
{ a0 => Expression[Expression[rol, :&, msk].reduce] }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
when 'add', 'addi', 'add.', 'addi.'; lambda { |di, *a| { a[0] => Expression[a[-2], :+, a[-1]] } }
|
|
216
|
+
when 'addis', 'addis.'; lambda { |di, *a| { a[0] => Expression[a[-2], :+, [a[-1], :<<, 16]] } }
|
|
217
|
+
when 'sub', 'subi', 'sub.', 'subi.'; lambda { |di, *a| { a[0] => Expression[a[-2], :-, a[-1]] } }
|
|
218
|
+
when 'subis', 'subis.'; lambda { |di, *a| { a[0] => Expression[a[-2], :-, [a[-1], :<<, 16]] } }
|
|
219
|
+
when /^b.*la?$/; lambda { |di, *a| { :lr => Expression[di.next_addr] } }
|
|
220
|
+
when 'nop', /^cmp/, /^b/; lambda { |di, *a| {} }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
@backtrace_binding[op] ||= binding if binding
|
|
224
|
+
}
|
|
225
|
+
@backtrace_binding
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def get_backtrace_binding(di)
|
|
229
|
+
a = di.instruction.args.map { |arg|
|
|
230
|
+
case arg
|
|
231
|
+
when Memref; arg.symbolic(di.address)
|
|
232
|
+
when Reg; arg.symbolic
|
|
233
|
+
else arg
|
|
234
|
+
end
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
binding = if binding = backtrace_binding[di.instruction.opname]
|
|
238
|
+
binding[di, *a]
|
|
239
|
+
else
|
|
240
|
+
puts "unknown instruction to emu #{di}" if $VERBOSE
|
|
241
|
+
{}
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
binding
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def get_xrefs_x(dasm, di)
|
|
248
|
+
return [] if not di.opcode.props[:setip]
|
|
249
|
+
|
|
250
|
+
arg = case di.instruction.opname
|
|
251
|
+
when 'bctr', 'bctrl'; :ctr
|
|
252
|
+
when 'blr', 'blrl'; :lr
|
|
253
|
+
else di.instruction.args.last
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
[Expression[
|
|
257
|
+
case arg
|
|
258
|
+
when Memref; Indirection[[arg.base.to_s.to_sym, :+, arg.offset], @size/8, di.address]
|
|
259
|
+
when Reg; arg.to_s.to_sym
|
|
260
|
+
else arg
|
|
261
|
+
end]]
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/ppc/main'
|
|
8
|
+
|
|
9
|
+
module Metasm
|
|
10
|
+
class PowerPC
|
|
11
|
+
# temporarily setup dasm.address_binding so that backtracking
|
|
12
|
+
# stack-related offsets resolve in :frameptr (relative to func start)
|
|
13
|
+
def decompile_makestackvars(dasm, funcstart, blocks)
|
|
14
|
+
oldfuncbd = dasm.address_binding[funcstart]
|
|
15
|
+
dasm.address_binding[funcstart] = { :sp => :frameptr } # this would suffice, the rest here is just optimisation
|
|
16
|
+
|
|
17
|
+
blocks.each { |block|
|
|
18
|
+
yield block
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dasm.address_binding[funcstart] = oldfuncbd if oldfuncbd
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# list variable dependency for each block, remove useless writes
|
|
25
|
+
# returns { blockaddr => [list of vars that are needed by a following block] }
|
|
26
|
+
def decompile_func_finddeps(dcmp, blocks, func)
|
|
27
|
+
deps_r = {} ; deps_w = {} ; deps_to = {}
|
|
28
|
+
deps_subfunc = {} # things read/written by subfuncs
|
|
29
|
+
|
|
30
|
+
# find read/writes by each block
|
|
31
|
+
blocks.each { |b, to|
|
|
32
|
+
deps_r[b] = [] ; deps_w[b] = [] ; deps_to[b] = to
|
|
33
|
+
deps_subfunc[b] = []
|
|
34
|
+
|
|
35
|
+
blk = dcmp.dasm.decoded[b].block
|
|
36
|
+
blk.list.each { |di|
|
|
37
|
+
a = di.backtrace_binding.values
|
|
38
|
+
w = []
|
|
39
|
+
di.backtrace_binding.keys.each { |k|
|
|
40
|
+
case k
|
|
41
|
+
when ::Symbol; w |= [k]
|
|
42
|
+
else a |= Expression[k].externals # if dword [eax] <- 42, eax is read
|
|
43
|
+
end
|
|
44
|
+
}
|
|
45
|
+
#a << :eax if di.opcode.name == 'ret' # standard ABI
|
|
46
|
+
|
|
47
|
+
deps_r[b] |= a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - deps_w[b]
|
|
48
|
+
deps_w[b] |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown]
|
|
49
|
+
}
|
|
50
|
+
stackoff = nil
|
|
51
|
+
blk.each_to_normal { |t|
|
|
52
|
+
t = dcmp.backtrace_target(t, blk.list.last.address)
|
|
53
|
+
next if not t = dcmp.c_parser.toplevel.symbol[t]
|
|
54
|
+
t.type = C::Function.new(C::BaseType.new(:int)) if not t.type.kind_of? C::Function # XXX this may seem a bit extreme, and yes, it is.
|
|
55
|
+
stackoff ||= Expression[dcmp.dasm.backtrace(:sp, blk.list.last.address, :snapshot_addr => blocks.first[0]).first, :-, :sp].reduce
|
|
56
|
+
}
|
|
57
|
+
if stackoff # last block instr == subfunction call
|
|
58
|
+
deps_r[b] |= deps_subfunc[b] - deps_w[b]
|
|
59
|
+
#deps_w[b] |= [:eax, :ecx, :edx] # standard ABI
|
|
60
|
+
end
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# find regs read and never written (must have been set by caller and are part of the func ABI)
|
|
66
|
+
uninitialized = lambda { |b, r, done|
|
|
67
|
+
from = deps_to.keys.find_all { |f| deps_to[f].include? b } - done
|
|
68
|
+
from.empty? or from.find { |f|
|
|
69
|
+
!deps_w[f].include?(r) and uninitialized[f, r, done + [b]]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# remove writes from a block if no following block read the value
|
|
74
|
+
dw = {}
|
|
75
|
+
deps_w.each { |b, deps|
|
|
76
|
+
dw[b] = deps.reject { |dep|
|
|
77
|
+
ret = true
|
|
78
|
+
done = []
|
|
79
|
+
todo = deps_to[b].dup
|
|
80
|
+
while a = todo.pop
|
|
81
|
+
next if done.include? a
|
|
82
|
+
done << a
|
|
83
|
+
if not deps_r[a] or deps_r[a].include? dep
|
|
84
|
+
ret = false
|
|
85
|
+
break
|
|
86
|
+
elsif not deps_w[a].include? dep
|
|
87
|
+
todo.concat deps_to[a]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
ret
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
dw
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil)
|
|
98
|
+
scope = func.initializer
|
|
99
|
+
func.type.args.each { |a| scope.symbol[a.name] = a }
|
|
100
|
+
stmts = scope.statements
|
|
101
|
+
func_entry = myblocks.first[0]
|
|
102
|
+
until myblocks.empty?
|
|
103
|
+
b, to = myblocks.shift
|
|
104
|
+
if l = dcmp.dasm.get_label_at(b)
|
|
105
|
+
stmts << C::Label.new(l)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# list of assignments [[dest reg, expr assigned]]
|
|
109
|
+
ops = []
|
|
110
|
+
# reg binding (reg => value, values.externals = regs at block start)
|
|
111
|
+
binding = {}
|
|
112
|
+
# Expr => CExpr
|
|
113
|
+
ce = lambda { |*e| dcmp.decompile_cexpr(Expression[Expression[*e].reduce], scope) }
|
|
114
|
+
# Expr => Expr.bind(binding) => CExpr
|
|
115
|
+
ceb = lambda { |*e| ce[Expression[*e].bind(binding)] }
|
|
116
|
+
|
|
117
|
+
# dumps a CExprs that implements an assignment to a reg (uses ops[], patches op => [reg, nil])
|
|
118
|
+
commit = lambda {
|
|
119
|
+
deps[b].map { |k|
|
|
120
|
+
[k, ops.rindex(ops.reverse.find { |r, v| r == k })]
|
|
121
|
+
}.sort_by { |k, i| i.to_i }.each { |k, i|
|
|
122
|
+
next if not i or not binding[k]
|
|
123
|
+
e = k
|
|
124
|
+
final = []
|
|
125
|
+
ops[0..i].reverse_each { |r, v|
|
|
126
|
+
final << r if not v
|
|
127
|
+
e = Expression[e].bind(r => v).reduce if not final.include? r
|
|
128
|
+
}
|
|
129
|
+
ops[i][1] = nil
|
|
130
|
+
binding.delete k
|
|
131
|
+
stmts << ce[k, :'=', e] if k != e
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# go !
|
|
136
|
+
dcmp.dasm.decoded[b].block.list.each_with_index { |di, didx|
|
|
137
|
+
a = di.instruction.args
|
|
138
|
+
if di.opcode.props[:setip] and not di.opcode.props[:stopexec]
|
|
139
|
+
# conditional jump
|
|
140
|
+
commit[]
|
|
141
|
+
n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address)
|
|
142
|
+
#cc = ceb[decode_cc_to_expr(di.opcode.name[1..-1])]
|
|
143
|
+
cc = ceb[:condjmp]
|
|
144
|
+
stmts << C::If.new(C::CExpression[cc], C::Goto.new(n))
|
|
145
|
+
to.delete dcmp.dasm.normalize(n)
|
|
146
|
+
next
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
case di.opcode.name
|
|
150
|
+
when 'blr'
|
|
151
|
+
commit[]
|
|
152
|
+
stmts << C::Return.new(nil)
|
|
153
|
+
when 'bl' # :saveip
|
|
154
|
+
n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address)
|
|
155
|
+
args = []
|
|
156
|
+
if t = dcmp.c_parser.toplevel.symbol[n] and t.type.args
|
|
157
|
+
stackoff = Expression[dcmp.dasm.backtrace(:sp, di.address, :snapshot_addr => func_entry), :-, :sp].bind(:sp => :frameptr).reduce rescue nil
|
|
158
|
+
args_todo = t.type.args.dup
|
|
159
|
+
args = []
|
|
160
|
+
args_todo.each {
|
|
161
|
+
if stackoff.kind_of? Integer
|
|
162
|
+
var = Indirection[[:frameptr, :+, stackoff], @size/8]
|
|
163
|
+
stackoff += @size/8
|
|
164
|
+
else
|
|
165
|
+
var = 0
|
|
166
|
+
end
|
|
167
|
+
args << ceb[var]
|
|
168
|
+
binding.delete var
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
commit[]
|
|
172
|
+
#next if not di.block.to_subfuncret
|
|
173
|
+
|
|
174
|
+
if n.kind_of? ::String
|
|
175
|
+
if not f = dcmp.c_parser.toplevel.symbol[n]
|
|
176
|
+
# internal functions are predeclared, so this one is extern
|
|
177
|
+
f = dcmp.c_parser.toplevel.symbol[n] = C::Variable.new
|
|
178
|
+
f.name = n
|
|
179
|
+
f.type = C::Function.new(C::BaseType.new(:int))
|
|
180
|
+
dcmp.c_parser.toplevel.statements << C::Declaration.new(f)
|
|
181
|
+
end
|
|
182
|
+
commit[]
|
|
183
|
+
else
|
|
184
|
+
# indirect funcall
|
|
185
|
+
fptr = ceb[n]
|
|
186
|
+
binding.delete n
|
|
187
|
+
commit[]
|
|
188
|
+
proto = C::Function.new(C::BaseType.new(:int))
|
|
189
|
+
f = C::CExpression[[fptr], proto]
|
|
190
|
+
end
|
|
191
|
+
binding.delete :eax
|
|
192
|
+
e = C::CExpression[f, :funcall, args]
|
|
193
|
+
e = C::CExpression[ce[:eax], :'=', e, f.type.type] if deps[b].include? :eax and f.type.type != C::BaseType.new(:void)
|
|
194
|
+
stmts << e
|
|
195
|
+
when 'b'
|
|
196
|
+
a = di.instruction.args.first
|
|
197
|
+
if a.kind_of? Expression
|
|
198
|
+
else
|
|
199
|
+
# indirect jmp, convert to return (*fptr)();
|
|
200
|
+
n = di.instruction.args.first.symbolic
|
|
201
|
+
fptr = ceb[n]
|
|
202
|
+
binding.delete n
|
|
203
|
+
commit[]
|
|
204
|
+
proto = C::Function.new(C::BaseType.new(:void))
|
|
205
|
+
ret = C::Return.new(C::CExpression[[[fptr], C::Pointer.new(proto)], :funcall, []])
|
|
206
|
+
class << ret ; attr_accessor :from_instr end
|
|
207
|
+
ret.from_instr = di
|
|
208
|
+
stmts << ret
|
|
209
|
+
to = []
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
bd = get_fwdemu_binding(di)
|
|
213
|
+
if di.backtrace_binding[:incomplete_binding]
|
|
214
|
+
commit[]
|
|
215
|
+
stmts << C::Asm.new(di.instruction.to_s, nil, nil, nil, nil, nil)
|
|
216
|
+
else
|
|
217
|
+
bd.each { |k, v|
|
|
218
|
+
if k.kind_of? ::Symbol
|
|
219
|
+
ops << [k, v]
|
|
220
|
+
else # memory
|
|
221
|
+
stmts << ceb[k, :'=', v]
|
|
222
|
+
binding.delete k
|
|
223
|
+
end
|
|
224
|
+
}
|
|
225
|
+
update = {}
|
|
226
|
+
bd.each { |k, v|
|
|
227
|
+
next if not k.kind_of? ::Symbol
|
|
228
|
+
update[k] = Expression[Expression[v].bind(binding).reduce]
|
|
229
|
+
}
|
|
230
|
+
binding.update update
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
}
|
|
234
|
+
commit[]
|
|
235
|
+
|
|
236
|
+
case to.length
|
|
237
|
+
when 0
|
|
238
|
+
if not myblocks.empty? and not %w[ret jmp].include? dcmp.dasm.decoded[b].block.list.last.instruction.opname
|
|
239
|
+
puts " block #{Expression[b]} has no to and don't end in ret"
|
|
240
|
+
end
|
|
241
|
+
when 1
|
|
242
|
+
if (myblocks.empty? ? nextaddr != to[0] : myblocks.first.first != to[0])
|
|
243
|
+
stmts << C::Goto.new(dcmp.dasm.auto_label_at(to[0], 'unknown_goto'))
|
|
244
|
+
end
|
|
245
|
+
else
|
|
246
|
+
puts " block #{Expression[b]} with multiple to"
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|