forked from michaeledgar/reversal
/
reverser.rb
162 lines (139 loc) · 4.18 KB
/
reverser.rb
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
##
# reversal.rb: Reverser dispatcher for different types of decompilable code.
#
#
# Copyright 2010 Michael J. Edgar, michael.j.edgar@dartmouth.edu
#
# MIT License, see LICENSE file in gem package
module Reversal
class Reverser
OPERATOR_LOOKUP = {:opt_plus => "+", :opt_minus => "-", :opt_mult => "*", :opt_div => "/",
:opt_mod => "%", :opt_eq => "==", :opt_neq => "!=", :opt_lt => "<",
:opt_le => "<=", :opt_gt => ">", :opt_ge => ">=", :opt_ltlt => "<<",
:opt_regexpmatch2 => "=~"}
# Instructions module depends on OPERATOR_LOOKUP
include Instructions
TAB_SIZE = 2
ALL_INFIX = OPERATOR_LOOKUP.values + ["<=>"]
attr_accessor :locals, :parent, :indent
def initialize(iseq, parent=nil)
@iseq = ISeq.new(iseq)
@iseq.validate!
@parent = parent
@locals = [:self] + @iseq.locals.reverse
reset!
end
def reset!
@stack = []
@else_stack = []
@end_stack = []
end
# include specific modules for different reversal techniques
def to_ir
reset!
# dispatch on the iseq type
self.__send__("to_ir_#{@iseq.type}".to_sym, @iseq)
end
def to_ir_block(iseq)
return r(:block, @iseq.argstring, decompile_body)
end
def to_ir_method(iseq)
reverser = Reverser.new(iseq, self)
iseq = ISeq.new(iseq)
return r(:defmethod, r(:lit, 0), iseq.name, reverser.decompile_body, iseq.argstring)
end
##
# If it's just top-level code, then there are no args - just decompile
# the body straight away
def to_ir_top(iseq)
decompile_body
end
##
# If it's just top-level code, then there are no args - just decompile
# the body straight away
def to_ir_class(iseq)
decompile_body
end
##
# Gets a local variable at the given bytecode-style index
def get_local(idx)
get_dynamic(idx, 0)
end
##
# Gets a dynamic variable, based on the bytecode-style index and
# the depth
def get_dynamic(idx, depth)
if depth == 0
@locals[idx - 1]
elsif @parent
@parent.get_dynamic(idx, depth - 1)
else
raise "Invalid dynamic variable requested: #{idx} #{depth} from #{self.iseq}"
end
end
##
# Pushes a node onto the stack, as a decompiled string
def push(str)
@stack.push str
end
##
# Pops a node from the stack, as a decompiled string
def pop(n = 1)
if @stack.empty?
raise "Popped an empty stack"
elsif n == 1
@stack.pop
else
popn(n)
end
end
def popn(n = 1)
(1..n).to_a.map {pop}.reverse
end
def remove_useless_dup
pop unless @stack.empty?
end
TRACE_NEWLINE = 1
TRACE_EXIT = 16
def forward_jump?(current, label)
@iseq.labels[label] && @iseq.labels[label] > current
end
def backward_jump?(current, label)
!forward_jump?(current, label)
end
def next_instruction_number(cur_inst, cur_line)
if cur_inst.is_a?(Array) && (cur_inst[0] == :branchif || cur_inst[0] == :branchunless)
return cur_line + 1
else
return cur_line + 1
end
end
def decompile_body(instruction = 0, stop = @iseq.body.size)
# for now, using non-idiomatic while loop bc of a chance we might need to
# loop back
iseq = @iseq
while instruction < stop do
inst = iseq.body[instruction]
#p inst, @stack
#puts "Instruction #{instruction} #{inst.inspect} #{@stack.inspect}"
case inst
when Integer
# x
@current_line = inst # unused
when Symbol
# :label_y
while inst == @end_stack.last do
@end_stack.pop
push "end"
end
when Array
# [:instruction, *args]
# call "decompile_#{instruction}"
send("decompile_#{inst.first}".to_sym, inst, instruction) if respond_to?("decompile_#{inst.first}".to_sym)
end
instruction = next_instruction_number(inst, instruction)
end
r(:list, *@stack)
end
end
end