This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] DWARF-2 .debug_line reader


Hi,

This is a follow-up to my previous post about reading the ELF
.debug_line section in GCJ-compiled libraries to determine the file
name and line number of debug stack frames. It improves the
performance greatly, by mapping any needed .debug_line sections into
memory, and cacheing ranges of PC addresses to a compilation unit
inside a debug section. I have also rewritten the bits I got from
elsewhere.

This patch doesn't modify the VMThrowable or NameFinder classes to use
this, since the DWARF-2 reader only handles file names and line
numbers, not classes and method names, which the existing
implementation needs. Hopefully, this will fit better into future
revisions of the throwable/stack trace implementation.

The look-up appears to be correct, for the most part. For some reason,
the line numbers I get for FirstThread are incorrect, but I don't know
why yet.

2004-07-24  Casey Marshall <csm@gnu.org>

	* configure.in: look for link.h.
	* Makefile.am: add new files.
	* Makefile.in: regenerated.
	* gnu/gcj/runtime/AddressMap.java,
	* gnu/gcj/runtime/AddressRange.java,
	* gnu/gcj/runtime/DebugInfo.java,
	* gnu/gcj/runtime/Dwarf2NameFinder.java,
	* gnu/gcj/runtime/natAddressRange.cc,
	* gnu/gcj/runtime/natDwarf2NameFinder.cc: new files.

-- 
Casey Marshall || csm@gnu.org
Index: Makefile.am
===================================================================
RCS file: /cvsroot/gcc/gcc/libjava/Makefile.am,v
retrieving revision 1.396
diff -u -r1.396 Makefile.am
--- Makefile.am	24 Jul 2004 16:43:44 -0000	1.396
+++ Makefile.am	25 Jul 2004 04:06:22 -0000
@@ -2277,6 +2277,10 @@
 gnu/gcj/io/DefaultMimeTypes.java \
 gnu/gcj/io/MimeTypes.java \
 gnu/gcj/io/SimpleSHSStream.java	\
+gnu/gcj/runtime/AddressMap.java \
+gnu/gcj/runtime/AddressRange.java \
+gnu/gcj/runtime/DebugInfo.java \
+gnu/gcj/runtime/Dwarf2NameFinder.java \
 gnu/gcj/runtime/FileDeleter.java \
 gnu/gcj/runtime/FinalizerThread.java \
 gnu/gcj/runtime/JNIWeakRef.java \
@@ -2953,6 +2957,8 @@
 gnu/gcj/convert/natOutput_SJIS.cc \
 gnu/gcj/io/natSimpleSHSStream.cc \
 gnu/gcj/io/shs.cc \
+gnu/gcj/runtime/natAddressRange.cc \
+gnu/gcj/runtime/natDwarf2NameFinder.cc \
 gnu/gcj/runtime/natFinalizerThread.cc \
 gnu/gcj/runtime/natNameFinder.cc \
 gnu/gcj/runtime/natSharedLibLoader.cc \
Index: Makefile.in
===================================================================
RCS file: /cvsroot/gcc/gcc/libjava/Makefile.in,v
retrieving revision 1.421
diff -u -r1.421 Makefile.in
--- Makefile.in	24 Jul 2004 16:43:45 -0000	1.421
+++ Makefile.in	25 Jul 2004 04:06:27 -0000
@@ -1960,6 +1960,10 @@
 gnu/gcj/io/DefaultMimeTypes.java \
 gnu/gcj/io/MimeTypes.java \
 gnu/gcj/io/SimpleSHSStream.java	\
+gnu/gcj/runtime/AddressMap.java \
+gnu/gcj/runtime/AddressRange.java \
+gnu/gcj/runtime/DebugInfo.java \
+gnu/gcj/runtime/Dwarf2NameFinder.java \
 gnu/gcj/runtime/FileDeleter.java \
 gnu/gcj/runtime/FinalizerThread.java \
 gnu/gcj/runtime/JNIWeakRef.java \
@@ -2635,6 +2639,8 @@
 gnu/gcj/convert/natOutput_SJIS.cc \
 gnu/gcj/io/natSimpleSHSStream.cc \
 gnu/gcj/io/shs.cc \
+gnu/gcj/runtime/natAddressRange.cc \
+gnu/gcj/runtime/natDwarf2NameFinder.cc \
 gnu/gcj/runtime/natFinalizerThread.cc \
 gnu/gcj/runtime/natNameFinder.cc \
 gnu/gcj/runtime/natSharedLibLoader.cc \
@@ -2818,10 +2824,12 @@
 gnu/gcj/convert/natIconv.lo gnu/gcj/convert/natInput_EUCJIS.lo \
 gnu/gcj/convert/natInput_SJIS.lo gnu/gcj/convert/natOutput_EUCJIS.lo \
 gnu/gcj/convert/natOutput_SJIS.lo gnu/gcj/io/natSimpleSHSStream.lo \
-gnu/gcj/io/shs.lo gnu/gcj/runtime/natFinalizerThread.lo \
-gnu/gcj/runtime/natNameFinder.lo gnu/gcj/runtime/natSharedLibLoader.lo \
-gnu/gcj/runtime/natStackTrace.lo gnu/gcj/runtime/natStringBuffer.lo \
-gnu/gcj/runtime/natVMClassLoader.lo gnu/java/lang/natMainThread.lo \
+gnu/gcj/io/shs.lo gnu/gcj/runtime/natAddressRange.lo \
+gnu/gcj/runtime/natDwarf2NameFinder.lo \
+gnu/gcj/runtime/natFinalizerThread.lo gnu/gcj/runtime/natNameFinder.lo \
+gnu/gcj/runtime/natSharedLibLoader.lo gnu/gcj/runtime/natStackTrace.lo \
+gnu/gcj/runtime/natStringBuffer.lo gnu/gcj/runtime/natVMClassLoader.lo \
+gnu/java/lang/natMainThread.lo \
 gnu/java/net/natPlainDatagramSocketImpl.lo \
 gnu/java/net/natPlainSocketImpl.lo \
 gnu/java/net/protocol/core/natCoreInputStream.lo \
