points, List texts) {
58 | preAnalysis();
59 |
60 | int nTraces = texts.size();
61 | @SuppressWarnings("unchecked")
62 | B[] pointsMapped = (B[])points.stream().map(fnBundle).toArray(PABundle[]::new);
63 |
64 | // Iterate through all subkeys
65 | for(int sK = 0; sK < nKeys; sK++) {
66 | // Map the plaintexts to the guesses for the current subkey
67 | final int k = sK;
68 | @SuppressWarnings("unchecked")
69 | Iterable>[] subkeyGuesses = texts.stream().map((x) -> createGuess(x,k)).toArray(Iterable[]::new);
70 |
71 | // Create nBits CPABundle objects, one per bit of output
72 | CryptoBitVector[] bitLevel = IntStream.range(0,nBits).mapToObj((x) -> new CryptoBitVector(nTraces)).toArray(CryptoBitVector[]::new);
73 |
74 | // For each plaintext => SBOX output guess
75 | for(int g = 0; g < subkeyGuesses.length; g++) {
76 | Iterator> bitIt = subkeyGuesses[g].iterator();
77 | while(bitIt.hasNext()) {
78 | Pair n = bitIt.next();
79 | bitLevel[n.x].assignBit(g, n.y==1);
80 | }
81 | }
82 | // So after this, bitLevel[nBits] contains CryptoBitVectors of the size of
83 | // the number of traces. The contents of the bitvectors are the raw bits
84 | // from the guesses.
85 |
86 | // Store that information into the guesses array
87 | for(int bitNum = 0; bitNum < bitLevel.length; bitNum++) {
88 | B blB = fnBundle.apply(bitLevel[bitNum]);
89 | for(B point : pointsMapped)
90 | recordMax(point.compute(blB), bitNum, sK);
91 | }
92 | }
93 | postAnalysisCommon();
94 | }
95 | protected void postAnalysisCommon() {
96 | for(int i = 0; i < nBits; i++) {
97 | int hp = highest_period[i];
98 | Printer.printf("Best correlation for bit %02d: subkey %02x %f\n", i, hp, highest_correlations[i][hp]);
99 | }
100 | for(int g = 0; g < nBits/nBitsPer; g++) {
101 | Score[] correlation_scores = IntStream.range(0,nKeys).mapToObj((x) -> new Score(x,0.0)).toArray(Score[]::new);
102 | for(int i = 0; i < nBitsPer; i++) {
103 | for(int sK = 0; sK < nKeys; sK++) {
104 | correlation_scores[sK].y += Math.abs(highest_correlations[g*nBitsPer+i][sK]);
105 | }
106 | }
107 | class Sortbyy implements Comparator {
108 | public int compare(Score l, Score r) {
109 | return l.y == r.y ? 0 : l.y > r.y ? -1 : 1;
110 | }
111 | }
112 | Arrays.sort(correlation_scores, new Sortbyy());
113 | for(int i = 0; i < nBitsPer; i++) {
114 | Printer.printf("%s %d, best key %d: %02x %f\n", quantityDesc, g, i, correlation_scores[i].x, correlation_scores[i].y);
115 | }
116 | Printer.printf("-----\n");
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/src/main/java/ghidra/pal/absint/tvl/TVLBitVector.java:
--------------------------------------------------------------------------------
1 | package ghidra.pal.absint.tvl;
2 |
3 | import ghidra.pal.util.Pair;
4 |
5 | //This class mostly came about from the fact that this code was originally a
6 | //very literal port from the OCaml. My OCaml framework had boolean data types
7 | //that were a single bit apiece. In Ghidra, everything seems to be
8 | //byte-granularity (similar to Hex-Rays microcode). So I wrote this analysis
9 | //allowing objects of arbitrary number of bits, whereas the Ghidra data types
10 | //are coarser. Unless there's a reason bit-granularity is useful, I should
11 | //probably re-design the architecture to work upon bytes.
12 | //
13 | //Anyway, this class just exists to signify in method signatures that bit
14 | //sizes are being specified, versus Ghidra's byte size specifications. For
15 | //proper type-safety, I should also have a BitSizeAdapter class.
16 | class GhidraSizeAdapter {
17 | public int sz;
18 | public GhidraSizeAdapter(int s) { sz = s; }
19 | }
20 |
21 | //This class implements aggregates of an arbitrary number of three-valued
22 | //quantities. I can think of many ways that I might refine this in a later
23 | //implementation:
24 | //* Use 2 bits apiece for a given 3-valued bit, rather than an entire byte.
25 | //* Use a byte-granularity by default, and implement larger quantities as
26 | //aggregates ("ByteVectors" instead of "BitVectors"). This allows a single
27 | //16-bit quantity to represent a byte. I should provide seamless access to
28 | //the elements across different bytes in this case.
29 | //* Use four values instead of three, basically Powerset({0,1}), where the
30 | //elements are:
31 | //* {0,1} (equivalent to the existing 1/2)
32 | //* {0} (equivalent to the existing 0)
33 | //* {1} (equivalent to the existing 1)
34 | //* {} (new -- signifying uninitialized)
35 | //The advantage of this is being more mathematically compatible with the
36 | //theoretical framework of abstract interpretation, in particular, the
37 | //existence of bottom elements. As for why I didn't code it that way in the
38 | //first place, again, this is a more-or-less literal port of the OCaml
39 | //version, and I know a lot more about abstract interpretation now than when
40 | //I originally created this analysis nine years ago.
41 | public class TVLBitVector {
42 | // Constants dictating the three possibilities
43 | public static final byte TVL_0 = 0;
44 | public static final byte TVL_HALF = 1;
45 | public static final byte TVL_1 = 2;
46 |
47 | // The raw array of 3-valued quantities.
48 | byte AbsValue[];
49 |
50 | // These methods are just a reflection of my lack of understanding of the
51 | // Java philosophy of best practices of object-oriented design. It's a sort
52 | // of schizophrenic mixture of encapsulation-and-data-hiding-but-not-really.
53 | public int Size() { return AbsValue.length; }
54 | public byte[] Value() { return AbsValue; }
55 |
56 | // If there are no 1/2 bits, and the constant fits in a long, get the value
57 | // and bit size.
58 | public Pair GetConstantValue()
59 | {
60 | if(AbsValue.length > 64)
61 | return null;
62 |
63 | long val = 0;
64 | for(int i = 0; i < AbsValue.length; i++) {
65 | if(AbsValue[i] == TVL_HALF)
66 | return null;
67 | if(AbsValue[i] == TVL_1)
68 | val |= 1 << i;
69 | }
70 | return new Pair(AbsValue.length,val);
71 | }
72 |
73 | // Set every bit to 1/2.
74 | void MakeTop()
75 | {
76 | for(int i = 0; i < AbsValue.length; i++)
77 | AbsValue[i] = TVL_HALF;
78 | }
79 |
80 | static final char[] Representation = { '0', '?', '1' };
81 |
82 | // Print the bit-vector as a series of bytes, with "?" used for 1/2 bits.
83 | @Override
84 | public String toString()
85 | {
86 | String s = "";
87 | for(int i = AbsValue.length-1; i >= 0; i--)
88 | s += Representation[AbsValue[i]];
89 | return s;
90 | }
91 |
92 | // Below here are the constructors and initializers.
93 |
94 | // sz: number of bits. Initialize all to 1/2.
95 | public TVLBitVector(int sz)
96 | {
97 | AbsValue = new byte[sz];
98 | MakeTop();
99 | }
100 |
101 | // gsa: container of a number of bytes. Initialize all to 1/2.
102 | public TVLBitVector(GhidraSizeAdapter gsa)
103 | {
104 | AbsValue = new byte[gsa.sz*8];
105 | MakeTop();
106 | }
107 |
108 | // Helper method to initialize a bitvector given a constant value.
109 | void InitializeFromConstant(int sz, long value)
110 | {
111 | AbsValue = new byte[sz];
112 | for (int i = 0; i < sz; i++)
113 | AbsValue[i] = ((value >> i) & 1) == 0 ? TVL_0 : TVL_1;
114 | }
115 |
116 | // sz: number of bits. value: constant.
117 | public TVLBitVector(int sz, long value)
118 | {
119 | InitializeFromConstant(sz,value);
120 | }
121 |
122 | // gsa: container of a number of bytes. value: constant.
123 | public TVLBitVector(GhidraSizeAdapter gsa, long value)
124 | {
125 | InitializeFromConstant(gsa.sz*8,value);
126 | }
127 |
128 | // Arr: an existing array of three-valued bits.
129 | public TVLBitVector(byte[] Arr)
130 | {
131 | AbsValue = Arr;
132 | }
133 |
134 | // Copy this object.
135 | public TVLBitVector clone()
136 | {
137 | return new TVLBitVector(AbsValue.clone());
138 | }
139 |
140 | public byte GetSign()
141 | {
142 | return AbsValue[AbsValue.length-1];
143 | }
144 |
145 | public boolean isTop() {
146 | for(int i = 0; i < AbsValue.length; i++)
147 | if(AbsValue[i] != TVL_HALF)
148 | return false;
149 | return true;
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/src/main/java/ghidra/pal/absint/tvl/TVLAbstractGhidraStateUtil.java:
--------------------------------------------------------------------------------
1 | package ghidra.pal.absint.tvl;
2 |
3 | import java.util.HashMap;
4 |
5 | // Implements Join on the level of AbstractMemory objects, memory HashMaps,
6 | // and entire state objects.
7 | public final class TVLAbstractGhidraStateUtil {
8 | private TVLAbstractGhidraStateUtil() {}
9 |
10 | // Join two individual TVLAbstractMemory objects.
11 | public static TVLAbstractMemory Join(TVLAbstractMemory lhs, TVLAbstractMemory rhs) {
12 | TVLAbstractMemory smaller, larger;
13 |
14 | // Optimization: start with the smaller memory.
15 | if(lhs.Contents.size() <= rhs.Contents.size()) {
16 | smaller = lhs;
17 | larger = rhs;
18 | }
19 | else {
20 | smaller = rhs;
21 | larger = lhs;
22 | }
23 | // Join together the individual entities.
24 | TVLAbstractMemory out = new TVLAbstractMemory(lhs.bigEndian);
25 | for(HashMap.Entry entry : smaller.Contents.entrySet()){
26 | // If the other map has an entity with the same key...
27 | if(larger.Contents.containsKey(entry.getKey())) {
28 | TVLBitVector other = larger.Contents.get(entry.getKey());
29 | TVLBitVector thisbv = entry.getValue();
30 | TVLBitVector joined = TVLBitVectorUtil.Join(other,thisbv);
31 |
32 | // Only add them to the output if non-Top.
33 | if(!joined.isTop())
34 | out.Contents.put(entry.getKey(), joined);
35 | }
36 | }
37 | return out;
38 | }
39 |
40 | public static HashMap Join(HashMap lhs, HashMap rhs) {
41 | HashMap smaller, larger;
42 |
43 | // Optimization: start with the smaller HashMap.
44 | if(lhs.size() <= rhs.size()) {
45 | smaller = lhs;
46 | larger = rhs;
47 | }
48 | else {
49 | smaller = rhs;
50 | larger = lhs;
51 | }
52 |
53 | // Join together the values in the map by key.
54 | HashMap memOut = new HashMap();
55 | for(HashMap.Entry entry : smaller.entrySet()) {
56 | // If the other map has an entity with the same key...
57 | if(larger.containsKey(entry.getKey())) {
58 | TVLAbstractMemory other = larger.get(entry.getKey());
59 | TVLAbstractMemory thismem = entry.getValue();
60 | TVLAbstractMemory joined = Join(other,thismem);
61 | // Join them with the function above and add to result.
62 | memOut.put(entry.getKey(), joined);
63 | }
64 | }
65 | return memOut;
66 | }
67 |
68 | // Join two entire state objects.
69 | public static TVLAbstractGhidraState Join(TVLAbstractGhidraState lhs, TVLAbstractGhidraState rhs) {
70 | TVLAbstractGhidraState out = new TVLAbstractGhidraState(lhs.bigEndian);
71 | out.Registers = Join(lhs.Registers, rhs.Registers);
72 | out.Uniques = Join(lhs.Uniques, rhs.Uniques);
73 | out.Memories = Join(lhs.Memories, rhs.Memories);
74 | return out;
75 | }
76 |
77 | // Join two individual TVLAbstractMemory objects.
78 | public static boolean isEqualTo(TVLAbstractMemory lhs, TVLAbstractMemory rhs) {
79 | TVLAbstractMemory smaller, larger;
80 |
81 | // Optimization: start with the smaller memory.
82 | if(lhs.Contents.size() <= rhs.Contents.size()) {
83 | smaller = lhs;
84 | larger = rhs;
85 | }
86 | else {
87 | smaller = rhs;
88 | larger = lhs;
89 | }
90 |
91 | for(HashMap.Entry entry : smaller.Contents.entrySet()){
92 | // If the other map has an entity with the same key...
93 | if(larger.Contents.containsKey(entry.getKey())) {
94 | TVLBitVector other = larger.Contents.get(entry.getKey());
95 | TVLBitVector thisbv = entry.getValue();
96 | if(!TVLBitVectorUtil.isEqualTo(other, thisbv)) {
97 | return false;
98 | }
99 | }
100 | else if(!entry.getValue().isTop()) {
101 | return false;
102 | }
103 | }
104 | for(HashMap.Entry entry : larger.Contents.entrySet()){
105 | // If the other map has an entity with the same key...
106 | if(smaller.Contents.containsKey(entry.getKey()))
107 | continue;
108 | else if(!entry.getValue().isTop()) {
109 | return false;
110 | }
111 | }
112 | return true;
113 | }
114 |
115 | // Join two individual TVLAbstractMemory objects.
116 | public static boolean isEqualTo(HashMap lhs, HashMap rhs) {
117 | HashMap smaller, larger;
118 |
119 | // Optimization: start with the smaller HashMap.
120 | if(lhs.size() <= rhs.size()) {
121 | smaller = lhs;
122 | larger = rhs;
123 | }
124 | else {
125 | smaller = rhs;
126 | larger = lhs;
127 | }
128 |
129 | for(HashMap.Entry entry : smaller.entrySet()){
130 | // If the other map has an entity with the same key...
131 | if(larger.containsKey(entry.getKey())) {
132 | TVLAbstractMemory other = larger.get(entry.getKey());
133 | TVLAbstractMemory thisbv = entry.getValue();
134 | if(!isEqualTo(other, thisbv)) {
135 | return false;
136 | }
137 | }
138 | else if(!entry.getValue().isTop()) {
139 | return false;
140 | }
141 | }
142 | for(HashMap.Entry entry : larger.entrySet()){
143 | // If the other map has an entity with the same key...
144 | if(smaller.containsKey(entry.getKey()))
145 | continue;
146 | else if(!entry.getValue().isTop()) {
147 | return false;
148 | }
149 | }
150 | return true;
151 | }
152 |
153 | // Join two entire state objects.
154 | public static boolean isEqualTo(TVLAbstractGhidraState lhs, TVLAbstractGhidraState rhs) {
155 | if(!isEqualTo(lhs.Registers, rhs.Registers))
156 | return false;
157 | if(!isEqualTo(lhs.Uniques, rhs.Uniques))
158 | return false;
159 | if(!isEqualTo(lhs.Memories, rhs.Memories))
160 | return false;
161 | return true;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/main/java/ghidra/pal/absint/tvl/TVLAbstractInterpretCFGStateBundle.java:
--------------------------------------------------------------------------------
1 | package ghidra.pal.absint.tvl;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.HashSet;
6 | import java.util.Iterator;
7 | import java.util.List;
8 |
9 | import ghidra.pal.cfg.CFG;
10 | import ghidra.pal.cfg.CFGEdge;
11 | import ghidra.pal.cfg.CFGVertex;
12 | import ghidra.pal.util.Pair;
13 | import ghidra.pal.util.Printer;
14 | import ghidra.program.model.address.Address;
15 | import ghidra.program.model.listing.Program;
16 | import ghidra.program.model.pcode.PcodeOp;
17 |
18 | // This class holds the state used for performing the abstract interpretation
19 | // upon a CFG. Thus, we decouple the state data from the code that computes it,
20 | // to simplify code that consumes the analysis results.
21 | public class TVLAbstractInterpretCFGStateBundle {
22 | Program currentProgram;
23 | TVLAbstractGhidraState rootInputState;
24 | HashSet,PcodeOp>> initialVerticesSet;
25 | HashMap,PcodeOp>, TVLAbstractGhidraState> outputStates;
26 | boolean debug;
27 |
28 | public TVLAbstractInterpretBlock pb;
29 | public CFG,PcodeOp> cfg;
30 | public TVLAbstractInterpretCFGStateBundle(Program p) {
31 | currentProgram = p;
32 | pb = new TVLAbstractInterpretBlock(currentProgram);
33 | outputStates = new HashMap,PcodeOp>, TVLAbstractGhidraState>();
34 | debug = false;
35 | }
36 | public void Init(CFG,PcodeOp> g, TVLAbstractGhidraState initialState) {
37 | cfg = g;
38 | rootInputState = initialState;
39 |
40 | // Store the initial vertices as a HashSet for fast lookups.
41 | initialVerticesSet = new HashSet,PcodeOp>>();
42 | initialVerticesSet.addAll(g.getInitialVertices());
43 | }
44 | public void setDebug(boolean d) {
45 | debug = d;
46 | }
47 | void DebugPrint(String format, Object... args) {
48 | if(debug)
49 | Printer.printf(format, args);
50 | }
51 |
52 | // Get the input state for a given vertex. If it's an initial vertex, clone
53 | // the cached one. Otherwise, form it by merging the output states from its
54 | // predecessors.
55 | public TVLAbstractGhidraState getInputState(CFGVertex,PcodeOp> currVertex) {
56 | Pair currLocator = currVertex.getLocator();
57 | TVLAbstractGhidraState joined = null;
58 |
59 | // If it was an initial vertex, clone the root state.
60 | if(initialVerticesSet.contains(currVertex)) {
61 | DebugPrint("%s: was initial vertex, using saved input state\n", currLocator.toString());
62 | return rootInputState.clone();
63 | }
64 | // Otherwise, merge outgoing states from incoming edges.
65 | DebugPrint("%s: was not initial vertex, joining output states from successors\n", currLocator.toString());
66 |
67 | // Iterate through all incoming edges.
68 | Iterator,PcodeOp>> itInEdges = cfg.getInEdges(currVertex).iterator();
69 | while(itInEdges.hasNext()) {
70 | CFGEdge,PcodeOp> e = itInEdges.next();
71 | CFGVertex,PcodeOp> inVertex = e.getStart();
72 |
73 | // If we have cached data for the output of an incoming vertex...
74 | if(outputStates.containsKey(inVertex)) {
75 | DebugPrint("%s: incoming vertex %s had cached state\n", currLocator.toString(), inVertex.getLocator().toString());
76 | TVLAbstractGhidraState inVertexState = outputStates.get(inVertex);
77 | // If this is the first incoming vertex with state, replace
78 | // the null pointer with a clone of the state.
79 | if(joined == null) {
80 | DebugPrint("\tPrevious state was null, replacing\n");
81 | joined = inVertexState.clone();
82 | }
83 | // Otherwise, join the existing state with this one.
84 | else {
85 | DebugPrint("\tHad previous state\n");
86 | joined = TVLAbstractGhidraStateUtil.Join(joined, inVertexState);
87 | }
88 | }
89 |
90 | // If we didn't have cached data, skip it.
91 | else {
92 | DebugPrint("%s: incoming vertex %s did not have cached state\n", currLocator.toString(), inVertex.getLocator().toString());
93 | }
94 | }
95 | // If no incoming vertex had cached data, create a Top state.
96 | if(joined == null) {
97 | DebugPrint("%s: no cached incoming vertex states, using top\n", currLocator.toString());
98 | return new TVLAbstractGhidraState(currentProgram.getLanguage().isBigEndian());
99 | }
100 | // Return the joined state.
101 | return joined;
102 | }
103 |
104 | // Associate the output state with the vertex. If it's the same as the
105 | // previous output state, there's no need to process the children again.
106 | public boolean updateOutputState(CFGVertex,PcodeOp> currVertex, TVLAbstractGhidraState out) {
107 | // If we've seen this block before, and the output state hasn't
108 | // changed, there's no need to process the children.
109 | if(outputStates.containsKey(currVertex)) {
110 | TVLAbstractGhidraState saved = outputStates.get(currVertex);
111 | if(TVLAbstractGhidraStateUtil.isEqualTo(saved,out))
112 | return false;
113 | }
114 | // If we haven't seen it before, or if the output changed, update
115 | // the output state.
116 | outputStates.put(currVertex, out);
117 | return true;
118 | }
119 |
120 | // After analysis, get a list of unvisited vertices, i.e., those targeted
121 | // solely by opaque predicates.
122 | public List,PcodeOp>> getUnvisited() {
123 | List,PcodeOp>> out = new ArrayList,PcodeOp>>();
124 | Iterator,PcodeOp>> itVert = cfg.getVertices().iterator();
125 | int numUnvisited = 0;
126 | while(itVert.hasNext()) {
127 | CFGVertex,PcodeOp> currVertex = itVert.next();
128 | if(!outputStates.containsKey(currVertex)) {
129 | DebugPrint("%s: vertex was not visited\n", currVertex.getLocator().toString());
130 | numUnvisited++;
131 | out.add(currVertex);
132 | }
133 | }
134 | DebugPrint("%s: %d vertices unvisited due to opaque predicates\n", cfg.getBeginAddr().toString(), numUnvisited);
135 | return out;
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/ghidra/pal/cfg/PcodeOpProvider.java:
--------------------------------------------------------------------------------
1 | package ghidra.pal.cfg;
2 |
3 | import ghidra.pcode.error.LowlevelError;
4 | import ghidra.pcode.opbehavior.BinaryOpBehavior;
5 | import ghidra.pcode.opbehavior.OpBehavior;
6 | import ghidra.pcode.opbehavior.UnaryOpBehavior;
7 | import ghidra.pcode.pcoderaw.PcodeOpRaw;
8 | import ghidra.program.model.address.Address;
9 | import ghidra.program.model.listing.Instruction;
10 | import ghidra.program.model.listing.Program;
11 | import ghidra.program.model.pcode.PcodeOp;
12 | import ghidra.pal.util.Pair;
13 |
14 | // This class analyzes PcodeOp objects and adds flows to the CFG state object
15 | // throughout CFG construction.
16 | public class PcodeOpProvider implements CFGVertexDetailProvider,PcodeOp> {
17 | CacheInstructions Cache;
18 |
19 | public PcodeOpProvider(Program p, CacheInstructions cache) {
20 | Cache = cache;
21 | Cache.init(p);
22 | }
23 |
24 | // Common method to compute the location for the fall-through.
25 | Pair getFallthruLoc(Pair curLoc, Instruction insn) {
26 | int pcodeLen = insn.getPcode().length;
27 | int insnLen = insn.getLength();
28 | int nextLoc = curLoc.y+1;
29 | // If we've reached the end of the pcode for this Instruction, move on
30 | // to the next one.
31 | if(nextLoc >= pcodeLen)
32 | return new Pair(curLoc.x.addWrap(insnLen),0);
33 | // Otherwise, increment the pcode index and return a new location.
34 | return new Pair(curLoc.x, nextLoc);
35 | }
36 |
37 | // Common method to compute the location for the taken destination.
38 | Pair getBranchTakenLoc(Pair curLoc, Instruction insn) {
39 | int insnLen = insn.getLength();
40 | PcodeOp[] pcode = insn.getPcode();
41 | PcodeOp currPcode = pcode[curLoc.y];
42 |
43 | // I adapted this from Ghidra's Emulate.java.
44 | Address destaddr = currPcode.getInput(0).getAddress();
45 | int current_op = curLoc.y;
46 |
47 | // If the destination is in the "constant space", presumably this means
48 | // it's within a single Instruction's Pcode block.
49 | if (destaddr.getAddressSpace().isConstantSpace()) {
50 |
51 | // Compute the destination as an index within the PcodeOp array.
52 | long id = destaddr.getOffset();
53 | id = id + current_op;
54 | current_op = (int) id;
55 |
56 | // If the destination is the last PcodeOp index, round it up to the
57 | // next Instruction according to logic ripped from Emulate.java.
58 | if (current_op == pcode.length)
59 | return new Pair(curLoc.x.addWrap(insnLen),0);
60 |
61 | // If a negative displacement, or outside of the Pcode, die. Again,
62 | // logic stolen from Emulate.
63 | else if ((current_op < 0) || (current_op >= pcode.length))
64 | throw new LowlevelError("Bad intra-instruction branch");
65 |
66 | // Else, if the destination is within the PcodeOp block, return
67 | // that location.
68 | else
69 | return new Pair(curLoc.x, current_op);
70 | }
71 | // Otherwise, if the destination address is an address, return the
72 | // location of its first PcodeOp.
73 | return new Pair(destaddr,0);
74 | }
75 |
76 | // Adds flows for an unconditional branch.
77 | void branch(Pair