KernelStackRecord.java revision 4ae67516a1d5dc4a5dbc761762bad5b596647388
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* ident "%Z%%M% %I% %E% SMI"
*/
package org.opensolaris.os.dtrace;
import java.util.*;
import java.io.*;
import java.util.regex.Pattern;
import java.beans.*;
/**
* A value generated by the DTrace {@code stack()} action.
* <p>
* Immutable. Supports persistence using {@link java.beans.XMLEncoder}.
*
* @author Tom Erickson
*/
public final class KernelStackRecord implements StackValueRecord,
Serializable, Comparable <KernelStackRecord>
{
static final long serialVersionUID = 8616454544771346573L;
static final int STACK_INDENT = 14;
static final StackFrame[] EMPTY_FRAMES = new StackFrame[] {};
static {
try {
BeanInfo info = Introspector.getBeanInfo(KernelStackRecord.class);
PersistenceDelegate persistenceDelegate =
new DefaultPersistenceDelegate(
new String[] {"stackFrames", "rawStackData"})
{
/*
* Need to prevent DefaultPersistenceDelegate from using
* overridden equals() method, resulting in a
* StackOverFlowError. Revert to PersistenceDelegate
* implementation. See
* http://forum.java.sun.com/thread.jspa?threadID=
* 477019&tstart=135
*/
protected boolean
mutatesTo(Object oldInstance, Object newInstance)
{
return (newInstance != null && oldInstance != null &&
oldInstance.getClass() == newInstance.getClass());
}
};
BeanDescriptor d = info.getBeanDescriptor();
d.setValue("persistenceDelegate", persistenceDelegate);
} catch (IntrospectionException e) {
System.out.println(e);
}
}
/**
* Splits formatted call stack generated by DTrace stack() and
* ustack() actions into tokens delimited by whitespace. Matches
* any number of whitespace characters on either side of a newline.
* Can't assume that a line has no whitespace characters. A java
* stack might have the line "StubRoutines (1)", which must not get
* split into two tokens.
*/
static final Pattern STACK_TOKENIZER = Pattern.compile("\\s*\n\\s*");
/**
* Called by JNI layer to convert a stack formatted by the native
* DTrace library into an unformatted array of stack frames.
*
* @param s string representation of stack data generated by the D
* {@code stack()}, {@code ustack()}, or {@code jstack()} action
* @return array of human-readable stack frames
*/
static StackFrame[]
parse(String s)
{
//
// First trim the leading whitespace to avoid an initial empty
// element in the returned array.
//
s = s.trim();
StackFrame[] frames;
if (s.length() == 0) {
frames = EMPTY_FRAMES;
} else {
String[] f = STACK_TOKENIZER.split(s);
int n = f.length;
frames = new StackFrame[n];
for (int i = 0; i < n; ++i) {
frames[i] = new StackFrame(f[i]);
}
}
return frames;
}
/** @serial */
private StackFrame[] stackFrames;
/** @serial */
private byte[] rawStackData;
/**
* Called by native code and by UserStackRecord (in its constructor
* called by native code).
*
* @throws NullPointerException if rawBytes is {@code null}
*/
KernelStackRecord(byte[] rawBytes)
{
// No need for defensive copy; native code will not modify input
// raw bytes.
rawStackData = rawBytes;
if (rawStackData == null) {
throw new NullPointerException("raw stack data is null");
}
}
/**
* Creates a {@code KernelStackRecord} with the given stack frames
* and raw stack data. Supports XML persistence.
*
* @param frames array of human-readable stack frames, copied so
* that later modifying the given frames array will not affect this
* {@code KernelStackRecord}; may be {@code null} or empty to
* indicate that the raw stack data was not converted to
* human-readable stack frames (see {@link
* StackValueRecord#getStackFrames()})
* @param rawBytes array of raw bytes used to represent this stack
* value in the native DTrace library, needed to distinguish stacks
* that have the same display value but are considered distinct by
* DTrace; copied so that later modifying the given array will not
* affect this {@code KernelStackRecord}
* @throws NullPointerException if the given array of raw bytes is
* {@code null} or if any element of the {@code frames} array is
* {@code null}
*/
public
KernelStackRecord(StackFrame[] frames, byte[] rawBytes)
{
if (frames != null) {
stackFrames = (StackFrame[])frames.clone();
}
if (rawBytes != null) {
rawStackData = (byte[])rawBytes.clone();
}
validate();
}
private final void
validate()
{
if (rawStackData == null) {
throw new NullPointerException("raw stack data is null");
}
// stackFrames may be null; if non-null, cannot contain null
// elements
if (stackFrames != null) {
for (StackFrame f : stackFrames) {
if (f == null) {
throw new NullPointerException("stack frame is null");
}
}
}
}
public StackFrame[]
getStackFrames()
{
if (stackFrames == null) {
return EMPTY_FRAMES;
}
return (StackFrame[])stackFrames.clone();
}
/**
* Called by native code and by UserStackRecord in its
* setStackFrames() method.
*/
void
setStackFrames(StackFrame[] frames)
{
// No need for defensive copy; native code will not modify input
// frames.
stackFrames = frames;
validate();
}
/**
* Gets the native DTrace representation of this record's stack as
* an array of raw bytes. The raw bytes are used in {@link
* #equals(Object o) equals()} and {@link
* #compareTo(KernelStackRecord r) compareTo()} to test equality and
* to determine the natural ordering of kernel stack records.
*
* @return the native DTrace library's internal representation of
* this record's stack as a non-null array of bytes
*/
public byte[]
getRawStackData()
{
return (byte[])rawStackData.clone();
}
/**
* Gets the raw bytes used to represent this record's stack value in
* the native DTrace library. To get a human-readable
* representation, call {@link #toString()}.
*
* @return {@link #getRawStackData()}
*/
public Object
getValue()
{
return (byte[])rawStackData.clone();
}
public List <StackFrame>
asList()
{
if (stackFrames == null) {
return Collections. <StackFrame> emptyList();
}
return Collections.unmodifiableList(Arrays.asList(stackFrames));
}
/**
* Compares the specified object with this {@code KernelStackRecord}
* for equality. Returns {@code true} if and only if the specified
* object is also a {@code KernelStackRecord} and both records have
* the same raw stack data.
* <p>
* This implementation first checks if the specified object is this
* {@code KernelStackRecord}. If so, it returns {@code true}.
*
* @return {@code true} if and only if the specified object is also
* a {@code KernelStackRecord} and both records have the same raw
* stack data
*/
@Override
public boolean
equals(Object o)
{
if (o == this) {
return true;
}
if (o instanceof KernelStackRecord) {
KernelStackRecord r = (KernelStackRecord)o;
return Arrays.equals(rawStackData, r.rawStackData);
}
return false;
}
/**
* Overridden to ensure that equal instances have equal hash codes.
*/
@Override
public int
hashCode()
{
return Arrays.hashCode(rawStackData);
}
/**
* Compares this record with the given {@code KernelStackRecord}.
* Compares the first unequal pair of bytes at the same index in
* each record's raw stack data, or if all corresponding bytes are
* equal, compares the length of each record's array of raw stack
* data. Corresponding bytes are compared as unsigned values. The
* {@code compareTo()} method is compatible with {@link
* #equals(Object o) equals()}.
* <p>
* This implementation first checks if the specified record is this
* {@code KernelStackRecord}. If so, it returns {@code 0}.
*
* @return -1, 0, or 1 as this record's raw stack data is less than,
* equal to, or greater than the given record's raw stack data.
*/
public int
compareTo(KernelStackRecord r)
{
if (r == this) {
return 0;
}
return ProbeData.compareByteArrays(rawStackData, r.rawStackData);
}
private void
readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();
// Make a defensive copy of stack frames and raw bytes
if (stackFrames != null) {
stackFrames = (StackFrame[])stackFrames.clone();
}
if (rawStackData != null) {
rawStackData = (byte[])rawStackData.clone();
}
// check class invariants
try {
validate();
} catch (Exception e) {
InvalidObjectException x = new InvalidObjectException(
e.getMessage());
x.initCause(e);
throw x;
}
}
/**
* Gets a multi-line string representation of a stack with one frame
* per line. Each line is of the format {@code
* module`function+offset}, where {@code module} and {@code
* function} are symbol names and offset is a hex integer preceded
* by {@code 0x}. For example: {@code genunix`open+0x19}. The
* offset (and the preceding '+' sign) are omitted if offset is
* zero. If function name lookup fails, the raw pointer value is
* used instead. In that case, the module name (and the `
* delimiter) may or may not be present, depending on whether or not
* module lookup also fails, and a raw pointer value appears in
* place of {@code function+offset} as a hex value preceded by
* {@code 0x}. The format just described, not including surrounding
* whitespce, is defined in the native DTrace library and is as
* stable as that library definition. Each line is indented by an
* equal (unspecified) number of spaces.
* <p>
* If human-readable stack frames are not available (see {@link
* #getStackFrames()}), a table represenation of {@link
* #getRawStackData()} is returned instead. The table displays 16
* bytes per row in unsigned hex followed by the ASCII character
* representations of those bytes. Each unprintable character is
* represented by a period (.).
*/
@Override
public String
toString()
{
StackFrame[] frames = getStackFrames();
if (frames.length == 0) {
return ScalarRecord.rawBytesString(rawStackData);
}
StringBuilder buf = new StringBuilder();
buf.append('\n');
for (StackFrame f : frames) {
for (int i = 0; i < STACK_INDENT; ++i) {
buf.append(' ');
}
buf.append(f);
buf.append('\n');
}
return buf.toString();
}
}