@@ -3092,7 +3100,7 @@
 
 DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
 
-TAR = gtar
+TAR = tar
 GZIP_ENV = --best
 DIST_SUBDIRS =  @DIRLTDL@ testsuite gcj include @DIRLTDL@ gcj include
 DEP_FILES =  .deps/$(srcdir)/$(CONVERT_DIR)/gen-from-JIS.P \
@@ -3140,7 +3148,10 @@
 .deps/gnu/gcj/io/DefaultMimeTypes.P .deps/gnu/gcj/io/MimeTypes.P \
 .deps/gnu/gcj/io/SimpleSHSStream.P \
 .deps/gnu/gcj/io/natSimpleSHSStream.P .deps/gnu/gcj/io/shs.P \
-.deps/gnu/gcj/natCore.P .deps/gnu/gcj/runtime/FileDeleter.P \
+.deps/gnu/gcj/natCore.P .deps/gnu/gcj/runtime/AddressMap.P \
+.deps/gnu/gcj/runtime/AddressRange.P .deps/gnu/gcj/runtime/DebugInfo.P \
+.deps/gnu/gcj/runtime/Dwarf2NameFinder.P \
+.deps/gnu/gcj/runtime/FileDeleter.P \
 .deps/gnu/gcj/runtime/FinalizerThread.P \
 .deps/gnu/gcj/runtime/JNIWeakRef.P .deps/gnu/gcj/runtime/MethodRef.P \
 .deps/gnu/gcj/runtime/NameFinder.P \
@@ -3148,6 +3159,8 @@
 .deps/gnu/gcj/runtime/SharedLibLoader.P \
 .deps/gnu/gcj/runtime/StackTrace.P .deps/gnu/gcj/runtime/StringBuffer.P \
 .deps/gnu/gcj/runtime/VMClassLoader.P \
+.deps/gnu/gcj/runtime/natAddressRange.P \
+.deps/gnu/gcj/runtime/natDwarf2NameFinder.P \
 .deps/gnu/gcj/runtime/natFinalizerThread.P \
 .deps/gnu/gcj/runtime/natNameFinder.P \
 .deps/gnu/gcj/runtime/natSharedLibLoader.P \
Index: configure.in
===================================================================
RCS file: /cvsroot/gcc/gcc/libjava/configure.in,v
retrieving revision 1.196
diff -u -r1.196 configure.in
--- configure.in	23 Jul 2004 23:34:16 -0000	1.196
+++ configure.in	25 Jul 2004 04:06:27 -0000
@@ -782,7 +782,7 @@
    AC_CHECK_FUNCS(inet_aton inet_addr, break)
    AC_CHECK_FUNCS(inet_pton uname inet_ntoa)
    AC_CHECK_FUNCS(fork execvp pipe sigaction ftruncate)
