1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package art;
18 
19 import java.lang.reflect.Executable;
20 import java.util.HashSet;
21 import java.util.Set;
22 import java.util.Objects;
23 
24 public class Breakpoint {
25   public static class Manager {
26     public static class BP {
27       public final Executable method;
28       public final long location;
29 
BP(Executable method)30       public BP(Executable method) {
31         this(method, getStartLocation(method));
32       }
33 
BP(Executable method, long location)34       public BP(Executable method, long location) {
35         this.method = method;
36         this.location = location;
37       }
38 
39       @Override
equals(Object other)40       public boolean equals(Object other) {
41         return (other instanceof BP) &&
42             method.equals(((BP)other).method) &&
43             location == ((BP)other).location;
44       }
45 
46       @Override
toString()47       public String toString() {
48         return method.toString() + " @ " + getLine();
49       }
50 
51       @Override
hashCode()52       public int hashCode() {
53         return Objects.hash(method, location);
54       }
55 
getLine()56       public int getLine() {
57         try {
58           LineNumber[] lines = getLineNumberTable(method);
59           int best = -1;
60           for (LineNumber l : lines) {
61             if (l.location > location) {
62               break;
63             } else {
64               best = l.line;
65             }
66           }
67           return best;
68         } catch (Exception e) {
69           return -1;
70         }
71       }
72     }
73 
74     private Set<BP> breaks = new HashSet<>();
75 
setBreakpoints(BP... bs)76     public void setBreakpoints(BP... bs) {
77       for (BP b : bs) {
78         if (breaks.add(b)) {
79           Breakpoint.setBreakpoint(b.method, b.location);
80         }
81       }
82     }
setBreakpoint(Executable method, long location)83     public void setBreakpoint(Executable method, long location) {
84       setBreakpoints(new BP(method, location));
85     }
86 
clearBreakpoints(BP... bs)87     public void clearBreakpoints(BP... bs) {
88       for (BP b : bs) {
89         if (breaks.remove(b)) {
90           Breakpoint.clearBreakpoint(b.method, b.location);
91         }
92       }
93     }
clearBreakpoint(Executable method, long location)94     public void clearBreakpoint(Executable method, long location) {
95       clearBreakpoints(new BP(method, location));
96     }
97 
clearAllBreakpoints()98     public void clearAllBreakpoints() {
99       clearBreakpoints(breaks.toArray(new BP[0]));
100     }
101   }
102 
startBreakpointWatch(Class<?> methodClass, Executable breakpointReached, Thread thr)103   public static void startBreakpointWatch(Class<?> methodClass,
104                                           Executable breakpointReached,
105                                           Thread thr) {
106     startBreakpointWatch(methodClass, breakpointReached, false, thr);
107   }
108 
109   /**
110    * Enables the trapping of breakpoint events.
111    *
112    * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
113    */
startBreakpointWatch(Class<?> methodClass, Executable breakpointReached, boolean allowRecursive, Thread thr)114   public static native void startBreakpointWatch(Class<?> methodClass,
115                                                  Executable breakpointReached,
116                                                  boolean allowRecursive,
117                                                  Thread thr);
stopBreakpointWatch(Thread thr)118   public static native void stopBreakpointWatch(Thread thr);
119 
120   public static final class LineNumber implements Comparable<LineNumber> {
121     public final long location;
122     public final int line;
123 
LineNumber(long loc, int line)124     private LineNumber(long loc, int line) {
125       this.location = loc;
126       this.line = line;
127     }
128 
equals(Object other)129     public boolean equals(Object other) {
130       return other instanceof LineNumber && ((LineNumber)other).line == line &&
131           ((LineNumber)other).location == location;
132     }
133 
compareTo(LineNumber other)134     public int compareTo(LineNumber other) {
135       int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
136       if (v != 0) {
137         return v;
138       } else {
139         return Long.valueOf(location).compareTo(Long.valueOf(other.location));
140       }
141     }
142   }
143 
setBreakpoint(Executable m, long loc)144   public static native void setBreakpoint(Executable m, long loc);
setBreakpoint(Executable m, LineNumber l)145   public static void setBreakpoint(Executable m, LineNumber l) {
146     setBreakpoint(m, l.location);
147   }
148 
clearBreakpoint(Executable m, long loc)149   public static native void clearBreakpoint(Executable m, long loc);
clearBreakpoint(Executable m, LineNumber l)150   public static void clearBreakpoint(Executable m, LineNumber l) {
151     clearBreakpoint(m, l.location);
152   }
153 
getLineNumberTableNative(Executable m)154   private static native Object[] getLineNumberTableNative(Executable m);
getLineNumberTable(Executable m)155   public static LineNumber[] getLineNumberTable(Executable m) {
156     Object[] nativeTable = getLineNumberTableNative(m);
157     long[] location = (long[])(nativeTable[0]);
158     int[] lines = (int[])(nativeTable[1]);
159     if (lines.length != location.length) {
160       throw new Error("Lines and locations have different lengths!");
161     }
162     LineNumber[] out = new LineNumber[lines.length];
163     for (int i = 0; i < lines.length; i++) {
164       out[i] = new LineNumber(location[i], lines[i]);
165     }
166     return out;
167   }
168 
getStartLocation(Executable m)169   public static native long getStartLocation(Executable m);
170 
locationToLine(Executable m, long location)171   public static int locationToLine(Executable m, long location) {
172     try {
173       Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
174       int best = -1;
175       for (Breakpoint.LineNumber l : lines) {
176         if (l.location > location) {
177           break;
178         } else {
179           best = l.line;
180         }
181       }
182       return best;
183     } catch (Exception e) {
184       return -1;
185     }
186   }
187 
lineToLocation(Executable m, int line)188   public static long lineToLocation(Executable m, int line) throws Exception {
189     try {
190       Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
191       for (Breakpoint.LineNumber l : lines) {
192         if (l.line == line) {
193           return l.location;
194         }
195       }
196       throw new Exception("Unable to find line " + line + " in " + m);
197     } catch (Exception e) {
198       throw new Exception("Unable to get line number info for " + m, e);
199     }
200   }
201 }
202 
203