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 2006 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[] 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 */ 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 159 KernelStackRecord(StackFrame[] frames, byte[] rawBytes) 160 { 161 if (frames != null) { 162 stackFrames = (StackFrame[])frames.clone(); 163 } 164 if (rawBytes != null) { 165 rawStackData = (byte[])rawBytes.clone(); 166 } 167 validate(); 168 } 169 170 private void 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[] 188 getStackFrames() 189 { 190 if (stackFrames == null) { 191 return EMPTY_FRAMES; 192 } 193 return (StackFrame[])stackFrames.clone(); 194 } 195 196 /** 197 * Called by native code and by UserStackRecord in its 198 * setStackFrames() method. 199 */ 200 void 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[] 220 getRawStackData() 221 { 222 return (byte[])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 233 getValue() 234 { 235 return (byte[])rawStackData.clone(); 236 } 237 238 public List <StackFrame> 239 asList() 240 { 241 if (stackFrames == null) { 242 return Collections. <StackFrame> emptyList(); 243 } 244 return Collections.unmodifiableList(Arrays.asList(stackFrames)); 245 } 246 247 /** 248 * Compares the specified object with this {@code KernelStackRecord} 249 * for equality. Returns {@code true} if and only if the specified 250 * object is also a {@code KernelStackRecord} and both records have 251 * the same raw stack data. 252 * <p> 253 * This implementation first checks if the specified object is this 254 * {@code KernelStackRecord}. If so, it returns {@code true}. 255 * 256 * @return {@code true} if and only if the specified object is also 257 * a {@code KernelStackRecord} and both records have the same raw 258 * stack data 259 */ 260 @Override 261 public boolean 262 equals(Object o) 263 { 264 if (o == this) { 265 return true; 266 } 267 if (o instanceof KernelStackRecord) { 268 KernelStackRecord r = (KernelStackRecord)o; 269 return Arrays.equals(rawStackData, r.rawStackData); 270 } 271 return false; 272 } 273 274 /** 275 * Overridden to ensure that equal instances have equal hash codes. 276 */ 277 @Override 278 public int 279 hashCode() 280 { 281 return Arrays.hashCode(rawStackData); 282 } 283 284 /** 285 * Compares this record with the given stack record. Compares the 286 * first unequal pair of bytes at the same index in each record's 287 * raw stack data, or if all corresponding bytes are equal, compares 288 * the length of each record's array of raw stack data. The {@code 289 * compareTo()} method is compatible with {@link #equals(Object o) 290 * equals()}. 291 * <p> 292 * This implementation first checks if the specified record is this 293 * {@code KernelStackRecord}. If so, it returns {@code 0}. 294 * 295 * @return -1, 0, or 1 as this record's raw stack data is less than, 296 * equal to, or greater than the given record's raw stack data. 297 */ 298 public int 299 compareTo(KernelStackRecord r) 300 { 301 if (r == this) { 302 return 0; 303 } 304 305 int len1 = rawStackData.length; 306 int len2 = r.rawStackData.length; 307 int cmp = 0; 308 for (int i = 0; (cmp == 0) && (i < len1) && (i < len2); ++i) { 309 cmp = ((rawStackData[i] < r.rawStackData[i]) ? -1 : 310 ((rawStackData[i] > r.rawStackData[i]) ? 1 : 0)); 311 } 312 if (cmp == 0) { 313 cmp = ((len1 < len2) ? -1 : ((len1 > len2) ? 1 : 0)); 314 } 315 return cmp; 316 } 317 318 private void 319 readObject(ObjectInputStream s) 320 throws IOException, ClassNotFoundException 321 { 322 s.defaultReadObject(); 323 // Make a defensive copy of stack frames and raw bytes 324 if (stackFrames != null) { 325 stackFrames = (StackFrame[])stackFrames.clone(); 326 } 327 if (rawStackData != null) { 328 rawStackData = (byte[])rawStackData.clone(); 329 } 330 // check class invariants 331 try { 332 validate(); 333 } catch (Exception e) { 334 throw new InvalidObjectException(e.getMessage()); 335 } 336 } 337 338 /** 339 * Gets a multi-line string representation of a stack with one frame 340 * per line. Each line is of the format {@code 341 * module`function+offset}, where {@code module} and {@code 342 * function} are symbol names and offset is a hex integer preceded 343 * by {@code 0x}. For example: {@code genunix`open+0x19}. The 344 * offset (and the preceding '+' sign) are omitted if offset is 345 * zero. If function name lookup fails, the raw pointer value is 346 * used instead. In that case, the module name (and the ` 347 * delimiter) may or may not be present, depending on whether or not 348 * module lookup also fails, and a raw pointer value appears in 349 * place of {@code function+offset} as a hex value preceded by 350 * {@code 0x}. The format just described, not including surrounding 351 * whitespce, is defined in the native DTrace library and is as 352 * stable as that library definition. Each line is indented by an 353 * equal (unspecified) number of spaces. 354 * <p> 355 * If human-readable stack frames are not available (see {@link 356 * #getStackFrames()}), a table represenation of {@link 357 * #getRawStackData()} is returned instead. The table displays 16 358 * bytes per row in unsigned hex followed by the ASCII character 359 * representations of those bytes. Each unprintable character is 360 * represented by a period (.). 361 */ 362 @Override 363 public String 364 toString() 365 { 366 StackFrame[] frames = getStackFrames(); 367 if (frames.length == 0) { 368 return ScalarRecord.rawBytesString(rawStackData); 369 } 370 371 StringBuffer buf = new StringBuffer(); 372 buf.append('\n'); 373 for (StackFrame f : frames) { 374 for (int i = 0; i < STACK_INDENT; ++i) { 375 buf.append(' '); 376 } 377 buf.append(f); 378 buf.append('\n'); 379 } 380 return buf.toString(); 381 } 382 } 383