-   AC_CHECK_HEADERS(execinfo.h unistd.h dlfcn.h) 
+   AC_CHECK_HEADERS(execinfo.h unistd.h dlfcn.h link.h) 
    AC_CHECK_FUNC(backtrace, [
      case "$host" in
        ia64-*-linux*)
Index: gnu/gcj/runtime/AddressMap.java
===================================================================
RCS file: gnu/gcj/runtime/AddressMap.java
diff -N gnu/gcj/runtime/AddressMap.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/gcj/runtime/AddressMap.java	25 Jul 2004 04:06:27 -0000
@@ -0,0 +1,343 @@
+/* AddressMap.java -- maps address ranges to objects.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+// Written by Casey Marshall <csm@gnu.org>.
+
+
+package gnu.gcj.runtime;
+
+import gnu.gcj.RawData;
+
+import java.util.AbstractMap;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+// FIXME we really should be able to do a binary search in this map,
+// instead of a linear one.
+
+/**
+ * An implementation of {@link Map} between ranges of addresses and
+ * objects.
+ *
+ * <p>That is, if we have a map that looks like this:
+<pre>
+  { (1,3) => "foo", (5,9) => "bar", (10,10) => "baz" }
+</pre>
+ *
+ * <p>Then requesting the key '2' would return "foo", requesting '5'
+ * would return "bar", but requesting '4' would return null.
+ *
+ * <p>Mapping a new range will update any overlapping ranges, increasing
+ * them. That is, putting '(10,12) => "quux"' into the above map would
+ * change it to look like this:
+ *
+<pre>
+  { (1,3) => "foo", (5,9) => "bar", (10,12) => "quux" }
+</pre>
+ *
+ * <p>Keys must be instances of {@link AddressRange}. Values can be of
+ * any type, but they are only compared by identity, since the typical
+ * mapped value will be a {@link RawData} pseudo-object.
+ */
+public class AddressMap extends AbstractMap implements SortedMap
+{
+
+  // Fields.
+  // -------------------------------------------------------------------------
+
+  private final SortedSet entries;
+
+  // Constructors.
+  // -------------------------------------------------------------------------
+
+  public AddressMap()
+  {
+    entries = new TreeSet();
+  }
+
+  public AddressMap (Map m)
+  {
+    this();
+    putAll (m);
+  }
+
+  private AddressMap (SortedSet subset)
+  {
+    entries = subset;
+  }
+
+  // Instance methods.
+  // -------------------------------------------------------------------------
+
+  public Set entrySet()
+  {
+    return entries;
+  }
+
+  public Object put (Object key, Object value)
+  {
+    AddressRange range = (AddressRange) key;
+    for (Iterator it = entries.iterator(); it.hasNext(); )
+      {
+        AddressEntry e = (AddressEntry) it.next();
+        if (e.getRange().equals (range))
+          return e.setValue (value);
+        if (e.getRange().compareTo (range) == 0)
+          {
+            RawData low = e.getRange().getLow();
+            if (AddressRange.compare (low, range.getLow()) > 0)
+              low = range.getLow();
+            RawData high = e.getRange().getHigh();
+            if (AddressRange.compare (high, range.getHigh()) < 0)
+              high = range.getHigh();
+            entries.remove (e);
+            entries.add (new AddressEntry (new AddressRange (low, high), value));
+            return e.getValue();
+          }
+      }
+    entries.add (new AddressEntry (range, value));
+    return null;
+  }
+
+  public Object put (RawData addr, Object value)
+  {
+    AddressEntry e1 = null;
+    AddressEntry e2 = null;
+    for (Iterator it = entries.iterator(); it.hasNext(); )
+      {
+        e1 = (AddressEntry) it.next();
+        if (e1.getRange().contains (addr))
+          return e1.setValue (value);
+        if (e1.compareTo (addr) > 0)
+          {
+            e1 = null;
+            break;
+          }
+        e2 = e1;
+      }
+
+    if (e2 == null)
+      entries.add (new AddressEntry (new AddressRange (addr), value));
+    entries.remove (e2);
+    entries.add (new AddressEntry
+                 (new AddressRange (e2.getRange().getLow(), addr), value));
+    return e2.getValue();
+  }
+
+  public Object get (Object key)
+  {
+    AddressEntry e = null;
+    e = getEntry ((AddressRange) key);
+    if (e != null)
+      return e.getValue();
+    return null;
+  }
+
+  public Object get (RawData addr)
+  {
+    AddressEntry e = getEntry (addr);
+    if (e != null)
+      return e.getValue();
+    return null;
+  }
+
+  public Object remove (Object key)
+  {
+    AddressEntry e = null;
+    e = getEntry ((AddressRange) key);
+    if (e != null)
+      {
+        entries.remove (e);
+        return e.getValue();
+      }
+    return null;
+  }
+
+  public Object remove (RawData addr)
+  {
+    AddressEntry e = getEntry (addr);
+    if (e != null)
+      {
+        entries.remove (e);
+        return e.getValue();
+      }
+    return null;
+  }
+
+  public boolean containsValue (Object o)
+  {
+    for (Iterator it = values().iterator(); it.hasNext(); )
+      if (it.next() == o)
+        return true;
+    return false;
+  }
+
+  // SortedMap methods.
+
+  public Comparator comparator()
+  {
+    return null;
+  }
+
+  public Object firstKey()
+  {
+    return ((AddressEntry) entries.first()).getKey();
+  }
+
+  public Object lastKey()
+  {
+    return ((AddressEntry) entries.last()).getKey();
+  }
+
+  public SortedMap headMap (Object fromKey)
+  {
+    AddressEntry e = null;
+    e = (AddressEntry) getEntry ((AddressRange) fromKey);
+    if (e != null)
+      return new AddressMap (entries.headSet (e));
+    throw new IllegalArgumentException();
+  }
+
+  public SortedMap tailMap (Object toKey)
+  {
+    AddressEntry e = null;
+    e = (AddressEntry) getEntry ((AddressRange) toKey);
+    if (e != null)
+      return new AddressMap (entries.tailSet (e));
+    throw new IllegalArgumentException();
+  }
+
+  public SortedMap subMap (Object fromKey, Object toKey)
+  {
+    AddressEntry e1 = null;
+    AddressEntry e2 = null;
+    e1 = (AddressEntry) getEntry ((AddressRange) fromKey);
+    e2 = (AddressEntry) getEntry ((AddressRange) toKey);
+    if (e1 != null && e2 != null)
+      return new AddressMap (entries.subSet (e1, e2));
+    throw new IllegalArgumentException();
+  }
+
+  public String toString()
+  {
+    StringBuffer str = new StringBuffer (AddressMap.class.getName());
+    str.append (" [ ");
+    for (Iterator it = entries.iterator(); it.hasNext(); )
+      {
+        str.append (it.next());
+        if (it.hasNext())
+          str.append (", ");
+      }
+    str.append (" ]");
+    return str.toString();
+  }
+
+  // Own methods.
+  // -------------------------------------------------------------------------
+
+  private AddressEntry getEntry (RawData addr)
+  {
+    for (Iterator it = entries.iterator(); it.hasNext(); )
+      {
+        AddressEntry e = (AddressEntry) it.next();
+        if (e.getRange().contains (addr))
+          return e;
+      }
+    return null;
+  }
+
+  private AddressEntry getEntry (AddressRange range)
+  {
+    for (Iterator it = entries.iterator(); it.hasNext(); )
+      {
+        AddressEntry e = (AddressEntry) it.next();
+        if (e.getRange().compareTo (range) == 0)
+          return e;
+      }
+    return null;
+  }
+
+  // Inner class.
+  // -------------------------------------------------------------------------
+
+  private class AddressEntry implements Comparable, Map.Entry
+  {
+
+    // Fields.
+    // -----------------------------------------------------------------------
+
+    private final AddressRange range;
+    private Object value;
+
+    // Constructor.
+    // -----------------------------------------------------------------------
+
+    private AddressEntry (final AddressRange range, final Object value)
+    {
+      this.range = range;
+      this.value = value;
+    }
+
+    // Instance methods.
+    // -----------------------------------------------------------------------
+
+    public boolean equals (Object o)
+    {
+      if (!(o instanceof AddressEntry))
+        return false;
+      if (!range.equals (o))
+        return false;
+      return (o == value);
+    }
+
+    public Object getKey()
+    {
+      return range;
+    }
+
+    public AddressRange getRange()
+    {
+      return range;
+    }
+
+    public Object getValue()
+    {
+      return value;
+    }
+
+    public int hashCode()
+    {
+      return range.hashCode() + (value != null ? value.hashCode() : 0);
+    }
+
+    public Object setValue (Object value)
+    {
+      Object t = this.value;
+      this.value = value;
+      return t;
+    }
+
+    public int compareTo (Object o)
+    {
+      return range.compareTo (((AddressEntry) o).range);
+    }
+
+    public String toString()
+    {
+      return range + "=>0x" +
+        Integer.toHexString(System.identityHashCode(value));
+    }
+  }
+}
Index: gnu/gcj/runtime/AddressRange.java
===================================================================
RCS file: gnu/gcj/runtime/AddressRange.java
diff -N gnu/gcj/runtime/AddressRange.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/gcj/runtime/AddressRange.java	25 Jul 2004 04:06:27 -0000
@@ -0,0 +1,101 @@
+/* AddressRange.java -- a range of addresses.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+// Written by Casey Marshall <csm@gnu.org>.
+
+
+package gnu.gcj.runtime;
+
+import gnu.gcj.RawData;
+
+/**
+ * A range of machine addresses. Objects of this type are used as keys
+ * in an {@link AddressMap}, which is used to map ranges of addresses
+ * to objects. This is an inclusive range, so an address is considered
+ * within a range if it is greater than or equal to the low address,
+ * and less than or equal to the high address.
+ */
+public final class AddressRange implements Comparable
+{
+
+  private final RawData low, high;
+
+  public AddressRange (RawData low, RawData high)
+  {
+    this.low  = low;
+    this.high = high;
+  }
+
+  public AddressRange (RawData addr)
+  {
+    this (addr, addr);
+  }
+
+  public RawData getLow()
+  {
+    return low;
+  }
+
+  public RawData getHigh()
+  {
+    return high;
+  }
+
+  public boolean contains (AddressRange range)
+  {
+    return compare (low, range.low) <= 0 && compare (high, range.high) >= 0;
+  }
+
+  public boolean contains (RawData addr)
+  {
+    return compare (low, addr) <= 0 && compare (high, addr) >= 0;
+  }
+
+  public boolean equals (Object o)
+  {
+    if (!(o instanceof AddressRange))
+      return false;
+    AddressRange that = (AddressRange) o;
+    return compare (low, that.low) == 0 && compare (high, that.high) == 0;
+  }
+
+  public int hashCode()
+  {
+    return hashCode (low) ^ hashCode (high);
+  }
+
+  /**
+   * Compares this address range to another. Ranges being compared should
+   * be disjoint; otherwise the behavior of this method will not be
+   * consistent with equals.
+   *
+   * @param o The object to compare.
+   * @return An integer greater than, equal to, or less than one.
+   * @throws ClassCastException If the argument is not an instance of
+   *  this class.
+   */
+  public int compareTo (Object o)
+  {
+    AddressRange that = (AddressRange) o;
+    if (compare (low, that.high) > 0)
+      return 1;
+    if (compare (high, that.low) < 0)
+      return -1;
+    return 0;
+  }
+
+  public String toString()
+  {
+    return ("(0x" + Integer.toHexString (hashCode (low)) +
+            ",0x" + Integer.toHexString (hashCode (high)) + ")");
+  }
+
+  static native int compare (RawData addr1, RawData addr2);
+  static native int hashCode (RawData addr);
+}
Index: gnu/gcj/runtime/DebugInfo.java
===================================================================
RCS file: gnu/gcj/runtime/DebugInfo.java
diff -N gnu/gcj/runtime/DebugInfo.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/gcj/runtime/DebugInfo.java	25 Jul 2004 04:06:27 -0000
@@ -0,0 +1,101 @@
+/* DebugInfo.java -- source-level debugging info.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+
+   This file is a part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+
+package gnu.gcj.runtime;
+
+/**
+ * A simple immutable class representing source-level debugging information,
+ * such as the source file, line, and column number of a statement.
+ *
+ * <p>The info contained in this class may not be complete. If for some
+ * frames a value cannot be determined, they will either be <code>null</code>
+ * or -1.
+ */
+public class DebugInfo
+{
+
+  // Fields.
+  // -------------------------------------------------------------------------
+
+  private final String filename;
+  private final int line;
+  private final int column;
+  private final boolean isNative;
+
+  // Constructors.
+  // -------------------------------------------------------------------------
+
+  public DebugInfo (String filename, int line)
+  {
+    this (filename, line, -1, false);
+  }
+
+  public DebugInfo (String filename, int line, boolean isNative)
+  {
+    this (filename, line, -1, isNative);
+  }
+
+  public DebugInfo (String filename, int line, int column, boolean isNative)
+  {
+    this.filename = filename;
+    this.line = line;
+    this.column = column;
+    this.isNative = isNative;
+  }
+
+  // Instance methods.
+  // -------------------------------------------------------------------------
+
+  public String getFileName()
+  {
+    return filename;
+  }
+
+  public int getLineNumber()
+  {
+    return line;
+  }
+
+  public int getColumnNumber()
+  {
+    return column;
+  }
+
+  public boolean isNative()
+  {
+    return isNative;
+  }
+
+  public String toString()
+  {
+    StringBuffer str = new StringBuffer();
+    if (filename != null)
+      str.append (filename);
+    else
+      str.append ("<<no file>>");
+    if (line >= 0)
+      str.append (":").append (line);
+    if (column >= 0)
+      str.append (":").append (column);
+    return str.toString();
+  }
+
+  public boolean equals (Object o)
+  {
+    if (!(o instanceof DebugInfo))
+      return false;
+    DebugInfo that = (DebugInfo) o;
+    if (filename == null)
+      if (that.filename != null)
+        return false;
+    return ((filename == null || filename.equals (that.filename))
+            && line == that.line && column == that.column);
+  }
+}
Index: gnu/gcj/runtime/Dwarf2NameFinder.java
===================================================================
RCS file: gnu/gcj/runtime/Dwarf2NameFinder.java
diff -N gnu/gcj/runtime/Dwarf2NameFinder.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/gcj/runtime/Dwarf2NameFinder.java	25 Jul 2004 04:06:27 -0000
@@ -0,0 +1,46 @@
+/* Dwarf2NameFinder.java -- reads debug info from ELF .debug_line sections.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+
+package gnu.gcj.runtime;
+
+import gnu.gcj.RawData;
+import java.util.HashMap;
+import java.util.Map;
+
+final class Dwarf2NameFinder
+{
+
+  // Fields.
+  // -------------------------------------------------------------------------
+
+  private final AddressMap addressCache;
+  private final Map libraries;
+
+  // Constructor.
+  // -------------------------------------------------------------------------
+
+  Dwarf2NameFinder()
+  {
+    addressCache = new AddressMap();
+    libraries = new HashMap();
+  }
+
+  // Instance methods.
+  // -------------------------------------------------------------------------
+
+  public native synchronized DebugInfo lookup (RawData addr, int n);
+
+  protected native void finalize();
+
+  public String toString()
+  {
+    return addressCache.toString();
+  }
+}
Index: gnu/gcj/runtime/natAddressRange.cc
===================================================================
RCS file: gnu/gcj/runtime/natAddressRange.cc
diff -N gnu/gcj/runtime/natAddressRange.cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/gcj/runtime/natAddressRange.cc	25 Jul 2004 04:06:27 -0000
@@ -0,0 +1,32 @@
+/* natAddressRange.java -- a range of addresses.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+// Written by Casey Marshall <csm@gnu.org>.
+
+
+#include <gcj/cni.h>
+#include <gnu/gcj/runtime/AddressRange.h>
+#include <gnu/gcj/RawData.h>
+
+jint
+gnu::gcj::runtime::AddressRange::compare (gnu::gcj::RawData *a1,
+					  gnu::gcj::RawData *a2)
+{
+  if (a1 < a2)
+    return -1;
+  if (a1 > a2)
+    return 1;
+  return 0;
+}
+
+jint
+gnu::gcj::runtime::AddressRange::hashCode (gnu::gcj::RawData *addr)
+{
+  return (jint) addr;
+}
Index: gnu/gcj/runtime/natDwarf2NameFinder.cc
===================================================================
RCS file: gnu/gcj/runtime/natDwarf2NameFinder.cc
diff -N gnu/gcj/runtime/natDwarf2NameFinder.cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/gcj/runtime/natDwarf2NameFinder.cc	25 Jul 2004 04:06:27 -0000
@@ -0,0 +1,676 @@
+/* natDwarf2NameFinder.cc -- reads debug info from ELF .debug_line sections.
+   Copyright (C) 2004  Free Software Foundation, Inc.
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+// Written by Casey Marshall <csm@gnu.org>
+
+
+#include <config.h>
+
+#include <jvm.h>
+#include <gcj/cni.h>
+#include <gnu/gcj/runtime/Dwarf2NameFinder.h>
+
+#include <gnu/gcj/RawData.h>
+#include <gnu/gcj/runtime/AddressMap.h>
+#include <gnu/gcj/runtime/AddressRange.h>
+#include <gnu/gcj/runtime/DebugInfo.h>
+#include <java/lang/String.h>
+#include <java/util/Collection.h>
+#include <java/util/Iterator.h>
+#include <java/util/Map.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/fcntl.h>
+#include <sys/mman.h>
+
+#ifdef HAVE_LINK_H
+#include <link.h>
+
+#pragma weak _DYNAMIC
+
+#define MAX_FNAMES 128
+
+// #define TRACE_DW2_NAME_FINDER
+
+#ifdef TRACE_DW2_NAME_FINDER
+#define TRACE(fmt, args...) fprintf (stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__ , ##args)
+#else
+#define TRACE(fmt, args...)
+#endif
+
+
+typedef uint32_t uword_t;
+typedef int32_t sword_t;
+typedef uint16_t uhalf_t;
+typedef uint8_t ubyte_t;
+typedef int8_t sbyte_t;
+
+/* Line number opcodes (see gcc/dwarf2.h).  */
+enum dwarf_line_number_ops
+{
+  DW_LNS_extended_op = 0,
+  DW_LNS_copy = 1,
+  DW_LNS_advance_pc = 2,
+  DW_LNS_advance_line = 3,
+  DW_LNS_set_file = 4,
+  DW_LNS_set_column = 5,
+  DW_LNS_negate_stmt = 6,
+  DW_LNS_set_basic_block = 7,
+  DW_LNS_const_add_pc = 8,
+  DW_LNS_fixed_advance_pc = 9,
+};
+
+/* Line number extended opcodes.  */
+enum dwarf_line_number_x_ops
+{
+  DW_LNE_end_sequence = 1,
+  DW_LNE_set_address = 2,
+  DW_LNE_define_file = 3
+};
+
+union dw2_datum
+{
+  uword_t uword;
+  sword_t sword;
+  uhalf_t uhalf;
+  ubyte_t ubyte;
+  sbyte_t sbyte;
+};
+
+typedef struct dw2_debug_line
+{
+  uword_t total_length;
+  uhalf_t version;
+  uword_t prologue_length;
+  ubyte_t minimum_instruction_length;
+  ubyte_t default_is_stmt;
+  sbyte_t line_base;
+  ubyte_t line_range;
+  ubyte_t opcode_base;
+  ubyte_t *standard_opcode_lengths;
+} dw2_debug_line_t;
+
+/*
+ * This structure is used in a mapping between library file names and
+ * the memory-mapped debug_line section.
+ */
+typedef struct dw2_finder_state
+{
+  // The file descriptor of the opened binary.
+  int fd;
+
+  // The size of the mapped debug_line section.
+  size_t debug_line_sz;
+
+  // The offset after debug_line where the section really begins
+  // (ie page size adjustment).
+  off_t debug_line_off;
+
+  // The address where the debug_line section in mapped.
+  ElfW(Addr) debug_line;
+
+  // The base load address of this link unit.
+  ElfW(Addr) base;
+
+  // The last possible address in this link unit, or NULL if this
+  // information is not available.
+  ElfW(Addr) limit;
+} dw2_finder_state_t;
+
+/*
+ * This structure is used in a nearest-fit map from PC addresses.
+ */
+typedef struct dw2_finder_cache_entry
+{
+
+  // A pointer to the finder state.
+  dw2_finder_state_t *state;
+
+  // The offset into the mapped debug_line section, where the debug
+  // info for this entry (is believed to) be.
+  off_t offset;
+} dw2_finder_cache_entry_t;
+
+static uword_t
+read_uword (ElfW(Addr) *p)
+{
+  union dw2_datum *d = *((union dw2_datum **) p);
+  *p += 4;
+  return d->uword;
+}
+
+// We don't read signed words below, but if we did, this is how
+// we would do it.
+//
+// static sword_t
+// read_sword (ElfW(Addr) *p)
+// {
+//   union dw2_datum *d = *((union dw2_datum **) p);
+//   *p += 4;
+//   return d->sword;
+// }
+
+static uhalf_t
+read_uhalf (ElfW(Addr) *p)
+{
+  union dw2_datum *d = *((union dw2_datum **) p);
+  *p += 2;
+  return d->uhalf;
+}
+
+static ubyte_t
+read_ubyte (ElfW(Addr) *p)
+{
+  union dw2_datum *d = *((union dw2_datum **) p);
+  *p += 1;
+  return d->ubyte;
+}
+
+static sbyte_t
+read_sbyte (ElfW(Addr) *p)
+{
+  union dw2_datum *d = *((union dw2_datum **) p);
+  *p += 1;
+  return d->sbyte;
+}
+
+static unsigned long
+read_uleb128 (ElfW(Addr) *p)
+{
+  unsigned long result = 0;
+  int shift = 0;
+  ubyte_t byte;
+
+  while (true)
+    {
+      byte = read_ubyte (p);
+      result |= (byte & 0x7F) << shift;
+      if (!(byte & 0x80))
+	break;
+      shift += 7;
+    }
+
+  return result;
+}
+
+static long
+read_sleb128 (ElfW(Addr) *p)
+{
+  long result = 0;
+  int shift = 0;
+  ubyte_t byte;
+  int size = sizeof (long) << 3;
+
+  while (true)
+    {
+      byte = read_ubyte (p);
+      result |= (byte & 0x7F) << shift;
+      shift += 7;
+      if (!(byte & 0x80))
+	break;
+    }
+
+  if ((shift < size) && (byte & 0x40))
+    result |= -(1 << shift);
+
+  return result;
+}
+
+static void
+close_state (dw2_finder_state_t *state)
+{
+  if (state->debug_line != 0)
+    {
+      munmap ((void *) state->debug_line, state->debug_line_sz);
+      state->debug_line = 0;
+      state->debug_line_sz = 0;
+    }
+  if (state->fd > -1)
+    {
+      close (state->fd);
+      state->fd = -1;
+    }
+  _Jv_Free (state);
+}
+
+static dw2_finder_state_t *
+open_state (char *libname, java::util::Map *libraries)
+{
+  dw2_finder_state_t *state;
+  ElfW(Ehdr) ehdr;
+  ElfW(Shdr) shdr;
+  ElfW(Shdr) strtabhdr;
+  char buf[12];  // .debug_line\0
+  bool found_dl = false;
+  long pagesize = sysconf (_SC_PAGESIZE);
+
+  state = (dw2_finder_state_t *) _Jv_Malloc (sizeof (struct dw2_finder_state));
+  state->debug_line = 0;
+  state->debug_line_sz = 0;
+  state->fd = open (libname, O_RDONLY);
+  if (state->fd == -1)
+    {
+      TRACE("open %s failed: %s", libname, strerror (errno));
+      close_state ((dw2_finder_state_t *) state);
+      return NULL;
+    }
+
+  // Read the ELF header, and exit if it is not an ELF file.
+  read (state->fd, &ehdr, sizeof (ElfW(Ehdr)));
+  if (memcmp (ehdr.e_ident, ELFMAG, SELFMAG))
+    {
+      TRACE("%s: not an ELF file", libname);
+      close_state ((dw2_finder_state_t *) state);
+
+      // A null value means "no debug info available from here"
+      libraries->put (JvNewStringLatin1 (libname), NULL);
+      return NULL;
+    }
+
+  // Load the string table header.
+  lseek (state->fd, ehdr.e_shoff + (ehdr.e_shstrndx * sizeof (ElfW(Shdr))),
+	 SEEK_SET);
+  read (state->fd, &strtabhdr, sizeof (ElfW(Shdr)));
+
+  // Find the .debug_line section.
+  for (int i = 0; i < ehdr.e_shnum; i++)
+    {
+      lseek (state->fd, ehdr.e_shoff + (i * sizeof (ElfW(Shdr))), SEEK_SET);
+      read (state->fd, &shdr, sizeof (ElfW(Shdr)));
+      lseek (state->fd, strtabhdr.sh_offset + shdr.sh_name, SEEK_SET);
+      read (state->fd, buf, sizeof (buf));
+      if (strcmp (buf, ".debug_line") == 0)
+	{
+	  found_dl = true;
+	  break;
+	}
+    }
+
+  if (!found_dl)
+    {
+      TRACE("%s: no .debug_line section", libname);
+      close_state ((dw2_finder_state_t *) state);
+      libraries->put (JvNewStringLatin1 (libname), NULL);
+      return NULL;
+    }
+
+  // Map the debug_line section into memory.
+  state->debug_line_off = shdr.sh_offset & (pagesize - 1);
+  state->debug_line_sz = shdr.sh_size + state->debug_line_off;
+  void *ptr = mmap (NULL, state->debug_line_sz, PROT_READ,
+		    MAP_SHARED, state->fd,
+		    shdr.sh_offset & ~(pagesize - 1));
+  if (ptr == MAP_FAILED)
+    {
+      TRACE("mapping .debug_line failed off=%u sz=%u",
+	    state->debug_line_off, state->debug_line_sz);
+      close_state ((dw2_finder_state_t *) state);
+      return NULL;
+    }
+  state->debug_line = (ElfW(Addr)) ptr;
+
+  TRACE("mapped .debug_line for %s at %p", libname, ptr);
+
+  libraries->put (JvNewStringLatin1 (libname), (gnu::gcj::RawData *) state);
+  return state;
+}
+
+static gnu::gcj::runtime::DebugInfo *
+interpret_dw2 (dw2_finder_state_t *state, off_t offset, ElfW(Addr) addr,
+	       gnu::gcj::runtime::AddressMap *addressCache)
+{
+  ElfW(Addr) p = state->debug_line + state->debug_line_off + offset;
+  ElfW(Addr) end = state->debug_line + state->debug_line_sz;
+  ElfW(Addr) prologue_end;
+  ElfW(Addr) entry_end;
+  ElfW(Addr) base_address = 0;
+  ElfW(Addr) address = 0;
+  ElfW(Addr) target = addr - state->base;
+  dw2_debug_line_t header;
+  off_t this_offset;
+  unsigned long fileno = 1;
+  char *fnames[MAX_FNAMES];
+  unsigned int nfnames;
+  char *define_file;
+  int const_pc_add;
+  int line = 1;
+  int column = 0;
+
+  TRACE("looking up %p at %p (ends at %p)", target, p, end);
+
+  while (p < end)
+    {
+      this_offset = p - (state->debug_line + state->debug_line_off + offset);
+      TRACE("at section %p, this_offset=%u", p, this_offset);
+      header.total_length = read_uword (&p);
+      entry_end = p + header.total_length;
+      header.version = read_uhalf (&p);
+      header.prologue_length = read_uword (&p);
+      prologue_end = p + header.prologue_length;
+      header.minimum_instruction_length = read_ubyte (&p);
+      header.default_is_stmt = read_ubyte (&p);
+      header.line_base = read_sbyte (&p);
+      header.line_range = read_ubyte (&p);
+      header.opcode_base = read_ubyte (&p);
+      header.standard_opcode_lengths = (ubyte_t *) p;
+      p += 9;
+
+      TRACE("debug_line header { total_length=%u, version=%u, prologue_length=%u,"
+	    " minimum_instruction_length=%u, default_is_statement=%u line_base=%d,"
+	    " line_range=%u, opcode_base=%u }",
+	    header.total_length, header.version, header.prologue_length,
+	    header.minimum_instruction_length, header.default_is_stmt,
+	    header.line_base, header.line_range, header.opcode_base);
+
+      // We don't support anything but DWARF-2 here.
+      if (header.version != 2 || header.opcode_base != 10)
+	{
+	  TRACE("skipping this section; not DWARF-2 (version=%u opcode_base=%u",
+		header.version, header.opcode_base);
+	  p = entry_end;
+	  continue;
+	}
+
+      const_pc_add = 245 / header.line_range;
+
+      // Skip the directories.
+      while (strlen ((char *) p) > 0)
+	{
+	  TRACE("Directory: %s", (char *) p);
+	  p += strlen ((char *) p) + 1;
+	}
+      p++;
+
+      nfnames = 0;
+      while (p < prologue_end - 1)
+	{
+	  // Store the file names.
+	  if (nfnames < MAX_FNAMES)
+	    fnames[nfnames++] = (char *) p;
+	  TRACE("File name: %s", (char *) p);
+	  p += strlen ((char *) p) + 1;
+
+	  // Skip the attributes.
+	  unsigned long l1 = read_uleb128 (&p);
+	  unsigned long l2 = read_uleb128 (&p);
+	  unsigned long l3 = read_uleb128 (&p);
+	  TRACE("dir: %lu, time: %lu, len: %lu", l1, l2, l3);
+	  TRACE("p=%p prologue_end=%p", p, prologue_end);
+	  (void) l1; (void) l2; (void) l3;
+	}
+      p++;
+
+      address = 0;
+      base_address = 0;
+      define_file = NULL;
+      fileno = 1;
+      line = 1;
+      column = 0;
+
+      while (p < entry_end)
+	{
+	  ubyte_t opcode = read_ubyte (&p);
+	  if (opcode < header.opcode_base)
+	    {
+	      switch (opcode)
+		{
+		case DW_LNS_extended_op:
+		  {
+		    unsigned long insn_len = read_uleb128 (&p);
+		    opcode = read_ubyte (&p);
+		    TRACE("special opcode %u insn_len=%lu", opcode, insn_len);
+		    switch (opcode)
+		      {
+		      case DW_LNE_end_sequence:
+			TRACE("End of sequence");
+			if (base_address <= target && address >= target)
+			  goto found_debug_info;
+			break;
+
+		      case DW_LNE_set_address:
+			base_address = *((ElfW(Addr) *) p);
+			p += sizeof (ElfW(Addr));
+			address = base_address;
+			TRACE("Set address to %p", base_address);
+
+			// XXX this might not be correct, as there can be more
+			// than one of these instructions in a single compilation
+			// unit.
+			if (address > target)
+			  {
+			    TRACE("not in this unit base=%p target=%p", address, target);
+			    p = entry_end;
+			    continue;
+			  }
+			break;
+
+		      case DW_LNE_define_file:
+			{
+			  define_file = (char *) p;
+			  while (*((char *) p++));
+			  read_uleb128 (&p);
+			  read_uleb128 (&p);
+			  read_uleb128 (&p);
+			}
+			break;
+
+		      default:
+			TRACE("Unsupported extended opcode %d", opcode);
+			p += insn_len;
+			break;
+		      }
+		  }
+		  break;
+
+		case DW_LNS_copy:
+		  TRACE("Copy");
+		  if (base_address <= target && address >= target)
+		    goto found_debug_info;
+		  break;
+
+		case DW_LNS_advance_pc:
+		  {
+		    unsigned long amt = read_uleb128 (&p);
+		    address += amt * header.minimum_instruction_length;
+		    TRACE("Advance PC by %lu to %p", amt, address);
+		  }
+		  break;
+
+		case DW_LNS_advance_line:
+		  {
+		    long amt = read_sleb128 (&p);
+		    line += (int) amt;
+		    TRACE("Advance line by %ld to %d", amt, line);
+		  }
+		  break;
+
+		case DW_LNS_set_file:
+		  fileno = read_uleb128 (&p);
+		  TRACE("Set file to %d", fileno);
+		  break;
+
+		case DW_LNS_set_column:
+		  column = (int) read_uleb128 (&p);
+		  TRACE("Set column to %d", column);
+		  break;
+
+		case DW_LNS_negate_stmt:
+		  TRACE("Negate statement (ignored)");
+		  break;
+
+		case DW_LNS_set_basic_block:
+		  TRACE("Set basic block (ignored)");
+		  break;
+
+		case DW_LNS_const_add_pc:
+		  address += const_pc_add;
+		  TRACE("Advance PC by (constant) %d to %p", const_pc_add, address);
+		  break;
+
+		case DW_LNS_fixed_advance_pc:
+		  {
+		    address += read_uhalf (&p);
+		    TRACE("Advance PC by (fixed) %p", address);
+		  }
+		  break;
+		}
+	    }
+	  else
+	    {
+	      int adj = opcode - header.opcode_base;
+	      int addr_adv = adj / header.line_range;
+	      address += addr_adv;
+	      if (base_address <= target && address >= target)
+		goto found_debug_info;
+	      int line_adv = header.line_base + (adj % header.line_range);
+	      line += line_adv;
+	      TRACE("Special opcode %d advance line by %d to %d and addr by %d to %p",
+		    opcode, line_adv, line, addr_adv, address);
+	    }
+	}
+    }
+  TRACE("nothing found for %p", target);
+  return NULL;
+
+ found_debug_info:
+
+  TRACE("found fileno=%d line=%d col=%d for %p (base=%p, addr=%p",
+	fileno, line, column, target, base_address, address);
+  // Cache the compilation unit we found this address in.
+  gnu::gcj::runtime::AddressRange *range
+    = new gnu::gcj::runtime::AddressRange ((gnu::gcj::RawData *) base_address,
+					   (gnu::gcj::RawData *) address);
+  dw2_finder_cache_entry_t *entry
+    = (dw2_finder_cache_entry_t *) addressCache->get (range);
+  TRACE("cached entry %p", entry);
+  if (entry == NULL || entry->state != state || this_offset != offset)
+    {
+      entry = (dw2_finder_cache_entry *)
+	JvMalloc (sizeof (struct dw2_finder_cache_entry));
+      entry->state = state;
+      entry->offset = this_offset;
+    }
+
+  // AddressMap does some magic to combine overlapping address ranges, so
+  // this will update any existing range, not replace it.
+  addressCache->put (range, (gnu::gcj::RawData *) entry);
+
+  char *fname = NULL;
+  if (fileno == 0)
+    fname = define_file;
+  else if (fileno <= nfnames && fileno <= MAX_FNAMES)
+    fname = fnames[fileno - 1];
+
+  return new gnu::gcj::runtime::DebugInfo
+    (fname ? JvNewStringLatin1 (fname) : NULL, line, column, true);
+}
+
+#endif // HAVE_LINK_H
+
+
+
+gnu::gcj::runtime::DebugInfo *
+gnu::gcj::runtime::Dwarf2NameFinder::lookup (gnu::gcj::RawData *addrs, jint n)
+{
+#ifdef HAVE_LINK_H
+  ElfW(Dyn) *dyn;
+  struct r_debug *rdbg = NULL;
+  struct link_map *lmap = NULL;
+  struct link_map *base = NULL;
+  char *libname;
+  jstring libstring;
+  _Jv_frame_info *stack = (_Jv_frame_info *) addrs;
+  ElfW(Addr) target = (ElfW(Addr)) stack[n].addr;
+  dw2_finder_cache_entry_t *entry;
+  dw2_finder_state_t *state;
+
+  // First check if we have the address (or a nearby one) cached.
+  entry = (dw2_finder_cache_entry_t *) addressCache->get ((gnu::gcj::RawData *) target);
+  if (entry != NULL && (ElfW(Addr)) entry->state->base < target)
+    {
+      TRACE("found cached entry for %p at %p", target, entry);
+      return interpret_dw2 (entry->state, entry->offset, target,
+			    addressCache);
+    }
+
+  // Find the dynamic loader information. This symbol will be 0 for
+  // static executables.
+  if (_DYNAMIC != NULL)
+    {
+      for (dyn = _DYNAMIC; dyn->d_tag != DT_NULL; dyn++)
+	if (dyn->d_tag == DT_DEBUG)
+	  {
+	    rdbg = (struct r_debug *) dyn->d_un.d_ptr;
+	    break;
+	  }
+    }
+  // XXX on certain platforms I've found that the above (which the
+  // glibc headers give as an example) won't work, because d_un.d_ptr is
+  // NULL there. The following does work. I don't know if this is the
+  // correct thing to do, however.
+  if (rdbg == NULL)
+    rdbg = &_r_debug;
+
+  // Find the link entry that corresponds to this address.
+  for (lmap = rdbg->r_map; lmap != NULL; lmap = lmap->l_next)
+    {
+      if (target >= lmap->l_addr)
+	{
+	  if (!base || base->l_addr < lmap->l_addr)
+	    base = lmap;
+	}
+    }
+
+  libname = base->l_name;
+  if (libname == NULL || strlen (libname) == 0)
+    libname = (char *) _Jv_ThisExecutable();
+  if (libname == NULL)
+    return NULL;
+
+  libstring = JvNewStringLatin1 (libname);
+  state = (dw2_finder_state_t *) libraries->get (libstring);
+  if (state == NULL)
+    state = open_state (libname, libraries);
+
+  if (state == NULL)
+    return NULL; // no debug info available.
+
+  state->base = (ElfW(Addr)) base->l_addr;
+  return interpret_dw2 (state, 0, target, addressCache);
+#else
+  return NULL;
+#endif // HAVE_LINK_H
+}
+
+void
+gnu::gcj::runtime::Dwarf2NameFinder::finalize()
+{
+#ifdef HAVE_LINK_H
+  java::util::Iterator *it = addressCache->values()->iterator();
+  while (it->hasNext())
+    {
+      RawData *d = (RawData *) it->next();
+      it->remove();
+      if (d != NULL)
+	JvFree (d);
+    }
+
+  it = libraries->values()->iterator();
+  while (it->hasNext())
+    {
+      dw2_finder_state_t *state = (dw2_finder_state_t *) it->next();
+      it->remove();
+      close_state (state);
+    }
+#endif
+}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]