1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * ident "%Z%%M% %I% %E% SMI" 27 */ 28 package org.opensolaris.os.dtrace; 29 30 import java.util.*; 31 import java.io.*; 32 import java.util.regex.Pattern; 33 import java.beans.*; 34 35 /** 36 * A value generated by the DTrace {@code stack()} action. 37 * <p> 38 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 39 * 40 * @author Tom Erickson 41 */ 42 public final class KernelStackRecord implements StackValueRecord, 43 Serializable, Comparable <KernelStackRecord> 44 { 45 static final long serialVersionUID = 8616454544771346573L; 46 static final int STACK_INDENT = 14; 47 static final StackFrame[] EMPTY_FRAMES = new StackFrame[] {}; 48 49 static { 50 try { 51 BeanInfo info = Introspector.getBeanInfo(KernelStackRecord.class); 52 PersistenceDelegate persistenceDelegate = 53 new DefaultPersistenceDelegate( 54 new String[] {"stackFrames", "rawStackData"}) 55 { 56 /* 57 * Need to prevent DefaultPersistenceDelegate from using 58 * overridden equals() method, resulting in a 59 * StackOverFlowError. Revert to PersistenceDelegate 60 * implementation. See 61 * http://forum.java.sun.com/thread.jspa?threadID= 62 * 477019&tstart=135 63 */ 64 protected boolean 65 mutatesTo(Object oldInstance, Object newInstance) 66 { 67 return (newInstance != null && oldInstance != null && 68 oldInstance.getClass() == newInstance.getClass()); 69 } 70 }; 71 BeanDescriptor d = info.getBeanDescriptor(); 72 d.setValue("persistenceDelegate", persistenceDelegate); 73 } catch (IntrospectionException e) { 74 System.out.println(e); 75 } 76 } 77 78 /** 79 * Splits formatted call stack generated by DTrace stack() and 80 * ustack() actions into tokens delimited by whitespace. Matches 81 * any number of whitespace characters on either side of a newline. 82 * Can't assume that a line has no whitespace characters. A java 83 * stack might have the line "StubRoutines (1)", which must not get 84 * split into two tokens. 85 */ 86 static final Pattern STACK_TOKENIZER = Pattern.compile("\\s*\n\\s*"); 87 88 /** 89 * Called by JNI layer to convert a stack formatted by the native 90 * DTrace library into an unformatted array of stack frames. 91 * 92 * @param s string representation of stack data generated by the D 93 * {@code stack()}, {@code ustack()}, or {@code jstack()} action 94 * @return array of human-readable stack frames 95 */ 96 static StackFrame[] parse(String s)97 parse(String s) 98 { 99 // 100 // First trim the leading whitespace to avoid an initial empty 101 // element in the returned array. 102 // 103 s = s.trim(); 104 StackFrame[] frames; 105 if (s.length() == 0) { 106 frames = EMPTY_FRAMES; 107 } else { 108 String[] f = STACK_TOKENIZER.split(s); 109 int n = f.length; 110 frames = new StackFrame[n]; 111 for (int i = 0; i < n; ++i) { 112 frames[i] = new StackFrame(f[i]); 113 } 114 } 115 return frames; 116 } 117 118 /** @serial */ 119 private StackFrame[] stackFrames; 120 /** @serial */ 121 private byte[] rawStackData; 122 123 /** 124 * Called by native code and by UserStackRecord (in its constructor 125 * called by native code). 126 * 127 * @throws NullPointerException if rawBytes is {@code null} 128 */ KernelStackRecord(byte[] rawBytes)129 KernelStackRecord(byte[] rawBytes) 130 { 131 // No need for defensive copy; native code will not modify input 132 // raw bytes. 133 rawStackData = rawBytes; 134 if (rawStackData == null) { 135 throw new NullPointerException("raw stack data is null"); 136 } 137 } 138 139 /** 140 * Creates a {@code KernelStackRecord} with the given stack frames 141 * and raw stack data. Supports XML persistence. 142 * 143 * @param frames array of human-readable stack frames, copied so 144 * that later modifying the given frames array will not affect this 145 * {@code KernelStackRecord}; may be {@code null} or empty to 146 * indicate that the raw stack data was not converted to 147 * human-readable stack frames (see {@link 148 * StackValueRecord#getStackFrames()}) 149 * @param rawBytes array of raw bytes used to represent this stack 150 * value in the native DTrace library, needed to distinguish stacks 151 * that have the same display value but are considered distinct by 152 * DTrace; copied so that later modifying the given array will not 153 * affect this {@code KernelStackRecord} 154 * @throws NullPointerException if the given array of raw bytes is 155 * {@code null} or if any element of the {@code frames} array is 156 * {@code null} 157 */ 158 public KernelStackRecord(StackFrame[] frames, byte[] rawBytes)159 KernelStackRecord(StackFrame[] frames, byte[] rawBytes) 160 { 161 if (frames != null) { 162 stackFrames = frames.clone(); 163 } 164 if (rawBytes != null) { 165 rawStackData = rawBytes.clone(); 166 } 167 validate(); 168 } 169 170 private final void validate()171 validate() 172 { 173 if (rawStackData == null) { 174 throw new NullPointerException("raw stack data is null"); 175 } 176 // stackFrames may be null; if non-null, cannot contain null 177 // elements 178 if (stackFrames != null) { 179 for (StackFrame f : stackFrames) { 180 if (f == null) { 181 throw new NullPointerException("stack frame is null"); 182 } 183 } 184 } 185 } 186 187 public StackFrame[] getStackFrames()188 getStackFrames() 189 { 190 if (stackFrames == null) { 191 return EMPTY_FRAMES; 192 } 193 return stackFrames.clone(); 194 } 195 196 /** 197 * Called by native code and by UserStackRecord in its 198 * setStackFrames() method. 199 */ 200 void setStackFrames(StackFrame[] frames)201 setStackFrames(StackFrame[] frames) 202 { 203 // No need for defensive copy; native code will not modify input 204 // frames. 205 stackFrames = frames; 206 validate(); 207 } 208 209 /** 210 * Gets the native DTrace representation of this record's stack as 211 * an array of raw bytes. The raw bytes are used in {@link 212 * #equals(Object o) equals()} and {@link 213 * #compareTo(KernelStackRecord r) compareTo()} to test equality and 214 * to determine the natural ordering of kernel stack records. 215 * 216 * @return the native DTrace library's internal representation of 217 * this record's stack as a non-null array of bytes 218 */ 219 public byte[] getRawStackData()220 getRawStackData() 221 { 222 return rawStackData.clone(); 223 } 224 225 /** 226 * Gets the raw bytes used to represent this record's stack value in 227 * the native DTrace library. To get a human-readable 228 * representation, call {@link #toString()}. 229 * 230 * @return {@link #getRawStackData()} 231 */ 232 public Object getValue()233 getValue() 234 { 235 return rawStackData.clone(); 236 } 237 238 public List <StackFrame> asList()239 asList() 240 { 241 if (stackFrames == null) { 242 return Collections. <StackFrame> emptyList(); 243 } 244 return Collections. <StackFrame> unmodifiableList( 245 Arrays.asList(stackFrames)); 246 } 247 248 /** 249 * Compares the specified object with this {@code KernelStackRecord} 250 * for equality. Returns {@code true} if and only if the specified 251 * object is also a {@code KernelStackRecord} and both records have 252 * the same raw stack data. 253 * <p> 254 * This implementation first checks if the specified object is this 255 * {@code KernelStackRecord}. If so, it returns {@code true}. 256 * 257 * @return {@code true} if and only if the specified object is also 258 * a {@code KernelStackRecord} and both records have the same raw 259 * stack data 260 */ 261 @Override 262 public boolean equals(Object o)263 equals(Object o) 264 { 265 if (o == this) { 266 return true; 267 } 268 if (o instanceof KernelStackRecord) { 269 KernelStackRecord r = (KernelStackRecord)o; 270 return Arrays.equals(rawStackData, r.rawStackData); 271 } 272 return false; 273 } 274 275 /** 276 * Overridden to ensure that equal instances have equal hash codes. 277 */ 278 @Override 279 public int hashCode()280 hashCode() 281 { 282 return Arrays.hashCode(rawStackData); 283 } 284 285 /** 286 * Compares this record with the given {@code KernelStackRecord}. 287 * Compares the first unequal pair of bytes at the same index in 288 * each record's raw stack data, or if all corresponding bytes are 289 * equal, compares the length of each record's array of raw stack 290 * data. Corresponding bytes are compared as unsigned values. The 291 * {@code compareTo()} method is compatible with {@link 292 * #equals(Object o) equals()}. 293 * <p> 294 * This implementation first checks if the specified record is this 295 * {@code KernelStackRecord}. If so, it returns {@code 0}. 296 * 297 * @return -1, 0, or 1 as this record's raw stack data is less than, 298 * equal to, or greater than the given record's raw stack data. 299 */ 300 public int compareTo(KernelStackRecord r)301 compareTo(KernelStackRecord r) 302 { 303 if (r == this) { 304 return 0; 305 } 306 307 return ProbeData.compareByteArrays(rawStackData, r.rawStackData); 308 } 309 310 private void readObject(ObjectInputStream s)311 readObject(ObjectInputStream s) 312 throws IOException, ClassNotFoundException 313 { 314 s.defaultReadObject(); 315 // Make a defensive copy of stack frames and raw bytes 316 if (stackFrames != null) { 317 stackFrames = stackFrames.clone(); 318 } 319 if (rawStackData != null) { 320 rawStackData = rawStackData.clone(); 321 } 322 // check class invariants 323 try { 324 validate(); 325 } catch (Exception e) { 326 InvalidObjectException x = new InvalidObjectException( 327 e.getMessage()); 328 x.initCause(e); 329 throw x; 330 } 331 } 332 333 /** 334 * Gets a multi-line string representation of a stack with one frame 335 * per line. Each line is of the format {@code 336 * module`function+offset}, where {@code module} and {@code 337 * function} are symbol names and offset is a hex integer preceded 338 * by {@code 0x}. For example: {@code genunix`open+0x19}. The 339 * offset (and the preceding '+' sign) are omitted if offset is 340 * zero. If function name lookup fails, the raw pointer value is 341 * used instead. In that case, the module name (and the ` 342 * delimiter) may or may not be present, depending on whether or not 343 * module lookup also fails, and a raw pointer value appears in 344 * place of {@code function+offset} as a hex value preceded by 345 * {@code 0x}. The format just described, not including surrounding 346 * whitespce, is defined in the native DTrace library and is as 347 * stable as that library definition. Each line is indented by an 348 * equal (unspecified) number of spaces. 349 * <p> 350 * If human-readable stack frames are not available (see {@link 351 * #getStackFrames()}), a table represenation of {@link 352 * #getRawStackData()} is returned instead. The table displays 16 353 * bytes per row in unsigned hex followed by the ASCII character 354 * representations of those bytes. Each unprintable character is 355 * represented by a period (.). 356 */ 357 @Override 358 public String toString()359 toString() 360 { 361 StackFrame[] frames = getStackFrames(); 362 if (frames.length == 0) { 363 return ScalarRecord.rawBytesString(rawStackData); 364 } 365 366 StringBuilder buf = new StringBuilder(); 367 buf.append('\n'); 368 for (StackFrame f : frames) { 369 for (int i = 0; i < STACK_INDENT; ++i) { 370 buf.append(' '); 371 } 372 buf.append(f); 373 buf.append('\n'); 374 } 375 return buf.toString(); 376 } 377 } 378