rpicsim 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +3 -1
- data/Introduction.md +1 -3
- data/README.md +1 -3
- data/docs/ChangeLog.md +13 -0
- data/docs/KnownIssues.md +2 -2
- data/docs/Manual.md +0 -1
- data/docs/RSpecIntegration.md +24 -1
- data/docs/SupportedMPLABXVersions.md +2 -0
- data/lib/rpicsim.rb +6 -1
- data/lib/rpicsim/composite_memory.rb +3 -0
- data/lib/rpicsim/flaws.rb +28 -4
- data/lib/rpicsim/label.rb +7 -0
- data/lib/rpicsim/memory.rb +4 -0
- data/lib/rpicsim/memory_watcher.rb +11 -4
- data/lib/rpicsim/mplab.rb +1 -0
- data/lib/rpicsim/mplab/mplab_assembly.rb +0 -1
- data/lib/rpicsim/pin.rb +7 -0
- data/lib/rpicsim/program_counter.rb +4 -0
- data/lib/rpicsim/program_file.rb +20 -17
- data/lib/rpicsim/rspec.rb +6 -3
- data/lib/rpicsim/rspec/helpers.rb +13 -5
- data/lib/rpicsim/rspec/persistent_expectations.rb +5 -3
- data/lib/rpicsim/rspec/sim_diagnostics.rb +1 -0
- data/lib/rpicsim/sim.rb +8 -3
- data/lib/rpicsim/stack_pointer.rb +6 -0
- data/lib/rpicsim/stack_trace.rb +32 -1
- data/lib/rpicsim/storage/memory_integer.rb +1 -0
- data/lib/rpicsim/symbol_set.rb +3 -1
- data/lib/rpicsim/variable.rb +14 -0
- data/lib/rpicsim/variable_set.rb +2 -0
- data/lib/rpicsim/version.rb +4 -1
- data/lib/rpicsim/xc8_sym_file.rb +63 -11
- metadata +2 -7
- data/docs/PreventingCallStackOverflow.md +0 -92
- data/lib/rpicsim/call_stack_info.rb +0 -303
- data/lib/rpicsim/instruction.rb +0 -337
- data/lib/rpicsim/mplab/mplab_disassembler.rb +0 -20
- data/lib/rpicsim/search.rb +0 -20
@@ -1,92 +0,0 @@
|
|
1
|
-
Preventing call stack overflow
|
2
|
-
====
|
3
|
-
|
4
|
-
PIC microcontrollers feature a stack implemented in hardware that keeps track of return addresses for subroutine calls.
|
5
|
-
Every time a `CALL` instruction is executed, the return address is pushed onto the stack.
|
6
|
-
Every time a `RETURN` or similar instruction is executed, the return address is popped off of the stack.
|
7
|
-
|
8
|
-
The call stack has a limited depth that depends on the device you are using.
|
9
|
-
If your program has too many levels of nested subroutines then the call stack could overflow.
|
10
|
-
Depending on the device, a call stack overflow could cause a reset or incorrect program execution.
|
11
|
-
Therefore, it is important to avoid a call stack overflow.
|
12
|
-
|
13
|
-
RPicSim can trace all the possible paths of execution for a PIC program in order to calculate what the maximum possible call stack depth is.
|
14
|
-
You can easily add this to your RSpec tests.
|
15
|
-
Here is an example:
|
16
|
-
|
17
|
-
!!!ruby
|
18
|
-
describe "call stack" do
|
19
|
-
subject(:call_stack_info) do
|
20
|
-
RPicSim::CallStackInfo.hash_from_program_file(MySim.program_file, [0, 4])
|
21
|
-
end
|
22
|
-
|
23
|
-
specify "mainline code uses no more than 5 levels" do
|
24
|
-
call_stack_info[0].max_depth be <= 5
|
25
|
-
end
|
26
|
-
|
27
|
-
specify "ISR code uses no more than 1 level" do
|
28
|
-
call_stack_info[4].max_depth.should be <= 1
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
This example is for a PIC10F322 program.
|
33
|
-
The mainline code's entry point is at address 0, and the first RSpec example makes sure that the maximum depth of subroutine calls in the mainline code is 5.
|
34
|
-
The firmware uses an interrupt and the interrupt vector is at address 4.
|
35
|
-
The second RSpec example makes sure that the ISR uses at most one level of the call stack (the ISR itself can only call one subroutine).
|
36
|
-
|
37
|
-
If the two tests above pass, then we can calculate the maximum amount of call stack space that might ever be used. If the mainline code is at its deepest point and it is interrupted by an ISR that happens to reach its deepest point, then the call stack would have:
|
38
|
-
|
39
|
-
* 5 levels used by the mainline code.
|
40
|
-
* 1 level used by the processor itself to store the address to return to when the interrupt is done.
|
41
|
-
* 1 level used by the ISR code.
|
42
|
-
|
43
|
-
The means there can be at most 7 things stored on the call stack, which is safely below the PIC10F322's limit of 8.
|
44
|
-
|
45
|
-
Suppose RPicSim tells you that your mainline code could take 5 levels of the stack and you are not sure why.
|
46
|
-
The {RPicSim::CallStackInfo#worst_case_code_paths_filtered_report} can produce a set of example code paths that demonstrate how the maximum depth could potentially happen.
|
47
|
-
To see the report, just add a line like this:
|
48
|
-
|
49
|
-
!!!ruby
|
50
|
-
p call_stack_info[0].worst_case_code_paths_filtered_report
|
51
|
-
|
52
|
-
The report will be a series of code paths that look something like this:
|
53
|
-
|
54
|
-
!!!plain
|
55
|
-
CodePath:
|
56
|
-
Instruction(0x0000, GOTO 0x20)
|
57
|
-
Instruction(0x0024 = start2, CALL 0x40)
|
58
|
-
Instruction(0x0040 = foo, CALL 0x41)
|
59
|
-
Instruction(0x0041 = goo, CALL 0x60)
|
60
|
-
Instruction(0x0060 = hoo, CALL 0x80)
|
61
|
-
Instruction(0x0080 = ioo, CALL 0x100)
|
62
|
-
Instruction(0x0100 = joo, CLRF 0x7)
|
63
|
-
|
64
|
-
This example code path shows five CALL instructions that could potentially be nested.
|
65
|
-
|
66
|
-
How it works
|
67
|
-
----
|
68
|
-
|
69
|
-
The {RPicSim::ProgramFile} class uses the MPLAB X disassembler to provide a graph with every reachable instruction in the firmware.
|
70
|
-
The {RPicSim::CallStackInfo} class traverses all possible paths through that graph from a given entry point to calculate the maximum possible call stack depth at every point in the graph.
|
71
|
-
|
72
|
-
Limitations
|
73
|
-
----
|
74
|
-
|
75
|
-
The algorithm is pessimistic:
|
76
|
-
|
77
|
-
* It does not try to track the runtime values of any of your program's variables in order to predict which code paths will happen.
|
78
|
-
* It cannot handle recursive functions because there is no way for it to figure out the maximum level of recursion.
|
79
|
-
* It does not know when interrupts are enabled.
|
80
|
-
|
81
|
-
All of those things are OK because they can only cause the algorithm to give an answer that is more pessimistic than reality; the reported maximum call stack depth might be higher than what is actually possible.
|
82
|
-
|
83
|
-
However, there are some things that can mess up the algorithm in a bad way and give you incorrect results:
|
84
|
-
|
85
|
-
* If you write to the PC register in order to do a computed jump, the algorithm does not currently detect that and will not correctly consider code paths coming from that instruction.
|
86
|
-
Be careful about this, because a computed jump might be generated automatically by a C compiler.
|
87
|
-
* Similarly, it cannot handle jumps on devices that have paged memory. In order to determine where a jump actually goes, it would need to know what page is selected by looking at the history of the program's execution.
|
88
|
-
* It does not account for the effect of PUSH and POP instructions on the call stack depth.
|
89
|
-
|
90
|
-
This code is not suitable (yet) for any firmware that uses a computed jump, paged program memory, or PUSH or POP instructions.
|
91
|
-
|
92
|
-
This code only checks the hardware call stack; it does not check any kind of data stack that your compiler might use to store the values of local variables.
|
@@ -1,303 +0,0 @@
|
|
1
|
-
require_relative 'search'
|
2
|
-
require 'set'
|
3
|
-
|
4
|
-
module RPicSim
|
5
|
-
# This class helps analyze programs to see whether it is possible for them
|
6
|
-
# to overflow the call stack, which is limited to a small number of levels on
|
7
|
-
# many PIC microcontrollers.
|
8
|
-
# It traverses the {Instruction} graph provided by a {ProgramFile}
|
9
|
-
# from a given root node and for each reachable instruction determines the
|
10
|
-
# maximum possible call stack depth when that instruction starts executing,
|
11
|
-
# relative to how deep the call stack depth was when the root instruction
|
12
|
-
# started.
|
13
|
-
#
|
14
|
-
# Here is an example of how you would use this to check the call stack depth
|
15
|
-
# in a program that has one interrupt vector at address 4 inside an RSpec test.
|
16
|
-
# Of course, you should adjust the numbers to suit your application:
|
17
|
-
#
|
18
|
-
# infos = CallStackInfo.hash_from_program_file(program_file, [0, 4])
|
19
|
-
# infos[0].max_depth.should <= 5 # main-line code should take at most 5 levels
|
20
|
-
# infos[4].max_depth.should <= 1 # ISR should take at most 1 stack level
|
21
|
-
#
|
22
|
-
# Additionally, it can generate reports of all different ways that the maximum
|
23
|
-
# call stack depth can be achieved, which can be helpful if you need to reduce
|
24
|
-
# your maximum stack depth.
|
25
|
-
class CallStackInfo
|
26
|
-
# For each of the given root instruction addresses, generates a {CallStackInfo}
|
27
|
-
# report. Returns the reports in a hash.
|
28
|
-
#
|
29
|
-
# @param program_file (ProgramFile)
|
30
|
-
# @param root_instruction_addresses Array(Integer) The program memory
|
31
|
-
# addresses of the entry vectors for your program. On a midrange device, these
|
32
|
-
# are typically 0 for the main-line code and 4 for the interrupt.
|
33
|
-
# @return [Hash(address => CallStackInfo)]
|
34
|
-
def self.hash_from_program_file(program_file, root_instruction_addresses)
|
35
|
-
infos = {}
|
36
|
-
root_instruction_addresses.each do |addr|
|
37
|
-
infos[addr] = from_root_instruction(program_file.instruction(addr))
|
38
|
-
end
|
39
|
-
infos
|
40
|
-
end
|
41
|
-
|
42
|
-
# Generates a {CallStackInfo} from the given root instruction.
|
43
|
-
# This will tell you the maximum value the call stack could get to for a
|
44
|
-
# program that starts at the given instruction with an empty call stack
|
45
|
-
# and never gets interrupted.
|
46
|
-
def self.from_root_instruction(root)
|
47
|
-
new(root)
|
48
|
-
end
|
49
|
-
|
50
|
-
# The maximum call stack depth for all the reachable instructions.
|
51
|
-
# If your program starts executing at the root node and the call stack
|
52
|
-
# is empty, then (not accounting for interrupts) the call stack will
|
53
|
-
# never exceed this depth.
|
54
|
-
#
|
55
|
-
# A value of 0 means that no subroutine calls a possible; a value of
|
56
|
-
# 1 means that at most one subroutine call is possible at any given time,
|
57
|
-
# and so on.
|
58
|
-
#
|
59
|
-
# @return (Integer)
|
60
|
-
attr_reader :max_depth
|
61
|
-
|
62
|
-
# @return (Instruction) The root instruction that this report was generated from.
|
63
|
-
attr_reader :root
|
64
|
-
|
65
|
-
# Generates a {CallStackInfo} from the given root instruction.
|
66
|
-
def initialize(root)
|
67
|
-
@root = root
|
68
|
-
generate
|
69
|
-
@max_depth = @max_depths.values.max
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def generate
|
75
|
-
@max_depths = { @root => 0 }
|
76
|
-
@back_links = Hash.new { [] }
|
77
|
-
instructions_to_process = [root]
|
78
|
-
until instructions_to_process.empty?
|
79
|
-
instruction = instructions_to_process.pop
|
80
|
-
instruction.transitions.reverse_each do |transition|
|
81
|
-
ni = transition.next_instruction
|
82
|
-
prev_depth = @max_depths[ni]
|
83
|
-
new_depth = @max_depths[instruction] + transition.call_depth_change
|
84
|
-
|
85
|
-
if new_depth > 50
|
86
|
-
raise "Recursion probably detected. Maximum call depth of #{ni} is at least #{new_depth}."
|
87
|
-
end
|
88
|
-
|
89
|
-
if prev_depth.nil? || new_depth > prev_depth
|
90
|
-
@max_depths[ni] = new_depth
|
91
|
-
instructions_to_process << ni
|
92
|
-
@back_links[ni] = []
|
93
|
-
end
|
94
|
-
|
95
|
-
if new_depth == @max_depths[ni]
|
96
|
-
@back_links[ni] << instruction
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
public
|
103
|
-
|
104
|
-
# Returns all the {Instruction}s that have the worse case possible call stack depth.
|
105
|
-
# @return [Array(Instruction)]
|
106
|
-
def instructions_with_worst_case
|
107
|
-
@max_depths.select { |instr, depth| depth == @max_depth }.map(&:first).sort
|
108
|
-
end
|
109
|
-
|
110
|
-
# Returns all the {Instruction}s that are reachable from the given root.
|
111
|
-
# @return [Array(Instruction)]
|
112
|
-
def reachable_instructions
|
113
|
-
@max_depths.keys
|
114
|
-
end
|
115
|
-
|
116
|
-
# Check the max-depth data hash for consistency.
|
117
|
-
def double_check!
|
118
|
-
reachable_instructions.each do |instr|
|
119
|
-
depth = @max_depths[instr]
|
120
|
-
|
121
|
-
instr.transitions.each do |transition|
|
122
|
-
next_instruction = transition.next_instruction
|
123
|
-
if @max_depths[next_instruction] < @max_depths[instr] + transition.call_depth_change
|
124
|
-
raise 'Call stack info double check failed: %s has max_depth %d and leads (%d) to %s with max_depth %d.' %
|
125
|
-
[instr, depth, transition.call_depth_change, next_instruction, @max_depths[next_instruction]]
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# Returns an array of {CodePath}s representing all possible ways that the call stack
|
133
|
-
# could reach the worst-case depth. This will often be a very large amount of data,
|
134
|
-
# even for a small project.
|
135
|
-
# @return [Array(CodePath)]
|
136
|
-
def worst_case_code_paths
|
137
|
-
instructions_with_worst_case.sort.flat_map do |instruction|
|
138
|
-
code_paths(instruction)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Returns a filtered version of {#worst_case_code_paths}.
|
143
|
-
# Filters out any code paths that are just a superset of another code path.
|
144
|
-
# For each instruction that has a back trace leading to it, it just returns
|
145
|
-
# the code paths with the smallest number of interesting instructions.
|
146
|
-
# @return [Array(CodePath)]
|
147
|
-
def worst_case_code_paths_filtered
|
148
|
-
all_code_paths = worst_case_code_paths
|
149
|
-
|
150
|
-
# Filter out code path that are just a superset of another code path.
|
151
|
-
previously_seen_instruction_sequences = Set.new
|
152
|
-
code_paths = []
|
153
|
-
all_code_paths.sort_by(&:count).each do |code_path|
|
154
|
-
seen_before = (1..code_path.instructions.size).any? do |n|
|
155
|
-
subsequence = code_path.instructions[0, n]
|
156
|
-
previously_seen_instruction_sequences.include? subsequence
|
157
|
-
end
|
158
|
-
if !seen_before
|
159
|
-
previously_seen_instruction_sequences << code_path.instructions
|
160
|
-
code_paths << code_path
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
# For each instruction that has a code path leading to it, pick out
|
165
|
-
# the shortest code path (in terms of interesting instructions).
|
166
|
-
code_paths = code_paths.group_by { |cp| cp.instructions.last }.map do |instr, paths|
|
167
|
-
paths.min_by { |cp| cp.interesting_instructions.count }
|
168
|
-
end
|
169
|
-
|
170
|
-
code_paths
|
171
|
-
end
|
172
|
-
|
173
|
-
# Returns a nice report string of all the {#worst_case_code_paths_filtered}.
|
174
|
-
# @return [String]
|
175
|
-
def worst_case_code_paths_filtered_report
|
176
|
-
s = ''
|
177
|
-
worst_case_code_paths_filtered.each do |code_path|
|
178
|
-
s << code_path.to_s + "\n"
|
179
|
-
s << "\n"
|
180
|
-
end
|
181
|
-
s
|
182
|
-
end
|
183
|
-
|
184
|
-
# @return [Array(CodePaths)] all the possible code paths that lead to the given instruction.
|
185
|
-
def code_paths(instruction)
|
186
|
-
code_paths = []
|
187
|
-
Search.depth_first_search_simple([[instruction]]) do |instrs|
|
188
|
-
prev_instrs = @back_links[instrs.first]
|
189
|
-
|
190
|
-
if prev_instrs.empty?
|
191
|
-
# This must be the root node.
|
192
|
-
if instrs.first != @root
|
193
|
-
raise "This instruction is not the root and has no back links: #{instrs}."
|
194
|
-
end
|
195
|
-
|
196
|
-
code_paths << CodePath.new(instrs)
|
197
|
-
[] # don't search anything from this node
|
198
|
-
else
|
199
|
-
# Investigate all possible code paths that could get to this instruction.
|
200
|
-
# However, exclude code paths that have the same instruction twice;
|
201
|
-
# otherwise we get stuck in an infinite loop.
|
202
|
-
(prev_instrs - instrs).map do |instr|
|
203
|
-
[instr] + instrs
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
code_paths
|
208
|
-
end
|
209
|
-
|
210
|
-
def inspect
|
211
|
-
"#<#{self.class}:root=#{@root.inspect}>"
|
212
|
-
end
|
213
|
-
|
214
|
-
# This is a helper class for {CallStackInfo}. It wraps an array of {Instruction} objects
|
215
|
-
# representing an execution path from one part of the program (usually the entry vector or
|
216
|
-
# the ISR vector) to another part of the program.
|
217
|
-
# It has method for reducing this list of instructions by only showing the interesting ones.
|
218
|
-
class CodePath
|
219
|
-
include Enumerable
|
220
|
-
|
221
|
-
# An array of {Instruction}s that represents a possible path through the program.
|
222
|
-
# Each instruction in the list could possibly execute after the previous one.
|
223
|
-
attr_reader :instructions
|
224
|
-
|
225
|
-
# A new instance that wraps the given instructions.
|
226
|
-
# @param instructions Array(Instruction) The instructions to wrap.
|
227
|
-
def initialize(instructions)
|
228
|
-
@instructions = instructions.freeze
|
229
|
-
end
|
230
|
-
|
231
|
-
# Iterates over the wrapped instructions by calling <tt>each</tt> on the underlying array.
|
232
|
-
# Since this class also includes <tt>Enumerable</tt>, it means you can use any of the
|
233
|
-
# usual methods of Enumerable (e.g. <tt>select</tt>) on this class.
|
234
|
-
def each(&proc)
|
235
|
-
instructions.each(&proc)
|
236
|
-
end
|
237
|
-
|
238
|
-
# Returns the addresses of the underlying instructions.
|
239
|
-
def addresses
|
240
|
-
instructions.map(&:address)
|
241
|
-
end
|
242
|
-
|
243
|
-
# Returns an array of the addresses of the interesting instructions.
|
244
|
-
def interesting_addresses
|
245
|
-
interesting_instructions.map(&:address)
|
246
|
-
end
|
247
|
-
|
248
|
-
# Returns just the interesting instructions, as defined by {#interesting_instruction?}.
|
249
|
-
#
|
250
|
-
# @return [Array(Integer)]
|
251
|
-
def interesting_instructions
|
252
|
-
@instructions.select.each_with_index do |instruction, index|
|
253
|
-
next_instruction = @instructions[index + 1]
|
254
|
-
interesting_instruction?(instruction, next_instruction)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
# Returns true if the given instruction is interesting. An instruction is
|
259
|
-
# interesting if you would need to see it in order to understand the path
|
260
|
-
# program has taken through the code and understand why the call stack
|
261
|
-
# could reach a certain depth.
|
262
|
-
#
|
263
|
-
# * The first and last instructions are interesting.
|
264
|
-
# * A branch that is taken is interesting.
|
265
|
-
# * A subroutine call is interesting.
|
266
|
-
def interesting_instruction?(instruction, next_instruction)
|
267
|
-
if instruction == @instructions.first || instruction == @instructions.last
|
268
|
-
return true
|
269
|
-
end
|
270
|
-
|
271
|
-
transition = instruction.transition_to(next_instruction)
|
272
|
-
|
273
|
-
if transition.call_depth_change >= 1
|
274
|
-
# This edge represents a function call so it is interesting.
|
275
|
-
return true
|
276
|
-
end
|
277
|
-
|
278
|
-
if transition.non_local?
|
279
|
-
# This edge represents a goto, so that is interesting
|
280
|
-
# because you need to know which gotos were taken to understand
|
281
|
-
# a code path. If seeing the skip is annoying we could
|
282
|
-
# suppress that by adding more information to the edge.
|
283
|
-
return true
|
284
|
-
end
|
285
|
-
|
286
|
-
# Everything else is not interesting.
|
287
|
-
|
288
|
-
# We are purposing deciding that skips are not interesting.
|
289
|
-
# because if you are trying to understand the full code path
|
290
|
-
# it is obvious whether any given skip was taken or not, as long
|
291
|
-
# as you know which calls and gotos were taken.
|
292
|
-
|
293
|
-
false
|
294
|
-
end
|
295
|
-
|
296
|
-
# Returns a multi-line string representing this execution path.
|
297
|
-
def to_s
|
298
|
-
"CodePath:\n" +
|
299
|
-
interesting_instructions.join("\n") + "\n"
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
data/lib/rpicsim/instruction.rb
DELETED
@@ -1,337 +0,0 @@
|
|
1
|
-
module RPicSim
|
2
|
-
# Instances of this class represent a particular instruction at a particular
|
3
|
-
# address in program memory. This class takes low-level information about a
|
4
|
-
# disassembled instruction and produces high-level information about what that
|
5
|
-
# instruction is and how it behaves.
|
6
|
-
#
|
7
|
-
# Instances of this class have links to the other instructions that the instruction
|
8
|
-
# could lead to, so the instances form a graph. This graph is traversed by
|
9
|
-
# classes like {CallStackInfo} to get useful information about the firmware.
|
10
|
-
class Instruction
|
11
|
-
include Comparable
|
12
|
-
|
13
|
-
# The program memory address of the instruction.
|
14
|
-
# For PIC18s this will be the byte address.
|
15
|
-
# For other PIC architectures, it will be the word address.
|
16
|
-
# @return (Integer)
|
17
|
-
attr_reader :address
|
18
|
-
|
19
|
-
# The opcode as a capitalized string (e.g. "MOVLW").
|
20
|
-
# @return (String)
|
21
|
-
attr_reader :opcode
|
22
|
-
|
23
|
-
# The operands of the instruction as a hash like { "k" => 92 }.
|
24
|
-
# @return (Hash)
|
25
|
-
attr_reader :operands
|
26
|
-
|
27
|
-
# The number of program memory address units that this instruction takes.
|
28
|
-
# The units of this are the same as the units of {#address}.
|
29
|
-
# @return (Integer)
|
30
|
-
attr_reader :size
|
31
|
-
|
32
|
-
# A line of assembly language that would represent this instruction.
|
33
|
-
# For example "GOTO 0x2".
|
34
|
-
# @return (String)
|
35
|
-
attr_reader :string
|
36
|
-
|
37
|
-
# Creates a new instruction.
|
38
|
-
def initialize(mplab_instruction, address, address_increment, instruction_store)
|
39
|
-
@address = address
|
40
|
-
@instruction_store = instruction_store
|
41
|
-
@address_increment = address_increment
|
42
|
-
|
43
|
-
if mplab_instruction == :invalid
|
44
|
-
@valid = false
|
45
|
-
@size = @address_increment
|
46
|
-
@string = '[INVALID]'
|
47
|
-
return
|
48
|
-
end
|
49
|
-
|
50
|
-
@valid = true
|
51
|
-
@opcode = mplab_instruction.opcode
|
52
|
-
@operands = mplab_instruction.operands
|
53
|
-
@string = mplab_instruction.instruction_string
|
54
|
-
|
55
|
-
@size = mplab_instruction.compute_size(address_increment)
|
56
|
-
raise "Invalid size #{@size} for #{inspect}" if ![1, 2, 4].include?(@size)
|
57
|
-
|
58
|
-
properties = Array case mplab_instruction.opcode
|
59
|
-
when 'ADDFSR'
|
60
|
-
when 'ADDLW'
|
61
|
-
when 'ADDWF'
|
62
|
-
when 'ADDWFC'
|
63
|
-
when 'ANDLW'
|
64
|
-
when 'ANDWF'
|
65
|
-
when 'ASRF'
|
66
|
-
when 'BC' then [:conditional_relative_branch]
|
67
|
-
when 'BCF'
|
68
|
-
when 'BN' then [:conditional_relative_branch]
|
69
|
-
when 'BNC' then [:conditional_relative_branch]
|
70
|
-
when 'BNN' then [:conditional_relative_branch]
|
71
|
-
when 'BNOV' then [:conditional_relative_branch]
|
72
|
-
when 'BNZ' then [:conditional_relative_branch]
|
73
|
-
when 'BRA' then [:relative_branch]
|
74
|
-
when 'BRW' then [:control_ender] # Hard to predict
|
75
|
-
when 'BSF'
|
76
|
-
when 'BTG'
|
77
|
-
when 'BTFSC' then [:conditional_skip]
|
78
|
-
when 'BTFSS' then [:conditional_skip]
|
79
|
-
when 'BZ' then [:conditional_relative_branch]
|
80
|
-
when 'CALL' then [:call]
|
81
|
-
when 'CALLW' then [:control_ender] # Hard to predict
|
82
|
-
when 'CPFSEQ' then [:conditional_skip]
|
83
|
-
when 'CPFSGT' then [:conditional_skip]
|
84
|
-
when 'CPFSLT' then [:conditional_skip]
|
85
|
-
when 'CLRF'
|
86
|
-
when 'CLRW'
|
87
|
-
when 'CLRWDT'
|
88
|
-
when 'COMF'
|
89
|
-
when 'DAW'
|
90
|
-
when 'DECF'
|
91
|
-
when 'DECFSZ' then [:conditional_skip]
|
92
|
-
when 'DCFSNZ' then
|
93
|
-
when 'GOTO' then [:goto]
|
94
|
-
when 'INCF'
|
95
|
-
when 'INCFSZ' then [:conditional_skip]
|
96
|
-
when 'INFSNZ' then [:conditional_skip]
|
97
|
-
when 'IORLW'
|
98
|
-
when 'IORWF'
|
99
|
-
when 'LFSR'
|
100
|
-
when 'LSLF'
|
101
|
-
when 'LSRF'
|
102
|
-
when 'MOVIW'
|
103
|
-
when 'MOVWI'
|
104
|
-
when 'MOVLB'
|
105
|
-
when 'MOVLP'
|
106
|
-
when 'MOVLW'
|
107
|
-
when 'MOVWF'
|
108
|
-
when 'MOVF'
|
109
|
-
when 'MOVFF'
|
110
|
-
when 'MULLW'
|
111
|
-
when 'MULWF'
|
112
|
-
when 'NEGF'
|
113
|
-
when 'NOP'
|
114
|
-
when 'OPTION'
|
115
|
-
when 'PUSH'
|
116
|
-
when 'POP'
|
117
|
-
when 'RCALL' then [:relative_call]
|
118
|
-
when 'RESET' then [:control_ender]
|
119
|
-
when 'RETFIE' then [:control_ender]
|
120
|
-
when 'RETLW' then [:control_ender]
|
121
|
-
when 'RETURN' then [:control_ender]
|
122
|
-
when 'RLCF'
|
123
|
-
when 'RLF'
|
124
|
-
when 'RLNCF'
|
125
|
-
when 'RRCF'
|
126
|
-
when 'RRF'
|
127
|
-
when 'RRNCF'
|
128
|
-
when 'SETF'
|
129
|
-
when 'SLEEP'
|
130
|
-
when 'SUBLW'
|
131
|
-
when 'SUBWF'
|
132
|
-
when 'SUBWFB'
|
133
|
-
when 'SWAPF'
|
134
|
-
when 'TBLRD*'
|
135
|
-
when 'TBLRD*+'
|
136
|
-
when 'TBLRD*-'
|
137
|
-
when 'TBLRD+*'
|
138
|
-
when 'TBLWT*'
|
139
|
-
when 'TBLWT*+'
|
140
|
-
when 'TBLWT*-'
|
141
|
-
when 'TBLWT+*'
|
142
|
-
when 'TRIS'
|
143
|
-
when 'TSTFSZ' then [:conditional_skip]
|
144
|
-
when 'XORLW'
|
145
|
-
when 'XORWF'
|
146
|
-
else
|
147
|
-
raise "Unrecognized opcode #{mplab_instruction.opcode} " +
|
148
|
-
"(#{address_description(address)}, operands #{mplab_instruction.operands.inspect})."
|
149
|
-
end
|
150
|
-
|
151
|
-
modules = {
|
152
|
-
conditional_skip: ConditionalSkip,
|
153
|
-
conditional_relative_branch: ConditionalRelativeBranch,
|
154
|
-
relative_branch: RelativeBranch,
|
155
|
-
relative_call: RelativeCall,
|
156
|
-
goto: Goto,
|
157
|
-
control_ender: ControlEnder,
|
158
|
-
call: Call,
|
159
|
-
}
|
160
|
-
|
161
|
-
properties.each do |p|
|
162
|
-
mod = modules[p]
|
163
|
-
if !mod
|
164
|
-
raise ArgumentError, "Invalid property: #{p.inspect}."
|
165
|
-
end
|
166
|
-
extend mod
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# Compares this instruction to another using the addresses. This means you can
|
171
|
-
# call +.sort+ on an array of instructions to put them in order by address.
|
172
|
-
def <=>(other)
|
173
|
-
address <=> other.address
|
174
|
-
end
|
175
|
-
|
176
|
-
# Human-readable string representation of the instruction.
|
177
|
-
def to_s
|
178
|
-
"Instruction(#{@instruction_store.address_description(address)}, #{@string})"
|
179
|
-
end
|
180
|
-
|
181
|
-
def inspect
|
182
|
-
"#<#{self.class}:#{@instruction_store.address_description(address)}, #{@string}>"
|
183
|
-
end
|
184
|
-
|
185
|
-
# Returns info about all the instructions that this instruction could directly lead to
|
186
|
-
# (not counting interrupts, returns, and not accounting
|
187
|
-
# at all for what happens after the last word in the main program memory is executed).
|
188
|
-
# For instructions that pop from the call stack like RETURN and RETFIE, this will be
|
189
|
-
# the empty array.
|
190
|
-
# @return [Array(Transition)]
|
191
|
-
def transitions
|
192
|
-
@transitions ||= generate_transitions
|
193
|
-
end
|
194
|
-
|
195
|
-
# Returns the transition from this instruction to the specified instruction
|
196
|
-
# or nil if no such transition exists.
|
197
|
-
# @return Transition
|
198
|
-
def transition_to(instruction)
|
199
|
-
@transitions.find { |t| t.next_instruction == instruction }
|
200
|
-
end
|
201
|
-
|
202
|
-
# Returns the addresses of all the instructions this instruction could directly lead to.
|
203
|
-
# @return [Array(Integer)]
|
204
|
-
def next_addresses
|
205
|
-
transitions.map(&:next_address)
|
206
|
-
end
|
207
|
-
|
208
|
-
# Returns true if this is actually a valid instruction. Invalid
|
209
|
-
# instructions can occur when disassembling bytes that are not actually
|
210
|
-
# instructions.
|
211
|
-
def valid?
|
212
|
-
@valid
|
213
|
-
end
|
214
|
-
|
215
|
-
private
|
216
|
-
|
217
|
-
# For certain opcodes, this method gets over-written.
|
218
|
-
def generate_transitions
|
219
|
-
[advance(1)]
|
220
|
-
end
|
221
|
-
|
222
|
-
# Makes a transition representing the default behavior: the microcontroller
|
223
|
-
# will increment the program counter and execute the next instruction in memory.
|
224
|
-
def advance(num)
|
225
|
-
transition(address + num * size)
|
226
|
-
end
|
227
|
-
|
228
|
-
def transition(addr, attrs = {})
|
229
|
-
Transition.new(self, addr, @instruction_store, attrs)
|
230
|
-
end
|
231
|
-
|
232
|
-
private
|
233
|
-
|
234
|
-
# Returns the address indicated by the operand 'k'.
|
235
|
-
# k is assumed to be a word address and it is assumed to be absolute
|
236
|
-
# k=0 is word 0 of memory, k=1 is word one.
|
237
|
-
# We need to multiply by the address increment because on PIC18
|
238
|
-
# program memory addresses are actually byte-based instead of word-based.
|
239
|
-
def k_address
|
240
|
-
operands[:k] * @address_increment
|
241
|
-
end
|
242
|
-
|
243
|
-
def n_address
|
244
|
-
address + @address_increment * (operands[:n] + 1)
|
245
|
-
end
|
246
|
-
|
247
|
-
def relative_k_address
|
248
|
-
address + @address_increment * (operands[:k] + 1)
|
249
|
-
end
|
250
|
-
|
251
|
-
def relative_target_address
|
252
|
-
if operands[:k]
|
253
|
-
relative_k_address
|
254
|
-
elsif operands[:n]
|
255
|
-
n_address
|
256
|
-
else
|
257
|
-
raise 'This instruction does not have fields k or n.'
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
### Modules that modify the behavior of the instruction. ###
|
262
|
-
|
263
|
-
# This module is mixed into any {Instruction} that represents a goto or branch.
|
264
|
-
module Goto
|
265
|
-
def generate_transitions
|
266
|
-
# Assumption: The GOTO instruction's k operand is absolute on all architectures
|
267
|
-
[transition(k_address, non_local: true)]
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
# This module is mixed into any {Instruction} that represents a conditional skip
|
272
|
-
# A conditional skip is an instruction that might cause the next instruction to be
|
273
|
-
# skipped depending on some condition.
|
274
|
-
module ConditionalSkip
|
275
|
-
def generate_transitions
|
276
|
-
[advance(1), advance(2)]
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
# This module is mixed into any {Instruction} that represents a return from a subroutine
|
281
|
-
# or a RESET instruction.
|
282
|
-
module ControlEnder
|
283
|
-
def generate_transitions
|
284
|
-
[]
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
# This module is mixed into any {Instruction} that represents a subroutine call.
|
289
|
-
module Call
|
290
|
-
def generate_transitions
|
291
|
-
[transition(k_address, call_depth_change: 1), advance(1)]
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
module RelativeCall
|
296
|
-
def generate_transitions
|
297
|
-
[transition(n_address, call_depth_change: 1), advance(1)]
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
module ConditionalRelativeBranch
|
302
|
-
def generate_transitions
|
303
|
-
[transition(n_address, non_local: true), advance(1)]
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
module RelativeBranch
|
308
|
-
def generate_transitions
|
309
|
-
[transition(relative_target_address, non_local: true)]
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
class Transition
|
314
|
-
attr_reader :previous_instruction
|
315
|
-
attr_reader :next_address
|
316
|
-
|
317
|
-
def initialize(previous_instruction, next_address, instruction_store, attrs)
|
318
|
-
@previous_instruction = previous_instruction
|
319
|
-
@next_address = next_address
|
320
|
-
@instruction_store = instruction_store
|
321
|
-
@attrs = attrs
|
322
|
-
end
|
323
|
-
|
324
|
-
def next_instruction
|
325
|
-
@next_instruction ||= @instruction_store.instruction(next_address)
|
326
|
-
end
|
327
|
-
|
328
|
-
def non_local?
|
329
|
-
@attrs.fetch(:non_local, false)
|
330
|
-
end
|
331
|
-
|
332
|
-
def call_depth_change
|
333
|
-
@attrs.fetch(:call_depth_change, 0)
|
334
|
-
end
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end
|