diff --git a/.travis.yml b/.travis.yml index cc2cdc43444b..f741a5e210d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ matrix: - env: JDK="jdk8" GATE="style,fullbuild" PRIMARY="substratevm" - env: JDK="jdk8" GATE="build,test" PRIMARY="compiler" - env: JDK="jdk8" GATE="build,test,helloworld" PRIMARY="substratevm" + - env: JDK="jdk8" GATE="build,test,helloworld_debug" PRIMARY="substratevm" - env: JDK="jdk8" GATE="build,bootstraplite" PRIMARY="compiler" - env: JDK="jdk8" GATE="style,fullbuild,sulongBasic" PRIMARY="sulong" addons: diff --git a/substratevm/DEBUGINFO.md b/substratevm/DEBUGINFO.md new file mode 100644 index 000000000000..af5d0ed297d3 --- /dev/null +++ b/substratevm/DEBUGINFO.md @@ -0,0 +1,254 @@ +Using the debug info feature +---------------------------- + +To add debug info to a generated native image add flag +-H:GenerateDebugInfo= to the native image command line (where N is +a positive integer value -- the default value 0 means generate no +debug info). For example, + + $ javac Hello.java + $ mx native-image -H:GenerateDebugInfo=1 Hello + +The resulting image should contain code (method) debug records in a +format gdb understands (Windows support is still under development). +At present it makes no difference which positive value is supplied as +argument to the GenerateDebugInfo option. + +The GenerateDebugInfo option also enables caching of sources for any +JDK runtime classes, GraalVM classes and application classes which can +be located during native image generation. The cache is created under +local subdirectory sources. It is used to configure source file search +path roots for the debugger. Files in the cache are located in a +directory hierarchy that matches the file path information included in +the native image debug records. The source cache should contain all +the files needed to debug the generated image and nothing more. This +local cache provides a convenient way of making just the necessary +sources available to the debugger/IDE when debugging a native image. + +The implementation tries to be smart about locating source files. It +uses the current JAVA_HOME to locate the JDK src.zip when searching +for JDK runtime sources. It also uses entries in the classpath to +suggest locations for GraalVM source files and application source +files (see below for precise details of the scheme used to identify +source locations). However, source layouts do vary and it may not be +possible to find all sources. Hence, users can specify the location of +source files explicitly on the command line using option +DebugInfoSourceSearchPath: + + $ javac --source-path apps/greeter/src \ + -d apps/greeter/classes org/my/greeter/*Greeter.java + $ javac -cp apps/greeter/classes \ + --source-path apps/hello/src \ + -d apps/hello/classes org/my/hello/Hello.java + $ mx native-image -H:GenerateDebugInfo=1 \ + -H:DebugInfoSourceSearchPath=apps/hello/src \ + -H:DebugInfoSourceSearchPath=apps/greeter/src \ + -cp apps/hello/classes:apps/greeter/classes org.my.hello.Hello + +Option DebugInfoSourceSearchPath can be repeated as many times as +required to notify all the target source locations. The value passed +to this option can be either an absolute or relative path. It can +identify either a directory, a source jar or a source zip file. It is +also possible to specify several source roots at once using a comma +separator: + + $ mx native-image -H:GenerateDebugInfo=1 \ + -H:DebugInfoSourceSearchPath=apps/hello/target/hello-sources.jar,apps/greeter/target/greeter-sources.jar \ + -cp apps/target/hello.jar:apps/target/greeter.jar \ + org.my.Hello + +Note that in both the examples above the DebugInfoSourceSearchPath +options are actually redundant. In the first case the classpath +entries for apps/hello/classes and apps/greeter/classes will be used +to derive the default search roots apps/hello/src and +apps/greeter/src. In the second case classpath entries +apps/target/hello.jar and apps/target/greeter.jar will be used to +derive the default search roots apps/target/hello-sources.jar and +apps/target/greeter-sources.jar. + +What is currently implemented +----------------------------- + +The currently implemented features include: + + - break points configured by file and line or by method name + - single stepping by line including both into and over function calls + - stack backtraces (not including frames detailing inlined code) + +Note that single stepping within a compiled method includes file and +line number info for inlined code, including inlined Graal methods. +So, gdb may switch files even though you are still in the same +compiled method. + +Identifying the location of source code +--------------------------------------- + +One goal of the implementation is to make it simple to configure your +debugger so that it can identify the relevant source file when it +stops during program execution. The native image generator tries to +achieve this by accumulating the relevant sources in a suitably +structured file cache. + +The native image generator uses different strategies to locate source +files for JDK runtime classes, GraalVM classes and application source +classes for inclusion in the local sources cache. It identifies which +strategy to use based on the package name of the class. So, for +example, packages starting with java.* or jdk.* are JDK classes; +packages starting with org.graal.* or com.oracle.svm.* are GraalVM +classes; any other packages are regarded as application classes. + +Sources for JDK runtime classes are retrieved from the src.zip found +in the JDK release used to run the native image generation process. +Retrieved files are cached under subdirectory sources/jdk, using the +module name (for JDK11) and package name of the associated class to +define the directory hierarchy in which the source is located. + +So, for example, on Linux the source for class java.util.HashMap will +be cached in file sources/jdk/java.base/java/util/HashMap.java. Debug +info records for this class and its methods will identify this source +file using the relative directory path java.base/java/util and file +name HashMap.java. On Windows things will be the same modulo use of +'\' rather than '/' as the file separator. + +Sources for GraalVM classes are retrieved from zip files or source +directories derived from entries in the classpath. Retrieved files are +cached under subdirectory sources/graal, using the package name of the +associated class to define the directory hierarchy in which the source +is located (e.g. class com.oracle.svm.core.VM has its source file +cached at sources/graal/com/oracle/svm/core/VM.java). + +The lookup scheme for cached GraalVM sources varies depending upon +what is found in each classpath entry. Given a jar file entry like +/path/to/foo.jar, the corresponding file /path/to/foo.src.zip is +considered as a candidate zip file system from which source files may +be extracted. When the entry specifies a dir like /path/to/bar then +directories /path/to/bar/src and /path/to/bar/src_gen are considered +as candidates. Candidates are skipped when i) the zip file or source +directory does not exist or ii) it does not contain at least one +subdirectory hierarchy that matches one of the the expected GraalVM +package hierarchies. + +Sources for application classes are retrieved from source jar files or +source directories derived from entries in the classpath. Retrieved +files are cached under subdirectory sources/src, using the package +name of the associated class to define the directory hierarchy in +which the source is located (e.g. class org.my.foo.Foo has its +source file cached as sources/src/org/my/foo/Foo.java). + +The lookup scheme for cached Application sources varies depending upon +what is found in each classpath entry. Given a jar file entry like +/path/to/foo.jar, the corresponding jar /path/to/foo-sources.jar is +considered as a candidate zip file system from which source files may +be extracted. When the entry specifies a dir like /path/to/bar/classes +or /path/to/bar/target/classes then directory /path/to/bar/src is +considered as a candidate. Finally, the current directory in which the +native image program is being run is also considered as a candidate. + +These lookup strategies are only provisional and may need extending in +future. Note however that it is possible to make missing sources +available by other means. One option is to unzip extra app source jars +or copying extra app source trees into the cache. Another is to +configure extra source search paths (see below). + +Configuring source paths in gdb +------------------------------- + +In order for gdb to be able to locate the source files for your app +classes, Graal classes and JDK runtime classes you need to provide gdb +with a list of source root dirs using the 'set directories' command: + + (gdb) set directories /path/to/sources/jdk:/path/to/sources/graal:/path/to/sources/src + +Directory .../sources/jdk should contain source files for all JDK runtime +classes referenced from debug records. + +Directory .../sources/graal should contain source files for all GraalVM +classes referenced from debug records. Note that the current +implementation does not yet find some sources for the GraalVM JIT +compiler in the org.graalvm.compiler* package subspace. + +Directory .../sources/src should contain source files for all +application classes referenced from debug records, assuming they can +be located using the lookup strategy described above. + +You can supplement the files cached in sources/src by unzipping +application source jars or copying application source trees into the +cache. You need to ensure that any new subdirectory you add to +sources/src corresponds to the top level package for the classes whose +sources are being included. + +You can also add extra directories to the search path. Note that gdb +does not understand zip format file systems so any extra entries you +add must identify a directory tree containing the relevant +sources. Once again. top level entries in the directory added to the +search path must correspond to the top level package for the classes +whose sources are being included. + +Configuring source paths in VS +------------------------------ + +TO BE ADDED + +Checking debug info on Linux +---------------------------- + +n.b. this is only of interest to those who want to understand how the +debug info implementation works or want to trouble shoot problems +encountered during debugging that might relate to the debug info +encoding. + +The objdump command can be used to display the debug info embedded +into a native image. The following commands (which all assume the +target binary is called hello) can be used to display all currently +generated content: + + $ objdump --dwarf=info hello > info + $ objdump --dwarf=abbrev hello > abbrev + $ objdump --dwarf=ranges hello > ranges + $ objdump --dwarf=decodedline hello > decodedline + $ objdump --dwarf=rawline hello > rawline + $ objdump --dwarf=str hello > str + $ objdump --dwarf=frames hello > frames + +The *info* section includes details of all compiled Java methods. + +The *abbrev* section defines the layout of records in the info section +that describe Java files (compilation units) and methods. + +The *ranges* section details the start and end addresses of method +code segments + +The *decodedline* section maps subsegments of method code range +segments to files and line numbers. This mapping includes entries +for files and line numbers for inlined methods. + +The *rawline* segment provides details of how the line table is +generated using DWARF state machine instructions that encode file, +line and address transitions. + +The *str* section provides a lookup table for strings referenced +from records in the info section + +The *frames* section lists transition points in compiled methods +where a (fixed size) stack frame is pushed or popped, allowing +the debugger to identify each frame's current and previous stack +pointers and it's return address. + +Note that some of the content embedded in the debug records is +generated by the C compiler and belongs to code that is either in +libraries or the C lib bootstrap code that is bundled in with the +Java method code. + +Currently supported targets +--------------------------- + +The prototype is currently implemented only for gdb on Linux. + + - Linux/x86_64 support has been tested and should work + correctly. + + - Linux/AArch64 support is present but has not yet been fully + verified (break points should work ok but stack backtraces + may be incorrect). + +Windows support is still under development. diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 1a91d5c6a317..99f2b3f11561 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -359,6 +359,7 @@ def __getattr__(self, name): GraalTags = Tags([ 'helloworld', + 'helloworld_debug', 'test', 'maven', 'js', @@ -468,6 +469,19 @@ def svm_gate_body(args, tasks): cinterfacetutorial([]) clinittest([]) + with Task('image demos debuginfo', tasks, tags=[GraalTags.helloworld_debug]) as t: + if t: + if svm_java8(): + javac_image(['--output-path', svmbuild_dir(), '-H:GenerateDebugInfo=1']) + javac_command = ['--javac-command', ' '.join(javac_image_command(svmbuild_dir())), '-H:GenerateDebugInfo=1'] + else: + # Building javac image currently only supported for Java 8 + javac_command = ['-H:GenerateDebugInfo=1'] + helloworld(['--output-path', svmbuild_dir()] + javac_command) + helloworld(['--output-path', svmbuild_dir(), '--shared', '-H:GenerateDebugInfo=1']) # Build and run helloworld as shared library + cinterfacetutorial(['-H:GenerateDebugInfo=1']) + clinittest([]) + with Task('native unittests', tasks, tags=[GraalTags.test]) as t: if t: with tempfile.NamedTemporaryFile(mode='w') as blacklist: diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 545421d5e532..985d3b400a04 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -604,7 +604,9 @@ "com.oracle.objectfile" : { "subDir": "src", "sourceDirs" : ["src"], - "dependencies" : [], + "dependencies" : [ + "compiler:GRAAL" + ], "checkstyle" : "com.oracle.svm.hosted", "javaCompliance" : "8+", "annotationProcessors" : ["compiler:GRAAL_PROCESSOR"], @@ -920,7 +922,9 @@ "dependencies": [ "com.oracle.objectfile" ], - }, + "distDependencies": [ + "compiler:GRAAL", + ], }, "GRAAL_HOTSPOT_LIBRARY": { "subDir": "src", diff --git a/substratevm/src/com.oracle.objectfile/.checkstyle_checks.xml b/substratevm/src/com.oracle.objectfile/.checkstyle_checks.xml new file mode 100644 index 000000000000..788e1fa0b557 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/.checkstyle_checks.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java index cbf13d644688..c9dabe238e12 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java @@ -43,13 +43,16 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.function.Consumer; import java.util.stream.StreamSupport; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.macho.MachOObjectFile; import com.oracle.objectfile.pecoff.PECoffObjectFile; import sun.nio.ch.DirectBuffer; +import org.graalvm.compiler.debug.DebugContext; /** * Abstract superclass for object files. An object file is a binary container for sections, @@ -1086,6 +1089,17 @@ protected boolean elementsCanSharePage(Element s1, Element s2, int offset1, int // flag compatibility } + /** + * API method provided to allow a native image generator to provide details of types, code and + * heap data inserted into a native image. + * + * @param debugInfoProvider an implementation of the provider interface that communicates + * details of the relevant types, code and heap data. + */ + public void installDebugInfo(@SuppressWarnings("unused") DebugInfoProvider debugInfoProvider) { + // do nothing by default + } + protected static Iterable allDecisions(final Map decisions) { return () -> StreamSupport.stream(decisions.values().spliterator(), false) .flatMap(layoutDecisionMap -> StreamSupport.stream(layoutDecisionMap.spliterator(), false)).iterator(); @@ -1725,4 +1739,50 @@ public final SymbolTable getOrCreateSymbolTable() { return createSymbolTable(); } } + + /** + * Temporary storage for a debug context installed in a nested scope under a call. to + * {@link #withDebugContext} + */ + private DebugContext debugContext = null; + + /** + * Allows a task to be executed with a debug context in a named subscope bound to the object + * file and accessible to code executed during the lifetime of the task. Invoked code may obtain + * access to the debug context using method {@link #debugContext}. + * + * @param context a context to be bound to the object file for the duration of the task + * execution. + * @param scopeName a name to be used to define a subscope current while the task is being + * executed. + * @param task a task to be executed while the context is bound to the object file. + */ + @SuppressWarnings("try") + public void withDebugContext(DebugContext context, String scopeName, Runnable task) { + try (DebugContext.Scope s = context.scope(scopeName)) { + this.debugContext = context; + task.run(); + } catch (Throwable e) { + throw debugContext.handle(e); + } finally { + debugContext = null; + } + } + + /** + * Allows a consumer to retrieve the debug context currently bound to this object file. This + * method must only called underneath an invocation of method {@link #withDebugContext}. + * + * @param scopeName a name to be used to define a subscope current while the consumer is active. + * @param action an action parameterised by the debug context. + */ + @SuppressWarnings("try") + public void debugContext(String scopeName, Consumer action) { + assert debugContext != null; + try (DebugContext.Scope s = debugContext.scope(scopeName)) { + action.accept(debugContext); + } catch (Throwable e) { + throw debugContext.handle(e); + } + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java new file mode 100644 index 000000000000..32e856a7e6e9 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * track debug info associated with a Java class. + */ +public class ClassEntry { + /** + * the name of the associated class. + */ + private String className; + /** + * details of the associated file. + */ + private FileEntry fileEntry; + /** + * a list recording details of all primary ranges included in this class sorted by ascending + * address range. + */ + private LinkedList primaryEntries; + /** + * an index identifying primary ranges which have already been encountered. + */ + private Map primaryIndex; + /** + * an index of all primary and secondary files referenced from this class's compilation unit. + */ + private Map localFilesIndex; + /** + * a list of the same files. + */ + private LinkedList localFiles; + /** + * an index of all primary and secondary dirs referenced from this class's compilation unit. + */ + private HashMap localDirsIndex; + /** + * a list of the same dirs. + */ + private LinkedList localDirs; + /** + * index of debug_info section compilation unit for this class. + */ + private int cuIndex; + /** + * index into debug_line section for associated compilation unit. + */ + private int lineIndex; + /** + * size of line number info prologue region for associated compilation unit. + */ + private int linePrologueSize; + /** + * total size of line number info region for associated compilation unit. + */ + private int totalSize; + + public ClassEntry(String className, FileEntry fileEntry) { + this.className = className; + this.fileEntry = fileEntry; + this.primaryEntries = new LinkedList<>(); + this.primaryIndex = new HashMap<>(); + this.localFiles = new LinkedList<>(); + this.localFilesIndex = new HashMap<>(); + this.localDirs = new LinkedList<>(); + this.localDirsIndex = new HashMap<>(); + if (fileEntry != null) { + localFiles.add(fileEntry); + localFilesIndex.put(fileEntry, localFiles.size()); + DirEntry dirEntry = fileEntry.getDirEntry(); + if (dirEntry != null) { + localDirs.add(dirEntry); + localDirsIndex.put(dirEntry, localDirs.size()); + } + } + this.cuIndex = -1; + this.lineIndex = -1; + this.linePrologueSize = -1; + this.totalSize = -1; + } + + public void addPrimary(Range primary, List frameSizeInfos, int frameSize) { + if (primaryIndex.get(primary) == null) { + PrimaryEntry primaryEntry = new PrimaryEntry(primary, frameSizeInfos, frameSize, this); + primaryEntries.add(primaryEntry); + primaryIndex.put(primary, primaryEntry); + } + } + + public void addSubRange(Range subrange, FileEntry subFileEntry) { + Range primary = subrange.getPrimary(); + /* + * the subrange should belong to a primary range + */ + assert primary != null; + PrimaryEntry primaryEntry = primaryIndex.get(primary); + /* + * we should already have seen the primary range + */ + assert primaryEntry != null; + assert primaryEntry.getClassEntry() == this; + primaryEntry.addSubRange(subrange, subFileEntry); + if (subFileEntry != null) { + if (localFilesIndex.get(subFileEntry) == null) { + localFiles.add(subFileEntry); + localFilesIndex.put(subFileEntry, localFiles.size()); + } + DirEntry dirEntry = subFileEntry.getDirEntry(); + if (dirEntry != null && localDirsIndex.get(dirEntry) == null) { + localDirs.add(dirEntry); + localDirsIndex.put(dirEntry, localDirs.size()); + } + } + } + + public int localDirsIdx(DirEntry dirEntry) { + if (dirEntry != null) { + return localDirsIndex.get(dirEntry); + } else { + return 0; + } + } + + public int localFilesIdx(@SuppressWarnings("hiding") FileEntry fileEntry) { + return localFilesIndex.get(fileEntry); + } + + public String getFileName() { + if (fileEntry != null) { + return fileEntry.getFileName(); + } else { + return ""; + } + } + + @SuppressWarnings("unused") + String getFullFileName() { + if (fileEntry != null) { + return fileEntry.getFullName(); + } else { + return null; + } + } + + @SuppressWarnings("unused") + String getDirName() { + if (fileEntry != null) { + return fileEntry.getPathName(); + } else { + return ""; + } + } + + public void setCUIndex(int cuIndex) { + // Should only get set once to a non-negative value. + assert cuIndex >= 0; + assert this.cuIndex == -1; + this.cuIndex = cuIndex; + } + + public int getCUIndex() { + // Should have been set before being read. + assert cuIndex >= 0; + return cuIndex; + } + + public int getLineIndex() { + return lineIndex; + } + + public void setLineIndex(int lineIndex) { + this.lineIndex = lineIndex; + } + + public void setLinePrologueSize(int linePrologueSize) { + this.linePrologueSize = linePrologueSize; + } + + public int getLinePrologueSize() { + return linePrologueSize; + } + + public int getTotalSize() { + return totalSize; + } + + public void setTotalSize(int totalSize) { + this.totalSize = totalSize; + } + + public FileEntry getFileEntry() { + return fileEntry; + } + + public String getClassName() { + return className; + } + + public LinkedList getPrimaryEntries() { + return primaryEntries; + } + + public Object primaryIndexFor(Range primaryRange) { + return primaryIndex.get(primaryRange); + } + + public LinkedList getLocalDirs() { + return localDirs; + } + + public LinkedList getLocalFiles() { + return localFiles; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java new file mode 100644 index 000000000000..9497dbe2bd15 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import org.graalvm.compiler.debug.DebugContext; + +import java.nio.ByteOrder; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * An abstract class which indexes the information presented by the DebugInfoProvider in an + * organization suitable for use by subclasses targeting a specific binary format. + */ +public abstract class DebugInfoBase { + protected ByteOrder byteOrder; + /** + * A table listing all known strings, some of which may be marked for insertion into the + * debug_str section. + */ + private StringTable stringTable = new StringTable(); + /** + * Index of all dirs in which files are found to reside either as part of substrate/compiler or + * user code. + */ + private Map dirsIndex = new HashMap<>(); + + /* + * The obvious traversal structure for debug records is: + * + * 1) by top level compiled method (primary Range) ordered by ascending address + * + * 2) by inlined method (sub range) within top level method ordered by ascending address + * + * These can be used to ensure that all debug records are generated in increasing address order + * + * An alternative traversal option is + * + * 1) by top level class (String id) + * + * 2) by top level compiled method (primary Range) within a class ordered by ascending address + * + * 3) by inlined method (sub range) within top level method ordered by ascending address + * + * This relies on the (current) fact that methods of a given class always appear in a single + * continuous address range with no intervening code from other methods or data values. this + * means we can treat each class as a compilation unit, allowing data common to all methods of + * the class to be shared. + * + * A third option appears to be to traverse via files, then top level class within file etc. + * Unfortunately, files cannot be treated as the compilation unit. A file F may contain multiple + * classes, say C1 and C2. There is no guarantee that methods for some other class C' in file F' + * will not be compiled into the address space interleaved between methods of C1 and C2. That is + * a shame because generating debug info records one file at a time would allow more sharing + * e.g. enabling all classes in a file to share a single copy of the file and dir tables. + */ + + /** + * List of class entries detailing class info for primary ranges. + */ + private LinkedList primaryClasses = new LinkedList<>(); + /** + * index of already seen classes. + */ + private Map primaryClassesIndex = new HashMap<>(); + /** + * Index of files which contain primary or secondary ranges. + */ + private Map filesIndex = new HashMap<>(); + /** + * List of of files which contain primary or secondary ranges. + */ + private LinkedList files = new LinkedList<>(); + + public DebugInfoBase(ByteOrder byteOrder) { + this.byteOrder = byteOrder; + } + + /** + * Entry point allowing ELFObjectFile to pass on information about types, code and heap data. + * + * @param debugInfoProvider provider instance passed by ObjectFile client. + */ + @SuppressWarnings("try") + public void installDebugInfo(DebugInfoProvider debugInfoProvider) { + /* + * This will be needed once we add support for type info: + * + * DebugTypeInfoProvider typeInfoProvider = debugInfoProvider.typeInfoProvider(); for + * (DebugTypeInfo debugTypeInfo : typeInfoProvider) { install types } + */ + + /* + * Ensure we have a null string in the string section. + */ + stringTable.uniqueDebugString(""); + + debugInfoProvider.codeInfoProvider().forEach(debugCodeInfo -> debugCodeInfo.debugContext((debugContext) -> { + /* + * primary file name and full method name need to be written to the debug_str section + */ + String fileName = debugCodeInfo.fileName(); + Path filePath = debugCodeInfo.filePath(); + // switch '$' in class names for '.' + String className = debugCodeInfo.className().replaceAll("\\$", "."); + String methodName = debugCodeInfo.methodName(); + String paramNames = debugCodeInfo.paramNames(); + String returnTypeName = debugCodeInfo.returnTypeName(); + int lo = debugCodeInfo.addressLo(); + int hi = debugCodeInfo.addressHi(); + int primaryLine = debugCodeInfo.line(); + + Range primaryRange = new Range(fileName, filePath, className, methodName, paramNames, returnTypeName, stringTable, lo, hi, primaryLine); + debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", className, methodName, filePath, fileName, primaryLine, lo, hi); + addRange(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize()); + debugCodeInfo.lineInfoProvider().forEach(debugLineInfo -> { + String fileNameAtLine = debugLineInfo.fileName(); + Path filePathAtLine = debugLineInfo.filePath(); + // Switch '$' in class names for '.' + String classNameAtLine = debugLineInfo.className().replaceAll("\\$", "."); + String methodNameAtLine = debugLineInfo.methodName(); + int loAtLine = lo + debugLineInfo.addressLo(); + int hiAtLine = lo + debugLineInfo.addressHi(); + int line = debugLineInfo.line(); + /* + * Record all subranges even if they have no line or file so we at least get a + * symbol for them. + */ + Range subRange = new Range(fileNameAtLine, filePathAtLine, classNameAtLine, methodNameAtLine, "", "", stringTable, loAtLine, hiAtLine, line, primaryRange); + addSubRange(primaryRange, subRange); + try (DebugContext.Scope s = debugContext.scope("Subranges")) { + debugContext.log(DebugContext.VERBOSE_LEVEL, "SubRange %s.%s %s %s:%d 0x%x, 0x%x]", classNameAtLine, methodNameAtLine, filePathAtLine, fileNameAtLine, line, loAtLine, hiAtLine); + } + }); + })); + /* + * This will be needed once we add support for data info: + * + * DebugDataInfoProvider dataInfoProvider = debugInfoProvider.dataInfoProvider(); for + * (DebugDataInfo debugDataInfo : dataInfoProvider) { install details of heap elements + * String name = debugDataInfo.toString(); } + */ + } + + private ClassEntry ensureClassEntry(Range range) { + String className = range.getClassName(); + /* + * See if we already have an entry. + */ + ClassEntry classEntry = primaryClassesIndex.get(className); + if (classEntry == null) { + /* + * Create and index the entry associating it with the right file. + */ + FileEntry fileEntry = ensureFileEntry(range); + classEntry = new ClassEntry(className, fileEntry); + primaryClasses.add(classEntry); + primaryClassesIndex.put(className, classEntry); + } + assert classEntry.getClassName().equals(className); + return classEntry; + } + + private FileEntry ensureFileEntry(Range range) { + String fileName = range.getFileName(); + if (fileName == null) { + return null; + } + Path filePath = range.getFilePath(); + Path fileAsPath = range.getFileAsPath(); + /* + * Ensure we have an entry. + */ + FileEntry fileEntry = filesIndex.get(fileAsPath); + if (fileEntry == null) { + DirEntry dirEntry = ensureDirEntry(filePath); + fileEntry = new FileEntry(fileName, dirEntry); + files.add(fileEntry); + /* + * Index the file entry by file path. + */ + filesIndex.put(fileAsPath, fileEntry); + if (!range.isPrimary()) { + /* Check we have a file for the corresponding primary range. */ + Range primaryRange = range.getPrimary(); + FileEntry primaryFileEntry = filesIndex.get(primaryRange.getFileAsPath()); + assert primaryFileEntry != null; + } + } + return fileEntry; + } + + private void addRange(Range primaryRange, List frameSizeInfos, int frameSize) { + assert primaryRange.isPrimary(); + ClassEntry classEntry = ensureClassEntry(primaryRange); + classEntry.addPrimary(primaryRange, frameSizeInfos, frameSize); + } + + private void addSubRange(Range primaryRange, Range subrange) { + assert primaryRange.isPrimary(); + assert !subrange.isPrimary(); + String className = primaryRange.getClassName(); + ClassEntry classEntry = primaryClassesIndex.get(className); + FileEntry subrangeFileEntry = ensureFileEntry(subrange); + /* + * The primary range should already have been seen and associated with a primary class + * entry. + */ + assert classEntry.primaryIndexFor(primaryRange) != null; + if (subrangeFileEntry != null) { + classEntry.addSubRange(subrange, subrangeFileEntry); + } + } + + private DirEntry ensureDirEntry(Path filePath) { + if (filePath == null) { + return null; + } + DirEntry dirEntry = dirsIndex.get(filePath); + if (dirEntry == null) { + dirEntry = new DirEntry(filePath); + dirsIndex.put(filePath, dirEntry); + } + return dirEntry; + } + + /* Accessors to query the debug info model. */ + public ByteOrder getByteOrder() { + return byteOrder; + } + + public LinkedList getPrimaryClasses() { + return primaryClasses; + } + + @SuppressWarnings("unused") + public LinkedList getFiles() { + return files; + } + + @SuppressWarnings("unused") + public FileEntry findFile(Path fullFileName) { + return filesIndex.get(fullFileName); + } + + public StringTable getStringTable() { + return stringTable; + } + + /** + * Indirects this call to the string table. + * + * @param string the string whose index is required. + * + * @return the offset of the string in the .debug_str section. + */ + public int debugStringIndex(String string) { + return stringTable.debugStringIndex(string); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java new file mode 100644 index 000000000000..034653171103 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import java.nio.file.Path; + +/** + * Tracks the directory associated with one or more source files. + * + * This is identified separately from each FileEntry idenityfing files that reside in the directory. + * That is necessary because the line info generator needs to collect and write out directory names + * into directory tables once only rather than once per file. + */ +public class DirEntry { + private Path path; + + public DirEntry(Path path) { + this.path = path; + } + + public Path getPath() { + return path; + } + + public String getPathString() { + return path.toString(); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java new file mode 100644 index 000000000000..50764f2aa564 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +/** + * Tracks debug info associated with a Java source file. + */ +public class FileEntry { + private String fileName; + private DirEntry dirEntry; + + public FileEntry(String fileName, DirEntry dirEntry) { + this.fileName = fileName; + this.dirEntry = dirEntry; + } + + /** + * The name of the associated file excluding path elements. + */ + public String getFileName() { + return fileName; + } + + public String getPathName() { + return getDirEntry().getPathString(); + } + + public String getFullName() { + return getDirEntry().getPath().resolve(getFileName()).toString(); + } + + /** + * The directory entry associated with this file entry. + */ + public DirEntry getDirEntry() { + return dirEntry; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java new file mode 100644 index 000000000000..1948cf019ba3 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * Tracks debug info associated with a primary method. i.e. a top level compiled method + */ +public class PrimaryEntry { + /** + * The primary range detailed by this object. + */ + private Range primary; + /** + * Details of the class owning this range. + */ + private ClassEntry classEntry; + /** + * A list of subranges associated with the primary range. + */ + private List subranges; + /** + * A mapping from subranges to their associated file entry. + */ + private HashMap subrangeIndex; + /** + * Details of of compiled method frame size changes. + */ + private List frameSizeInfos; + /** + * Size of compiled method frame. + */ + private int frameSize; + + public PrimaryEntry(Range primary, List frameSizeInfos, int frameSize, ClassEntry classEntry) { + this.primary = primary; + this.classEntry = classEntry; + this.subranges = new LinkedList<>(); + this.subrangeIndex = new HashMap<>(); + this.frameSizeInfos = frameSizeInfos; + this.frameSize = frameSize; + } + + public void addSubRange(Range subrange, FileEntry subFileEntry) { + /* + * We should not see a subrange more than once. + */ + assert !subranges.contains(subrange); + assert subrangeIndex.get(subrange) == null; + /* + * We need to generate a file table entry for all ranges. + */ + subranges.add(subrange); + subrangeIndex.put(subrange, subFileEntry); + } + + public Range getPrimary() { + return primary; + } + + public ClassEntry getClassEntry() { + return classEntry; + } + + public List getSubranges() { + return subranges; + } + + public FileEntry getSubrangeFileEntry(Range subrange) { + return subrangeIndex.get(subrange); + } + + public List getFrameSizeInfos() { + return frameSizeInfos; + } + + public int getFrameSize() { + return frameSize; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java new file mode 100644 index 000000000000..1b65c315e8a7 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Details of a specific address range in a compiled method either a primary range identifying a + * whole method or a sub-range identifying a sequence of instructions that belong to an inlined + * method. + */ + +public class Range { + private String fileName; + private Path filePath; + private String className; + private String methodName; + private String paramNames; + private String returnTypeName; + private String fullMethodName; + private int lo; + private int hi; + private int line; + /* + * This is null for a primary range. + */ + private Range primary; + + /* + * Create a primary range. + */ + public Range(String fileName, Path filePath, String className, String methodName, String paramNames, String returnTypeName, StringTable stringTable, int lo, int hi, int line) { + this(fileName, filePath, className, methodName, paramNames, returnTypeName, stringTable, lo, hi, line, null); + } + + /* + * Create a primary or secondary range. + */ + public Range(String fileName, Path filePath, String className, String methodName, String paramNames, String returnTypeName, StringTable stringTable, int lo, int hi, int line, Range primary) { + /* + * Currently file name and full method name need to go into the debug_str section other + * strings just need to be deduplicated to save space. + */ + this.fileName = (fileName == null ? null : stringTable.uniqueDebugString(fileName)); + this.filePath = filePath; + this.className = stringTable.uniqueString(className); + this.methodName = stringTable.uniqueString(methodName); + this.paramNames = stringTable.uniqueString(paramNames); + this.returnTypeName = stringTable.uniqueString(returnTypeName); + this.fullMethodName = stringTable.uniqueDebugString(constructClassAndMethodNameWithParams()); + this.lo = lo; + this.hi = hi; + this.line = line; + this.primary = primary; + } + + public boolean contains(Range other) { + return (lo <= other.lo && hi >= other.hi); + } + + public boolean isPrimary() { + return getPrimary() == null; + } + + public Range getPrimary() { + return primary; + } + + public String getFileName() { + return fileName; + } + + public Path getFilePath() { + return filePath; + } + + public Path getFileAsPath() { + if (filePath != null) { + return filePath.resolve(fileName); + } else if (fileName != null) { + return Paths.get(fileName); + } else { + return null; + } + } + + public String getClassName() { + return className; + } + + public String getMethodName() { + return methodName; + } + + public int getHi() { + return hi; + } + + public int getLo() { + return lo; + } + + public int getLine() { + return line; + } + + public String getFullMethodName() { + return fullMethodName; + } + + private String getExtendedMethodName(boolean includeParams, boolean includeReturnType) { + StringBuilder builder = new StringBuilder(); + if (includeReturnType && returnTypeName.length() > 0) { + builder.append(returnTypeName); + builder.append(' '); + } + if (className != null) { + builder.append(className); + builder.append("::"); + } + builder.append(methodName); + if (includeParams && !paramNames.isEmpty()) { + builder.append('('); + builder.append(paramNames); + builder.append(')'); + } + return builder.toString(); + } + + private String constructClassAndMethodNameWithParams() { + return getExtendedMethodName(true, false); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java new file mode 100644 index 000000000000..c6d077661f64 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +/** + * Used to retain a unique (up to equals) copy of a String. Also flags whether the String needs to + * be located in the debug_string section and, if so, tracks the offset at which it gets written. + */ +public class StringEntry { + private String string; + private int offset; + private boolean addToStrSection; + + StringEntry(String string) { + this.string = string; + this.offset = -1; + } + + public String getString() { + return string; + } + + public int getOffset() { + /* + * Offset must be set before this can be fetched + */ + assert offset >= 0; + return offset; + } + + public void setOffset(int offset) { + assert this.offset < 0; + assert offset >= 0; + this.offset = offset; + } + + public boolean isAddToStrSection() { + return addToStrSection; + } + + public void setAddToStrSection() { + this.addToStrSection = true; + } + + @Override + public boolean equals(Object object) { + if (object == null || !(object instanceof StringEntry)) { + return false; + } else { + StringEntry other = (StringEntry) object; + return this == other || string.equals(other.string); + } + } + + @Override + public int hashCode() { + return string.hashCode() + 37; + } + + @Override + public String toString() { + return string; + } + + public boolean isEmpty() { + return string.length() == 0; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java new file mode 100644 index 000000000000..4b8537228aec --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import java.util.HashMap; +import java.util.Iterator; + +/** + * Allows incoming strings to be reduced to unique (up to equals) instances and supports marking of + * strings which need to be written to the debug_str section and retrieval of the location offset + * after writing. + */ +public class StringTable implements Iterable { + + private final HashMap table; + + public StringTable() { + this.table = new HashMap<>(); + } + + /** + * Ensures a unique instance of a string exists in the table, inserting the supplied String if + * no equivalent String is already present. This should only be called before the string section + * has been written. + * + * @param string the string to be included in the table + * @return the unique instance of the String + */ + public String uniqueString(String string) { + return ensureString(string, false); + } + + /** + * Ensures a unique instance of a string exists in the table and is marked for inclusion in the + * debug_str section, inserting the supplied String if no equivalent String is already present. + * This should only be called before the string section has been written. + * + * @param string the string to be included in the table and marked for inclusion in the + * debug_str section + * @return the unique instance of the String + */ + public String uniqueDebugString(String string) { + return ensureString(string, true); + } + + private String ensureString(String string, boolean addToStrSection) { + StringEntry stringEntry = table.get(string); + if (stringEntry == null) { + stringEntry = new StringEntry(string); + table.put(string, stringEntry); + } + if (addToStrSection && !stringEntry.isAddToStrSection()) { + stringEntry.setAddToStrSection(); + } + return stringEntry.getString(); + } + + /** + * Retrieves the offset at which a given string was written into the debug_str section. This + * should only be called after the string section has been written. + * + * @param string the strng whose offset is to be retrieved + * @return the offset or -1 if the string does not define an entry or the entry has not been + * written to the debug_str section + */ + public int debugStringIndex(String string) { + StringEntry stringEntry = table.get(string); + assert stringEntry != null; + if (stringEntry == null) { + return -1; + } + return stringEntry.getOffset(); + } + + @Override + public Iterator iterator() { + return table.values().iterator(); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java new file mode 100644 index 000000000000..63523cb22340 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debuginfo; + +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.graalvm.compiler.debug.DebugContext; + +/** + * Interfaces used to allow a native image to communicate details of types, code and data to the + * underlying object file so that the latter can insert appropriate debug info. + */ +public interface DebugInfoProvider { + /** + * Access details of a specific type. + */ + interface DebugTypeInfo { + } + + /** + * Access details of a specific compiled method. + */ + interface DebugCodeInfo { + void debugContext(Consumer action); + + /** + * @return the name of the file containing a compiled method excluding any path. + */ + String fileName(); + + /** + * @return a relative path to the file containing a compiled method derived from its package + * name or null if the method is in the empty package. + */ + Path filePath(); + + /** + * @return the fully qualified name of the class owning the compiled method. + */ + String className(); + + /** + * @return the name of the compiled method including signature. + */ + String methodName(); + + /** + * @return the lowest address containing code generated for the method represented as an + * offset into the code segment. + */ + int addressLo(); + + /** + * @return the first address above the code generated for the method represented as an + * offset into the code segment. + */ + int addressHi(); + + /** + * @return the starting line number for the method. + */ + int line(); + + /** + * @return a stream of records detailing line numbers and addresses within the compiled + * method. + */ + Stream lineInfoProvider(); + + /** + * @return a string identifying the method parameters. + */ + String paramNames(); + + /** + * @return a string identifying the method return type. + */ + String returnTypeName(); + + /** + * @return the size of the method frame between prologue and epilogue. + */ + int getFrameSize(); + + /** + * @return a list of positions at which the stack is extended to a full frame or torn down + * to an empty frame + */ + List getFrameSizeChanges(); + } + + /** + * Access details of a specific heap object. + */ + interface DebugDataInfo { + } + + /** + * Access details of code generated for a specific outer or inlined method at a given line + * number. + */ + interface DebugLineInfo { + /** + * @return the name of the file containing the outer or inlined method excluding any path. + */ + String fileName(); + + /** + * @return a relative path to the file containing the outer or inlined method derived from + * its package name or null if the method is in the empty package. + */ + Path filePath(); + + /** + * @return the fully qualified name of the class owning the outer or inlined method. + */ + String className(); + + /** + * @return the name of the outer or inlined method including signature. + */ + String methodName(); + + /** + * @return the lowest address containing code generated for an outer or inlined code segment + * reported at this line represented as an offset into the code segment. + */ + int addressLo(); + + /** + * @return the first address above the code generated for an outer or inlined code segment + * reported at this line represented as an offset into the code segment. + */ + int addressHi(); + + /** + * @return the line number for the outer or inlined segment. + */ + int line(); + } + + interface DebugFrameSizeChange { + enum Type { + EXTEND, + CONTRACT + } + + int getOffset(); + + DebugFrameSizeChange.Type getType(); + } + + @SuppressWarnings("unused") + Stream typeInfoProvider(); + + Stream codeInfoProvider(); + + @SuppressWarnings("unused") + Stream dataInfoProvider(); +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java index 6a2f99a778ba..729ca948b6cd 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java @@ -42,6 +42,14 @@ import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.StringTable; import com.oracle.objectfile.SymbolTable; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.objectfile.elf.dwarf.DwarfARangesSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfAbbrevSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfFrameSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfLineSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; +import com.oracle.objectfile.elf.dwarf.DwarfStrSectionImpl; import com.oracle.objectfile.io.AssemblyBuffer; import com.oracle.objectfile.io.OutputAssembler; @@ -1156,4 +1164,40 @@ public SymbolTable getSymbolTable() { protected int getMinimumFileSize() { return 0; } + + @Override + public void installDebugInfo(DebugInfoProvider debugInfoProvider) { + DwarfDebugInfo dwarfSections = new DwarfDebugInfo(getMachine(), getByteOrder()); + /* We need an implementation for each generated DWARF section. */ + DwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl(); + DwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl(); + DwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl(); + DwarfInfoSectionImpl elfInfoSectionImpl = dwarfSections.getInfoSectionImpl(); + DwarfARangesSectionImpl elfARangesSectionImpl = dwarfSections.getARangesSectionImpl(); + DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl(); + /* Now we can create the section elements with empty content. */ + newUserDefinedSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl); + newUserDefinedSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl); + newUserDefinedSection(frameSectionImpl.getSectionName(), frameSectionImpl); + newUserDefinedSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl); + newUserDefinedSection(elfARangesSectionImpl.getSectionName(), elfARangesSectionImpl); + newUserDefinedSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl); + /* + * The byte[] for each implementation's content are created and written under + * getOrDecideContent. Doing that ensures that all dependent sections are filled in and then + * sized according to the declared dependencies. However, if we leave it at that then + * associated reloc sections only get created when the first reloc is inserted during + * content write that's too late for them to have layout constraints included in the layout + * decision set and causes an NPE during reloc section write. So we need to create the + * relevant reloc sections here in advance. + */ + elfStrSectionImpl.getOrCreateRelocationElement(false); + elfAbbrevSectionImpl.getOrCreateRelocationElement(false); + frameSectionImpl.getOrCreateRelocationElement(false); + elfInfoSectionImpl.getOrCreateRelocationElement(false); + elfARangesSectionImpl.getOrCreateRelocationElement(false); + elfLineSectionImpl.getOrCreateRelocationElement(false); + /* Ok now we can populate the debug info model. */ + dwarfSections.installDebugInfo(debugInfoProvider); + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFRelocationSection.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFRelocationSection.java index 1209c6662099..8aa18d5344f4 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFRelocationSection.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFRelocationSection.java @@ -236,6 +236,15 @@ public Iterable getDependencies(Map // decisions.get(syms).getDecision(LayoutProperty.Kind.CONTENT); // deps.add(BuildDependency.createOrGet(ourContent, symtabContent)); /* If we're dynamic, it also depends on the vaddr of all referenced sections. */ + + // our content depends on the content of the section being relocated + // (because entries only get registered during generation) + if (relocated != null) { + LayoutDecision relocatedSectionContent = decisions.get(relocated).getDecision(LayoutDecision.Kind.CONTENT); + deps.add(BuildDependency.createOrGet(ourContent, + relocatedSectionContent)); + } + if (isDynamic()) { Set referenced = new HashSet<>(); for (Entry ent : entries.keySet()) { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java new file mode 100644 index 000000000000..5e459ee9a0be --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.LayoutDecisionMap; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.PrimaryEntry; +import com.oracle.objectfile.debugentry.Range; +import org.graalvm.compiler.debug.DebugContext; + +import java.util.LinkedList; +import java.util.Map; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ARANGES_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_INFO_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_VERSION_2; + +/** + * Section generator for debug_aranges section. + */ +public class DwarfARangesSectionImpl extends DwarfSectionImpl { + private static final int DW_AR_HEADER_SIZE = 12; + private static final int DW_AR_HEADER_PAD_SIZE = 4; // align up to 2 * address size + + public DwarfARangesSectionImpl(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public String getSectionName() { + return DW_ARANGES_SECTION_NAME; + } + + @Override + public void createContent() { + int pos = 0; + /* + * We need an entry for each compilation unit. + * + *
    + * + *
  • uint32 length ............ in bytes (not counting these 4 bytes) + * + *
  • uint16 dwarf_version ..... always 2 + * + *
  • uint32 info_offset ....... offset of compilation unit on debug_info + * + *
  • uint8 address_size ....... always 8 + * + *
  • uint8 segment_desc_size .. ??? + * + *
+ * + * That is 12 bytes followed by padding aligning up to 2 * address size. + * + *
    + * + *
  • uint8 pad[4] + * + *
+ * + * Followed by N + 1 times: + * + *
  • uint64 lo ................ lo address of range + * + *
  • uint64 length ............ number of bytes in range + * + *
+ * + * Where N is the number of ranges belonging to the compilation unit and the last range + * contains two zeroes. + */ + + for (ClassEntry classEntry : getPrimaryClasses()) { + pos += DW_AR_HEADER_SIZE; + /* + * Align to 2 * address size. + */ + pos += DW_AR_HEADER_PAD_SIZE; + pos += classEntry.getPrimaryEntries().size() * 2 * 8; + pos += 2 * 8; + } + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { + ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); + LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); + if (decisionMap != null) { + Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); + if (valueObj != null && valueObj instanceof Number) { + /* + * This may not be the final vaddr for the text segment but it will be close enough + * to make debug easier i.e. to within a 4k page or two. + */ + debugTextBase = ((Number) valueObj).longValue(); + } + } + return super.getOrDecideContent(alreadyDecided, contentHint); + } + + @Override + public void writeContent(DebugContext context) { + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context, pos); + + log(context, " [0x%08x] DEBUG_ARANGES", pos); + for (ClassEntry classEntry : getPrimaryClasses()) { + int lastpos = pos; + int length = DW_AR_HEADER_SIZE + DW_AR_HEADER_PAD_SIZE - 4; + int cuIndex = classEntry.getCUIndex(); + LinkedList classPrimaryEntries = classEntry.getPrimaryEntries(); + /* + * Add room for each entry into length count. + */ + length += classPrimaryEntries.size() * 2 * 8; + length += 2 * 8; + log(context, " [0x%08x] %s CU %d length 0x%x", pos, classEntry.getFileName(), cuIndex, length); + pos = putInt(length, buffer, pos); + /* DWARF version is always 2. */ + pos = putShort(DW_VERSION_2, buffer, pos); + pos = putInt(cuIndex, buffer, pos); + /* Address size is always 8. */ + pos = putByte((byte) 8, buffer, pos); + /* Segment size is always 0. */ + pos = putByte((byte) 0, buffer, pos); + assert (pos - lastpos) == DW_AR_HEADER_SIZE; + /* + * Align to 2 * address size. + */ + for (int i = 0; i < DW_AR_HEADER_PAD_SIZE; i++) { + pos = putByte((byte) 0, buffer, pos); + } + log(context, " [0x%08x] Address Length Name", pos); + for (PrimaryEntry classPrimaryEntry : classPrimaryEntries) { + Range primary = classPrimaryEntry.getPrimary(); + log(context, " [0x%08x] %016x %016x %s", pos, debugTextBase + primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodName()); + pos = putRelocatableCodeOffset(primary.getLo(), buffer, pos); + pos = putLong(primary.getHi() - primary.getLo(), buffer, pos); + } + pos = putLong(0, buffer, pos); + pos = putLong(0, buffer, pos); + } + + assert pos == size; + } + + /* + * The debug_aranges section content depends on debug_info section content and offset. + */ + private static final String TARGET_SECTION_NAME = DW_INFO_SECTION_NAME; + + @Override + public String targetSectionName() { + return TARGET_SECTION_NAME; + } + + private final LayoutDecision.Kind[] targetSectionKinds = { + LayoutDecision.Kind.CONTENT, + LayoutDecision.Kind.OFFSET + }; + + @Override + public LayoutDecision.Kind[] targetSectionKinds() { + return targetSectionKinds; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java new file mode 100644 index 000000000000..c8ed13ca2cd4 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.LayoutDecision; +import org.graalvm.compiler.debug.DebugContext; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ABBREV_CODE_compile_unit; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ABBREV_CODE_subprogram; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ABBREV_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_external; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_hi_pc; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_language; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_low_pc; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_name; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_null; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_AT_stmt_list; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CHILDREN_no; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CHILDREN_yes; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FORM_addr; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FORM_data1; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FORM_data4; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FORM_flag; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FORM_null; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FORM_strp; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FRAME_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_TAG_compile_unit; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_TAG_subprogram; + +/** + * Section generator for debug_abbrev section. + */ +public class DwarfAbbrevSectionImpl extends DwarfSectionImpl { + + public DwarfAbbrevSectionImpl(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public String getSectionName() { + return DW_ABBREV_SECTION_NAME; + } + + @Override + public void createContent() { + int pos = 0; + /* + * An abbrev table contains abbrev entries for one or more CUs. the table includes a + * sequence of abbrev entries each of which defines a specific DIE layout employed to + * describe some DIE in a CU. a table is terminated by a null entry. + * + * A null entry has consists of just a 0 abbrev code. + * + *
    + * + *
  • LEB128 abbrev_code; ...... == 0 + * + *
+ * + * Mon-null entries have the following format. + * + *
    + * + *
  • LEB128 abbrev_code; ......unique noncode for this layout != 0 + * + *
  • LEB128 tag; .............. defines the type of the DIE (class, subprogram, var + * etc) + * + *
  • uint8 has_chldren; ....... is the DIE followed by child DIEs or a sibling + * DIE + * + *
  • attribute_spec* .......... zero or more attributes + * + *
  • null_attribute_spec ...... terminator
+ * + * An attribute_spec consists of an attribute name and form + * + *
    + * + *
  • LEB128 attr_name; ........ 0 for the null attribute name + * + *
  • LEB128 attr_form; ........ 0 for the null attribute form + * + *
+ * + * For the moment we only use one abbrev table for all CUs. It contains two DIEs, the first + * to describe the compilation unit itself and the second to describe each method within + * that compilation unit. + * + * The DIE layouts are as follows: + * + *
  • abbrev_code == 1, tag == DW_TAG_compilation_unit, has_children + * + *
  • DW_AT_language : ... DW_FORM_data1 + * + *
  • DW_AT_name : ....... DW_FORM_strp + * + *
  • DW_AT_low_pc : ..... DW_FORM_address + * + *
  • DW_AT_hi_pc : ...... DW_FORM_address + * + *
  • DW_AT_stmt_list : .. DW_FORM_data4
+ * + *
  • abbrev_code == 2, tag == DW_TAG_subprogram, no_children + * + *
  • DW_AT_name : ....... DW_FORM_strp + * + *
  • DW_AT_hi_pc : ...... DW_FORM_addr + * + *
  • DW_AT_external : ... DW_FORM_flag + * + *
+ */ + + pos = writeAbbrev1(null, null, pos); + pos = writeAbbrev2(null, null, pos); + + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public void writeContent(DebugContext context) { + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context, pos); + + pos = writeAbbrev1(context, buffer, pos); + pos = writeAbbrev2(context, buffer, pos); + assert pos == size; + } + + private int writeAttrType(long code, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putSLEB(code, scratch, 0); + } else { + return putSLEB(code, buffer, pos); + } + } + + private int writeAttrForm(long code, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putSLEB(code, scratch, 0); + } else { + return putSLEB(code, buffer, pos); + } + } + + @SuppressWarnings("unused") + private int writeAbbrev1(DebugContext context, byte[] buffer, int p) { + int pos = p; + /* + * Abbrev 1 compile unit. + */ + pos = writeAbbrevCode(DW_ABBREV_CODE_compile_unit, buffer, pos); + pos = writeTag(DW_TAG_compile_unit, buffer, pos); + pos = writeFlag(DW_CHILDREN_yes, buffer, pos); + pos = writeAttrType(DW_AT_language, buffer, pos); + pos = writeAttrForm(DW_FORM_data1, buffer, pos); + pos = writeAttrType(DW_AT_name, buffer, pos); + pos = writeAttrForm(DW_FORM_strp, buffer, pos); + pos = writeAttrType(DW_AT_low_pc, buffer, pos); + pos = writeAttrForm(DW_FORM_addr, buffer, pos); + pos = writeAttrType(DW_AT_hi_pc, buffer, pos); + pos = writeAttrForm(DW_FORM_addr, buffer, pos); + pos = writeAttrType(DW_AT_stmt_list, buffer, pos); + pos = writeAttrForm(DW_FORM_data4, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DW_AT_null, buffer, pos); + pos = writeAttrForm(DW_FORM_null, buffer, pos); + return pos; + } + + @SuppressWarnings("unused") + private int writeAbbrev2(DebugContext context, byte[] buffer, int p) { + int pos = p; + /* + * Abbrev 2 compile unit. + */ + pos = writeAbbrevCode(DW_ABBREV_CODE_subprogram, buffer, pos); + pos = writeTag(DW_TAG_subprogram, buffer, pos); + pos = writeFlag(DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DW_AT_name, buffer, pos); + pos = writeAttrForm(DW_FORM_strp, buffer, pos); + pos = writeAttrType(DW_AT_low_pc, buffer, pos); + pos = writeAttrForm(DW_FORM_addr, buffer, pos); + pos = writeAttrType(DW_AT_hi_pc, buffer, pos); + pos = writeAttrForm(DW_FORM_addr, buffer, pos); + pos = writeAttrType(DW_AT_external, buffer, pos); + pos = writeAttrForm(DW_FORM_flag, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DW_AT_null, buffer, pos); + pos = writeAttrForm(DW_FORM_null, buffer, pos); + return pos; + } + + /** + * The debug_abbrev section content depends on debug_frame section content and offset. + */ + private static final String TARGET_SECTION_NAME = DW_FRAME_SECTION_NAME; + + @Override + public String targetSectionName() { + return TARGET_SECTION_NAME; + } + + private final LayoutDecision.Kind[] targetSectionKinds = { + LayoutDecision.Kind.CONTENT, + LayoutDecision.Kind.OFFSET + }; + + @Override + public LayoutDecision.Kind[] targetSectionKinds() { + return targetSectionKinds; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java new file mode 100644 index 000000000000..2a6081280b4b --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import java.nio.ByteOrder; + +import com.oracle.objectfile.debugentry.DebugInfoBase; + +import com.oracle.objectfile.elf.ELFMachine; + +/** + * A class that models the debug info in an organization that facilitates generation of the required + * DWARF sections. It groups common data and behaviours for use by the various subclasses of class + * DwarfSectionImpl that take responsibility for generating content for a specific section type. + */ +public class DwarfDebugInfo extends DebugInfoBase { + + /* + * Names of the different ELF sections we create or reference in reverse dependency order. + */ + public static final String TEXT_SECTION_NAME = ".text"; + public static final String DW_STR_SECTION_NAME = ".debug_str"; + public static final String DW_LINE_SECTION_NAME = ".debug_line"; + public static final String DW_FRAME_SECTION_NAME = ".debug_frame"; + public static final String DW_ABBREV_SECTION_NAME = ".debug_abbrev"; + public static final String DW_INFO_SECTION_NAME = ".debug_info"; + public static final String DW_ARANGES_SECTION_NAME = ".debug_aranges"; + + /** + * Currently generated debug info relies on DWARF spec vesion 2. + */ + public static final short DW_VERSION_2 = 2; + + /* + * Define all the abbrev section codes we need for our DIEs. + */ + @SuppressWarnings("unused") public static final int DW_ABBREV_CODE_null = 0; + public static final int DW_ABBREV_CODE_compile_unit = 1; + public static final int DW_ABBREV_CODE_subprogram = 2; + + /* + * Define all the Dwarf tags we need for our DIEs. + */ + public static final int DW_TAG_compile_unit = 0x11; + public static final int DW_TAG_subprogram = 0x2e; + /* + * Define all the Dwarf attributes we need for our DIEs. + */ + public static final int DW_AT_null = 0x0; + public static final int DW_AT_name = 0x3; + @SuppressWarnings("unused") public static final int DW_AT_comp_dir = 0x1b; + public static final int DW_AT_stmt_list = 0x10; + public static final int DW_AT_low_pc = 0x11; + public static final int DW_AT_hi_pc = 0x12; + public static final int DW_AT_language = 0x13; + public static final int DW_AT_external = 0x3f; + @SuppressWarnings("unused") public static final int DW_AT_return_addr = 0x2a; + @SuppressWarnings("unused") public static final int DW_AT_frame_base = 0x40; + /* + * Define all the Dwarf attribute forms we need for our DIEs. + */ + public static final int DW_FORM_null = 0x0; + @SuppressWarnings("unused") private static final int DW_FORM_string = 0x8; + public static final int DW_FORM_strp = 0xe; + public static final int DW_FORM_addr = 0x1; + public static final int DW_FORM_data1 = 0x0b; + public static final int DW_FORM_data4 = 0x6; + @SuppressWarnings("unused") public static final int DW_FORM_data8 = 0x7; + @SuppressWarnings("unused") public static final int DW_FORM_block1 = 0x0a; + public static final int DW_FORM_flag = 0xc; + + /* + * Define specific attribute values for given attribute or form types. + */ + /* + * DIE header has_children attribute values. + */ + public static final byte DW_CHILDREN_no = 0; + public static final byte DW_CHILDREN_yes = 1; + /* + * DW_FORM_flag attribute values. + */ + @SuppressWarnings("unused") public static final byte DW_FLAG_false = 0; + public static final byte DW_FLAG_true = 1; + /* + * Value for DW_AT_language attribute with form DATA1. + */ + public static final byte DW_LANG_Java = 0xb; + + /* + * DW_AT_Accessibility attribute values. + * + * These are not needed until we make functions members. + */ + @SuppressWarnings("unused") public static final byte DW_ACCESS_public = 1; + @SuppressWarnings("unused") public static final byte DW_ACCESS_protected = 2; + @SuppressWarnings("unused") public static final byte DW_ACCESS_private = 3; + + /* + * Others that are not yet needed. + */ + @SuppressWarnings("unused") public static final int DW_AT_type = 0; // only present for non-void + // functions + @SuppressWarnings("unused") public static final int DW_AT_accessibility = 0; + + /* + * CIE and FDE entries. + */ + + /* Full byte/word values. */ + public static final int DW_CFA_CIE_id = -1; + @SuppressWarnings("unused") public static final int DW_CFA_FDE_id = 0; + + public static final byte DW_CFA_CIE_version = 1; + + /* Values encoded in high 2 bits. */ + public static final byte DW_CFA_advance_loc = 0x1; + public static final byte DW_CFA_offset = 0x2; + @SuppressWarnings("unused") public static final byte DW_CFA_restore = 0x3; + + /* Values encoded in low 6 bits. */ + public static final byte DW_CFA_nop = 0x0; + @SuppressWarnings("unused") public static final byte DW_CFA_set_loc1 = 0x1; + public static final byte DW_CFA_advance_loc1 = 0x2; + public static final byte DW_CFA_advance_loc2 = 0x3; + public static final byte DW_CFA_advance_loc4 = 0x4; + @SuppressWarnings("unused") public static final byte DW_CFA_offset_extended = 0x5; + @SuppressWarnings("unused") public static final byte DW_CFA_restore_extended = 0x6; + @SuppressWarnings("unused") public static final byte DW_CFA_undefined = 0x7; + @SuppressWarnings("unused") public static final byte DW_CFA_same_value = 0x8; + public static final byte DW_CFA_register = 0x9; + public static final byte DW_CFA_def_cfa = 0xc; + @SuppressWarnings("unused") public static final byte DW_CFA_def_cfa_register = 0xd; + public static final byte DW_CFA_def_cfa_offset = 0xe; + + private DwarfStrSectionImpl dwarfStrSection; + private DwarfAbbrevSectionImpl dwarfAbbrevSection; + private DwarfInfoSectionImpl dwarfInfoSection; + private DwarfARangesSectionImpl dwarfARangesSection; + private DwarfLineSectionImpl dwarfLineSection; + private DwarfFrameSectionImpl dwarfFameSection; + + public DwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) { + super(byteOrder); + dwarfStrSection = new DwarfStrSectionImpl(this); + dwarfAbbrevSection = new DwarfAbbrevSectionImpl(this); + dwarfInfoSection = new DwarfInfoSectionImpl(this); + dwarfARangesSection = new DwarfARangesSectionImpl(this); + dwarfLineSection = new DwarfLineSectionImpl(this); + if (elfMachine == ELFMachine.AArch64) { + dwarfFameSection = new DwarfFrameSectionImplAArch64(this); + } else { + dwarfFameSection = new DwarfFrameSectionImplX86_64(this); + } + } + + public DwarfStrSectionImpl getStrSectionImpl() { + return dwarfStrSection; + } + + public DwarfAbbrevSectionImpl getAbbrevSectionImpl() { + return dwarfAbbrevSection; + } + + public DwarfFrameSectionImpl getFrameSectionImpl() { + return dwarfFameSection; + } + + public DwarfInfoSectionImpl getInfoSectionImpl() { + return dwarfInfoSection; + } + + public DwarfARangesSectionImpl getARangesSectionImpl() { + return dwarfARangesSection; + } + + public DwarfLineSectionImpl getLineSectionImpl() { + return dwarfLineSection; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java new file mode 100644 index 000000000000..e9000985be4d --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.PrimaryEntry; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import org.graalvm.compiler.debug.DebugContext; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_CIE_id; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_CIE_version; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_advance_loc; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_advance_loc1; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_advance_loc2; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_advance_loc4; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_def_cfa; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_def_cfa_offset; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_nop; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_offset; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_CFA_register; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FRAME_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_LINE_SECTION_NAME; + +/** + * Section generic generator for debug_frame section. + */ +public abstract class DwarfFrameSectionImpl extends DwarfSectionImpl { + + private static final int PADDING_NOPS_ALIGNMENT = 8; + + public DwarfFrameSectionImpl(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public String getSectionName() { + return DW_FRAME_SECTION_NAME; + } + + @Override + public void createContent() { + int pos = 0; + + /* + * The frame section contains one CIE at offset 0 followed by an FIE for each method. + */ + pos = writeCIE(null, pos); + pos = writeMethodFrames(null, pos); + + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public void writeContent(DebugContext context) { + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context, pos); + + /* + * There are entries for the prologue region where the stack is being built, the method body + * region(s) where the code executes with a fixed size frame and the epilogue region(s) + * where the stack is torn down. + */ + pos = writeCIE(buffer, pos); + pos = writeMethodFrames(buffer, pos); + + if (pos != size) { + System.out.format("pos = 0x%x size = 0x%x", pos, size); + } + assert pos == size; + } + + private int writeCIE(byte[] buffer, int p) { + /* + * We only need a vanilla CIE with default fields because we have to have at least one the + * layout is: + * + *
    + * + *
  • uint32 : length ............... length of remaining fields in this CIE + * + *
  • uint32 : CIE_id ................ unique id for CIE == 0xffffff + * + *
  • uint8 : version ................ == 1 + * + *
  • uint8[] : augmentation ......... == "" so always 1 byte + * + *
  • ULEB : code_alignment_factor ... == 1 (could use 4 for Aarch64) + * + *
  • ULEB : data_alignment_factor ... == -8 + * + *
  • byte : ret_addr reg id ......... x86_64 => 16 AArch64 => 32 + * + *
  • byte[] : initial_instructions .. includes pad to 8-byte boundary + * + *
+ */ + int pos = p; + if (buffer == null) { + pos += putInt(0, scratch, 0); // don't care about length + pos += putInt(DW_CFA_CIE_id, scratch, 0); + pos += putByte(DW_CFA_CIE_version, scratch, 0); + pos += putAsciiStringBytes("", scratch, 0); + pos += putULEB(1, scratch, 0); + pos += putSLEB(-8, scratch, 0); + pos += putByte((byte) getPCIdx(), scratch, 0); + /* + * Write insns to set up empty frame. + */ + pos = writeInitialInstructions(buffer, pos); + /* + * Pad to word alignment. + */ + pos = writePaddingNops(buffer, pos); + /* + * No need to write length. + */ + return pos; + } else { + int lengthPos = pos; + pos = putInt(0, buffer, pos); + pos = putInt(DW_CFA_CIE_id, buffer, pos); + pos = putByte(DW_CFA_CIE_version, buffer, pos); + pos = putAsciiStringBytes("", buffer, pos); + pos = putULEB(1, buffer, pos); + pos = putSLEB(-8, buffer, pos); + pos = putByte((byte) getPCIdx(), buffer, pos); + /* + * write insns to set up empty frame + */ + pos = writeInitialInstructions(buffer, pos); + /* + * Pad to word alignment. + */ + pos = writePaddingNops(buffer, pos); + patchLength(lengthPos, buffer, pos); + return pos; + } + } + + private int writeMethodFrames(byte[] buffer, int p) { + int pos = p; + for (ClassEntry classEntry : getPrimaryClasses()) { + for (PrimaryEntry primaryEntry : classEntry.getPrimaryEntries()) { + long lo = primaryEntry.getPrimary().getLo(); + long hi = primaryEntry.getPrimary().getHi(); + int frameSize = primaryEntry.getFrameSize(); + int currentOffset = 0; + int lengthPos = pos; + pos = writeFDEHeader((int) lo, (int) hi, buffer, pos); + for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : primaryEntry.getFrameSizeInfos()) { + int advance = debugFrameSizeInfo.getOffset() - currentOffset; + currentOffset += advance; + pos = writeAdvanceLoc(advance, buffer, pos); + if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) { + /* + * SP has been extended so rebase CFA using full frame. + */ + pos = writeDefCFAOffset(frameSize, buffer, pos); + } else { + /* + * SP has been contracted so rebase CFA using empty frame. + */ + pos = writeDefCFAOffset(8, buffer, pos); + } + } + pos = writePaddingNops(buffer, pos); + patchLength(lengthPos, buffer, pos); + } + } + return pos; + } + + private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) { + /* + * We only need a vanilla FDE header with default fields the layout is: + * + *
    + * + *
  • uint32 : length ............ length of remaining fields in this FDE + * + *
  • uint32 : CIE_offset ........ always 0 i.e. identifies our only CIE + * header + * + *
  • uint64 : initial_location .. i.e. method lo address + * + *
  • uint64 : address_range ..... i.e. method hi - lo + * + *
  • byte[] : instructions ...... includes pad to 8-byte boundary + * + *
+ */ + + int pos = p; + if (buffer == null) { + /* Dummy length. */ + pos += putInt(0, scratch, 0); + /* CIE_offset */ + pos += putInt(0, scratch, 0); + /* Initial address. */ + pos += putLong(lo, scratch, 0); + /* Address range. */ + return pos + putLong(hi - lo, scratch, 0); + } else { + /* Dummy length. */ + pos = putInt(0, buffer, pos); + /* CIE_offset */ + pos = putInt(0, buffer, pos); + /* Initial address. */ + pos = putRelocatableCodeOffset(lo, buffer, pos); + /* Address range. */ + return putLong(hi - lo, buffer, pos); + } + } + + private int writePaddingNops(byte[] buffer, int p) { + int pos = p; + while ((pos & (PADDING_NOPS_ALIGNMENT - 1)) != 0) { + if (buffer == null) { + pos++; + } else { + pos = putByte(DW_CFA_nop, buffer, pos); + } + } + return pos; + } + + protected int writeDefCFA(int register, int offset, byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + pos += putByte(DW_CFA_def_cfa, scratch, 0); + pos += putSLEB(register, scratch, 0); + return pos + putULEB(offset, scratch, 0); + } else { + pos = putByte(DW_CFA_def_cfa, buffer, pos); + pos = putULEB(register, buffer, pos); + return putULEB(offset, buffer, pos); + } + } + + protected int writeDefCFAOffset(int offset, byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + pos += putByte(DW_CFA_def_cfa_offset, scratch, 0); + return pos + putULEB(offset, scratch, 0); + } else { + pos = putByte(DW_CFA_def_cfa_offset, buffer, pos); + return putULEB(offset, buffer, pos); + } + } + + protected int writeAdvanceLoc(int offset, byte[] buffer, int pos) { + if (offset <= 0x3f) { + return writeAdvanceLoc0((byte) offset, buffer, pos); + } else if (offset <= 0xff) { + return writeAdvanceLoc1((byte) offset, buffer, pos); + } else if (offset <= 0xffff) { + return writeAdvanceLoc2((short) offset, buffer, pos); + } else { + return writeAdvanceLoc4(offset, buffer, pos); + } + } + + protected int writeAdvanceLoc0(byte offset, byte[] buffer, int pos) { + byte op = advanceLoc0Op(offset); + if (buffer == null) { + return pos + putByte(op, scratch, 0); + } else { + return putByte(op, buffer, pos); + } + } + + protected int writeAdvanceLoc1(byte offset, byte[] buffer, int p) { + int pos = p; + byte op = DW_CFA_advance_loc1; + if (buffer == null) { + pos += putByte(op, scratch, 0); + return pos + putByte(offset, scratch, 0); + } else { + pos = putByte(op, buffer, pos); + return putByte(offset, buffer, pos); + } + } + + protected int writeAdvanceLoc2(short offset, byte[] buffer, int p) { + byte op = DW_CFA_advance_loc2; + int pos = p; + if (buffer == null) { + pos += putByte(op, scratch, 0); + return pos + putShort(offset, scratch, 0); + } else { + pos = putByte(op, buffer, pos); + return putShort(offset, buffer, pos); + } + } + + protected int writeAdvanceLoc4(int offset, byte[] buffer, int p) { + byte op = DW_CFA_advance_loc4; + int pos = p; + if (buffer == null) { + pos += putByte(op, scratch, 0); + return pos + putInt(offset, scratch, 0); + } else { + pos = putByte(op, buffer, pos); + return putInt(offset, buffer, pos); + } + } + + protected int writeOffset(int register, int offset, byte[] buffer, int p) { + byte op = offsetOp(register); + int pos = p; + if (buffer == null) { + pos += putByte(op, scratch, 0); + return pos + putULEB(offset, scratch, 0); + } else { + pos = putByte(op, buffer, pos); + return putULEB(offset, buffer, pos); + } + } + + protected int writeRegister(int savedReg, int savedToReg, byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + pos += putByte(DW_CFA_register, scratch, 0); + pos += putULEB(savedReg, scratch, 0); + return pos + putULEB(savedToReg, scratch, 0); + } else { + pos = putByte(DW_CFA_register, buffer, pos); + pos = putULEB(savedReg, buffer, pos); + return putULEB(savedToReg, buffer, pos); + } + } + + protected abstract int getPCIdx(); + + @SuppressWarnings("unused") + protected abstract int getSPIdx(); + + protected abstract int writeInitialInstructions(byte[] buffer, int pos); + + /** + * The debug_frame section content depends on debug_line section content and offset. + */ + private static final String TARGET_SECTION_NAME = DW_LINE_SECTION_NAME; + + @Override + public String targetSectionName() { + return TARGET_SECTION_NAME; + } + + private final LayoutDecision.Kind[] targetSectionKinds = { + LayoutDecision.Kind.CONTENT, + LayoutDecision.Kind.OFFSET + }; + + @Override + public LayoutDecision.Kind[] targetSectionKinds() { + return targetSectionKinds; + } + + private static byte offsetOp(int register) { + assert (register >> 6) == 0; + return (byte) ((DW_CFA_offset << 6) | register); + } + + private static byte advanceLoc0Op(int offset) { + assert (offset >= 0 && offset <= 0x3f); + return (byte) ((DW_CFA_advance_loc << 6) | offset); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java new file mode 100644 index 000000000000..28c3c5f9550f --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +/** + * AArch64-specific section generator for debug_frame section that knows details of AArch64 + * registers and frame layout. + */ +public class DwarfFrameSectionImplAArch64 extends DwarfFrameSectionImpl { + // public static final int DW_CFA_FP_IDX = 29; + private static final int DW_CFA_LR_IDX = 30; + private static final int DW_CFA_SP_IDX = 31; + private static final int DW_CFA_PC_IDX = 32; + + public DwarfFrameSectionImplAArch64(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public int getPCIdx() { + return DW_CFA_PC_IDX; + } + + @Override + public int getSPIdx() { + return DW_CFA_SP_IDX; + } + + @Override + public int writeInitialInstructions(byte[] buffer, int p) { + int pos = p; + /* + * Register rsp has not been updated and caller pc is in lr: + * + *
    + * + *
  • register r32 (rpc), r30 (lr) + * + *
+ */ + pos = writeRegister(DW_CFA_PC_IDX, DW_CFA_LR_IDX, buffer, pos); + return pos; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java new file mode 100644 index 000000000000..1649cbd43ea8 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +/** + * x86_64-specific section generator for debug_frame section that knows details of x86_64 registers + * and frame layout. + */ +public class DwarfFrameSectionImplX86_64 extends DwarfFrameSectionImpl { + private static final int DW_CFA_RSP_IDX = 7; + private static final int DW_CFA_RIP_IDX = 16; + + public DwarfFrameSectionImplX86_64(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public int getPCIdx() { + return DW_CFA_RIP_IDX; + } + + @Override + public int getSPIdx() { + return DW_CFA_RSP_IDX; + } + + @Override + public int writeInitialInstructions(byte[] buffer, int p) { + int pos = p; + /* + * Register rsp points at the word containing the saved rip so the frame base (cfa) is at + * rsp + 8: + * + *
    + * + *
  • def_cfa r7 (sp) offset 8 + * + *
+ */ + pos = writeDefCFA(DW_CFA_RSP_IDX, 8, buffer, pos); + /* + * Register rip is saved at offset 8 (coded as 1 which gets scaled by dataAlignment) from + * cfa + * + *
    + * + *
  • offset r16 (rip) cfa - 8 + * + *
+ */ + pos = writeOffset(DW_CFA_RIP_IDX, 1, buffer, pos); + return pos; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java new file mode 100644 index 000000000000..1221485ff464 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.PrimaryEntry; +import com.oracle.objectfile.debugentry.Range; +import org.graalvm.compiler.debug.DebugContext; + +import java.util.LinkedList; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ABBREV_CODE_compile_unit; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ABBREV_CODE_subprogram; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_ABBREV_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_FLAG_true; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_INFO_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_LANG_Java; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_VERSION_2; + +/** + * Section generator for debug_info section. + */ +public class DwarfInfoSectionImpl extends DwarfSectionImpl { + /** + * an info header section always contains a fixed number of bytes. + */ + private static final int DW_DIE_HEADER_SIZE = 11; + + public DwarfInfoSectionImpl(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public String getSectionName() { + return DW_INFO_SECTION_NAME; + } + + @Override + public void createContent() { + /* + * We need a single level 0 DIE for each compilation unit (CU). Each CU's Level 0 DIE is + * preceded by a fixed header and terminated by a null DIE: + * + *
    + * + *
  • uint32 length ......... excluding this length field + * + *
  • uint16 dwarf_version .. always 2 ?? + * + *
  • uint32 abbrev offset .. always 0 ?? + * + *
  • uint8 address_size .... always 8 + * + *
  • DIE* .................. sequence of top-level and nested child entries + * + *
  • null_DIE .............. == 0 + * + *
+ * + * A DIE is a recursively defined structure. it starts with a code for the associated abbrev + * entry followed by a series of attribute values, as determined by the entry, terminated by + * a null value and followed by zero or more child DIEs (zero iff has_children == + * no_children). + * + *
    + * + *
  • LEB128 abbrev_code != 0 .. non-zero value indexes tag + attr layout of + * DIE + * + *
  • attribute_value* ......... value sequence as determined by abbrev entry + * + *
  • DIE* ..................... sequence of child DIEs (if appropriate) + *
  • + * + *
  • null_value ............... == 0 + * + *
+ * + * Note that a null_DIE looks like: + * + *
    + * + *
  • LEB128 abbrev_code ....... == 0 + * + *
+ * + * i.e. it also looks like a null_value. + */ + + byte[] buffer = null; + int pos = 0; + + for (ClassEntry classEntry : getPrimaryClasses()) { + int lengthPos = pos; + pos = writeCUHeader(buffer, pos); + assert pos == lengthPos + DW_DIE_HEADER_SIZE; + pos = writeCU(null, classEntry, buffer, pos); + /* + * No need to backpatch length at lengthPos. + */ + } + buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public void writeContent(DebugContext context) { + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context, pos); + + log(context, " [0x%08x] DEBUG_INFO", pos); + log(context, " [0x%08x] size = 0x%08x", pos, size); + for (ClassEntry classEntry : getPrimaryClasses()) { + /* + * Save the offset of this file's CU so it can be used when writing the aranges section. + */ + classEntry.setCUIndex(pos); + int lengthPos = pos; + pos = writeCUHeader(buffer, pos); + log(context, " [0x%08x] Compilation Unit", pos, size); + assert pos == lengthPos + DW_DIE_HEADER_SIZE; + pos = writeCU(context, classEntry, buffer, pos); + /* + * Backpatch length at lengthPos (excluding length field). + */ + patchLength(lengthPos, buffer, pos); + } + assert pos == size; + } + + private int writeCUHeader(byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + /* CU length. */ + pos += putInt(0, scratch, 0); + /* DWARF version. */ + pos += putShort(DW_VERSION_2, scratch, 0); + /* Abbrev offset. */ + pos += putInt(0, scratch, 0); + /* Address size. */ + return pos + putByte((byte) 8, scratch, 0); + } else { + /* CU length. */ + pos = putInt(0, buffer, pos); + /* DWARF version. */ + pos = putShort(DW_VERSION_2, buffer, pos); + /* Abbrev offset. */ + pos = putInt(0, buffer, pos); + /* Address size. */ + return putByte((byte) 8, buffer, pos); + } + } + + private int writeCU(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + LinkedList classPrimaryEntries = classEntry.getPrimaryEntries(); + log(context, " [0x%08x] <0> Abbrev Number %d", pos, DW_ABBREV_CODE_compile_unit); + pos = writeAbbrevCode(DW_ABBREV_CODE_compile_unit, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrData1(DW_LANG_Java, buffer, pos); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(classEntry.getFileName()), classEntry.getFileName()); + pos = writeAttrStrp(classEntry.getFileName(), buffer, pos); + log(context, " [0x%08x] lo_pc 0x%08x", pos, classPrimaryEntries.getFirst().getPrimary().getLo()); + pos = writeAttrAddress(classPrimaryEntries.getFirst().getPrimary().getLo(), buffer, pos); + log(context, " [0x%08x] hi_pc 0x%08x", pos, classPrimaryEntries.getLast().getPrimary().getHi()); + pos = writeAttrAddress(classPrimaryEntries.getLast().getPrimary().getHi(), buffer, pos); + log(context, " [0x%08x] stmt_list 0x%08x", pos, classEntry.getLineIndex()); + pos = writeAttrData4(classEntry.getLineIndex(), buffer, pos); + for (PrimaryEntry primaryEntry : classPrimaryEntries) { + pos = writePrimary(context, primaryEntry, buffer, pos); + } + /* + * Write a terminating null attribute for the the level 2 primaries. + */ + return writeAttrNull(buffer, pos); + + } + + private int writePrimary(DebugContext context, PrimaryEntry primaryEntry, byte[] buffer, int p) { + int pos = p; + Range primary = primaryEntry.getPrimary(); + verboseLog(context, " [0x%08x] <1> Abbrev Number %d", pos, DW_ABBREV_CODE_subprogram); + pos = writeAbbrevCode(DW_ABBREV_CODE_subprogram, buffer, pos); + verboseLog(context, " [0x%08x] name 0x%X (%s)", pos, debugStringIndex(primary.getFullMethodName()), primary.getFullMethodName()); + pos = writeAttrStrp(primary.getFullMethodName(), buffer, pos); + verboseLog(context, " [0x%08x] lo_pc 0x%08x", pos, primary.getLo()); + pos = writeAttrAddress(primary.getLo(), buffer, pos); + verboseLog(context, " [0x%08x] hi_pc 0x%08x", pos, primary.getHi()); + pos = writeAttrAddress(primary.getHi(), buffer, pos); + /* + * Need to pass true only if method is public. + */ + verboseLog(context, " [0x%08x] external true", pos); + return writeFlag(DW_FLAG_true, buffer, pos); + } + + private int writeAttrStrp(String value, byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + return pos + putInt(0, scratch, 0); + } else { + int idx = debugStringIndex(value); + return putInt(idx, buffer, pos); + } + } + + @SuppressWarnings("unused") + public int writeAttrString(String value, byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + return pos + value.length() + 1; + } else { + return putAsciiStringBytes(value, buffer, pos); + } + } + + /** + * The debug_info section content depends on abbrev section content and offset. + */ + private static final String TARGET_SECTION_NAME = DW_ABBREV_SECTION_NAME; + + @Override + public String targetSectionName() { + return TARGET_SECTION_NAME; + } + + private final LayoutDecision.Kind[] targetSectionKinds = { + LayoutDecision.Kind.CONTENT, + LayoutDecision.Kind.OFFSET + }; + + @Override + public LayoutDecision.Kind[] targetSectionKinds() { + return targetSectionKinds; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java new file mode 100644 index 000000000000..15f97e9df7cd --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java @@ -0,0 +1,904 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.LayoutDecisionMap; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.DirEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.PrimaryEntry; +import com.oracle.objectfile.debugentry.Range; +import org.graalvm.compiler.debug.DebugContext; + +import java.util.Map; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_LINE_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_STR_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_VERSION_2; + +/** + * Section generator for debug_line section. + */ +public class DwarfLineSectionImpl extends DwarfSectionImpl { + /** + * Line header section always contains fixed number of bytes. + */ + private static final int DW_LN_HEADER_SIZE = 27; + /** + * Current generator follows C++ with line base -5. + */ + private static final int DW_LN_LINE_BASE = -5; + /** + * Current generator follows C++ with line range 14 giving full range -5 to 8. + */ + private static final int DW_LN_LINE_RANGE = 14; + /** + * Current generator uses opcode base of 13 which must equal DW_LNS_define_file + 1. + */ + private static final int DW_LN_OPCODE_BASE = 13; + + /* + * Standard opcodes defined by Dwarf 2 + */ + /* + * 0 can be returned to indicate an invalid opcode. + */ + private static final byte DW_LNS_undefined = 0; + /* + * 0 can be inserted as a prefix for extended opcodes. + */ + private static final byte DW_LNS_extended_prefix = 0; + /* + * Append current state as matrix row 0 args. + */ + private static final byte DW_LNS_copy = 1; + /* + * Increment address 1 uleb arg. + */ + private static final byte DW_LNS_advance_pc = 2; + /* + * Increment line 1 sleb arg. + */ + private static final byte DW_LNS_advance_line = 3; + /* + * Set file 1 uleb arg. + */ + private static final byte DW_LNS_set_file = 4; + /* + * sSet column 1 uleb arg. + */ + private static final byte DW_LNS_set_column = 5; + /* + * Flip is_stmt 0 args. + */ + private static final byte DW_LNS_negate_stmt = 6; + /* + * Set end sequence and copy row 0 args. + */ + private static final byte DW_LNS_set_basic_block = 7; + /* + * Increment address as per opcode 255 0 args. + */ + private static final byte DW_LNS_const_add_pc = 8; + /* + * Increment address 1 ushort arg. + */ + private static final byte DW_LNS_fixed_advance_pc = 9; + + /* + * Extended opcodes defined by DWARF 2. + */ + /* + * There is no extended opcode 0. + */ + @SuppressWarnings("unused") private static final byte DW_LNE_undefined = 0; + /* + * End sequence of addresses. + */ + private static final byte DW_LNE_end_sequence = 1; + /* + * Set address as explicit long argument. + */ + private static final byte DW_LNE_set_address = 2; + /* + * Set file as explicit string argument. + */ + private static final byte DW_LNE_define_file = 3; + + DwarfLineSectionImpl(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public String getSectionName() { + return DW_LINE_SECTION_NAME; + } + + @Override + public void createContent() { + /* + * We need to create a header, dir table, file table and line number table encoding for each + * CU. + */ + + /* + * Write entries for each file listed in the primary list. + */ + int pos = 0; + for (ClassEntry classEntry : getPrimaryClasses()) { + if (classEntry.getFileName().length() != 0) { + int startPos = pos; + classEntry.setLineIndex(startPos); + int headerSize = headerSize(); + int dirTableSize = computeDirTableSize(classEntry); + int fileTableSize = computeFileTableSize(classEntry); + int prologueSize = headerSize + dirTableSize + fileTableSize; + classEntry.setLinePrologueSize(prologueSize); + int lineNumberTableSize = computeLineNUmberTableSize(classEntry); + int totalSize = prologueSize + lineNumberTableSize; + classEntry.setTotalSize(totalSize); + pos += totalSize; + } + } + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + private static int headerSize() { + /* + * Header size is standard 31 bytes: + * + *
    + * + *
  • uint32 total_length + * + *
  • uint16 version + * + *
  • uint32 prologue_length + * + *
  • uint8 min_insn_length + * + *
  • uint8 default_is_stmt + * + *
  • int8 line_base + * + *
  • uint8 line_range + * + *
  • uint8 opcode_base + * + *
  • uint8 li_opcode_base + * + *
  • uint8[opcode_base-1] standard_opcode_lengths + * + *
+ */ + + return DW_LN_HEADER_SIZE; + } + + private static int computeDirTableSize(ClassEntry classEntry) { + /* + * Table contains a sequence of 'nul'-terminated dir name bytes followed by an extra 'nul' + * and then a sequence of 'nul'-terminated file name bytes followed by an extra 'nul'. + * + * For now we assume dir and file names are ASCII byte strings. + */ + int dirSize = 0; + for (DirEntry dir : classEntry.getLocalDirs()) { + dirSize += dir.getPathString().length() + 1; + } + /* + * Allow for separator nul. + */ + dirSize++; + return dirSize; + } + + private int computeFileTableSize(ClassEntry classEntry) { + /* + * Table contains a sequence of 'nul'-terminated dir name bytes followed by an extra 'nul' + * and then a sequence of 'nul'-terminated file name bytes followed by an extra 'nul'. + * + * For now we assume dir and file names are ASCII byte strings. + */ + int fileSize = 0; + for (FileEntry localEntry : classEntry.getLocalFiles()) { + /* + * We want the file base name excluding path. + */ + String baseName = localEntry.getFileName(); + int length = baseName.length(); + /* We should never have a null or zero length entry in local files. */ + assert length > 0; + fileSize += length + 1; + DirEntry dirEntry = localEntry.getDirEntry(); + int idx = classEntry.localDirsIdx(dirEntry); + fileSize += putULEB(idx, scratch, 0); + /* + * The two zero timestamps require 1 byte each. + */ + fileSize += 2; + } + /* + * Allow for terminator nul. + */ + fileSize++; + return fileSize; + } + + private int computeLineNUmberTableSize(ClassEntry classEntry) { + /* + * Sigh -- we have to do this by generating the content even though we cannot write it into + * a byte[]. + */ + return writeLineNumberTable(null, classEntry, null, 0); + } + + @Override + public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { + ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); + LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); + if (decisionMap != null) { + Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); + if (valueObj != null && valueObj instanceof Number) { + /* + * This may not be the final vaddr for the text segment but it will be close enough + * to make debug easier i.e. to within a 4k page or two. + */ + debugTextBase = ((Number) valueObj).longValue(); + } + } + return super.getOrDecideContent(alreadyDecided, contentHint); + } + + @Override + public void writeContent(DebugContext context) { + byte[] buffer = getContent(); + + int pos = 0; + enableLog(context, pos); + log(context, " [0x%08x] DEBUG_LINE", pos); + + for (ClassEntry classEntry : getPrimaryClasses()) { + if (classEntry.getFileName().length() != 0) { + int startPos = pos; + assert classEntry.getLineIndex() == startPos; + log(context, " [0x%08x] Compile Unit for %s", pos, classEntry.getFileName()); + pos = writeHeader(classEntry, buffer, pos); + log(context, " [0x%08x] headerSize = 0x%08x", pos, pos - startPos); + int dirTablePos = pos; + pos = writeDirTable(context, classEntry, buffer, pos); + log(context, " [0x%08x] dirTableSize = 0x%08x", pos, pos - dirTablePos); + int fileTablePos = pos; + pos = writeFileTable(context, classEntry, buffer, pos); + log(context, " [0x%08x] fileTableSize = 0x%08x", pos, pos - fileTablePos); + int lineNumberTablePos = pos; + pos = writeLineNumberTable(context, classEntry, buffer, pos); + log(context, " [0x%08x] lineNumberTableSize = 0x%x", pos, pos - lineNumberTablePos); + log(context, " [0x%08x] size = 0x%x", pos, pos - startPos); + } + } + assert pos == buffer.length; + } + + private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + /* + * 4 ubyte length field. + */ + pos = putInt(classEntry.getTotalSize() - 4, buffer, pos); + /* + * 2 ubyte version is always 2. + */ + pos = putShort(DW_VERSION_2, buffer, pos); + /* + * 4 ubyte prologue length includes rest of header and dir + file table section. + */ + int prologueSize = classEntry.getLinePrologueSize() - 6; + pos = putInt(prologueSize, buffer, pos); + /* + * 1 ubyte min instruction length is always 1. + */ + pos = putByte((byte) 1, buffer, pos); + /* + * 1 byte default is_stmt is always 1. + */ + pos = putByte((byte) 1, buffer, pos); + /* + * 1 byte line base is always -5. + */ + pos = putByte((byte) DW_LN_LINE_BASE, buffer, pos); + /* + * 1 ubyte line range is always 14 giving range -5 to 8. + */ + pos = putByte((byte) DW_LN_LINE_RANGE, buffer, pos); + /* + * 1 ubyte opcode base is always 13. + */ + pos = putByte((byte) DW_LN_OPCODE_BASE, buffer, pos); + /* + * specify opcode arg sizes for the standard opcodes. + */ + /* DW_LNS_copy */ + putByte((byte) 0, buffer, pos); + /* DW_LNS_advance_pc */ + putByte((byte) 1, buffer, pos + 1); + /* DW_LNS_advance_line */ + putByte((byte) 1, buffer, pos + 2); + /* DW_LNS_set_file */ + putByte((byte) 1, buffer, pos + 3); + /* DW_LNS_set_column */ + putByte((byte) 1, buffer, pos + 4); + /* DW_LNS_negate_stmt */ + putByte((byte) 0, buffer, pos + 5); + /* DW_LNS_set_basic_block */ + putByte((byte) 0, buffer, pos + 6); + /* DW_LNS_const_add_pc */ + putByte((byte) 0, buffer, pos + 7); + /* DW_LNS_fixed_advance_pc */ + putByte((byte) 1, buffer, pos + 8); + /* DW_LNS_end_sequence */ + putByte((byte) 0, buffer, pos + 9); + /* DW_LNS_set_address */ + putByte((byte) 0, buffer, pos + 10); + /* DW_LNS_define_file */ + pos = putByte((byte) 1, buffer, pos + 11); + return pos; + } + + private int writeDirTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + verboseLog(context, " [0x%08x] Dir Name", pos); + /* + * Write out the list of dirs referenced form this file entry. + */ + int dirIdx = 1; + for (DirEntry dir : classEntry.getLocalDirs()) { + /* + * write nul terminated string text. + */ + verboseLog(context, " [0x%08x] %-4d %s", pos, dirIdx, dir.getPath()); + pos = putAsciiStringBytes(dir.getPathString(), buffer, pos); + dirIdx++; + } + /* + * Separate dirs from files with a nul. + */ + pos = putByte((byte) 0, buffer, pos); + return pos; + } + + private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + int fileIdx = 1; + verboseLog(context, " [0x%08x] Entry Dir Name", pos); + for (FileEntry localEntry : classEntry.getLocalFiles()) { + /* + * We need the file name minus path, the associated dir index, and 0 for time stamps. + */ + String baseName = localEntry.getFileName(); + DirEntry dirEntry = localEntry.getDirEntry(); + int dirIdx = classEntry.localDirsIdx(dirEntry); + verboseLog(context, " [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName); + pos = putAsciiStringBytes(baseName, buffer, pos); + pos = putULEB(dirIdx, buffer, pos); + pos = putULEB(0, buffer, pos); + pos = putULEB(0, buffer, pos); + fileIdx++; + } + /* + * Terminate files with a nul. + */ + pos = putByte((byte) 0, buffer, pos); + return pos; + } + + private int debugLine = 1; + private int debugCopyCount = 0; + + private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + FileEntry fileEntry = classEntry.getFileEntry(); + if (fileEntry == null) { + return pos; + } + /* + * The primary file entry should always be first in the local files list. + */ + assert classEntry.localFilesIdx(fileEntry) == 1; + String primaryClassName = classEntry.getClassName(); + String primaryFileName = classEntry.getFileName(); + String file = primaryFileName; + int fileIdx = 1; + log(context, " [0x%08x] primary class %s", pos, primaryClassName); + log(context, " [0x%08x] primary file %s", pos, primaryFileName); + for (PrimaryEntry primaryEntry : classEntry.getPrimaryEntries()) { + Range primaryRange = primaryEntry.getPrimary(); + assert primaryRange.getFileName().equals(primaryFileName); + /* + * Each primary represents a method i.e. a contiguous sequence of subranges. we assume + * the default state at the start of each sequence because we always post an + * end_sequence when we finish all the subranges in the method. + */ + long line = primaryRange.getLine(); + if (line < 0 && primaryEntry.getSubranges().size() > 0) { + line = primaryEntry.getSubranges().get(0).getLine(); + } + if (line < 0) { + line = 0; + } + long address = primaryRange.getLo(); + + /* + * Set state for primary. + */ + log(context, " [0x%08x] primary range [0x%08x, 0x%08x] %s:%d", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(), primaryRange.getFullMethodName(), + primaryRange.getLine()); + + /* + * Initialize and write a row for the start of the primary method. + */ + pos = writeSetFileOp(context, file, fileIdx, buffer, pos); + pos = writeSetBasicBlockOp(context, buffer, pos); + /* + * Address is currently 0. + */ + pos = writeSetAddressOp(context, address, buffer, pos); + /* + * State machine value of line is currently 1 increment to desired line. + */ + if (line != 1) { + pos = writeAdvanceLineOp(context, line - 1, buffer, pos); + } + pos = writeCopyOp(context, buffer, pos); + + /* + * Now write a row for each subrange lo and hi. + */ + for (Range subrange : primaryEntry.getSubranges()) { + assert subrange.getLo() >= primaryRange.getLo(); + assert subrange.getHi() <= primaryRange.getHi(); + FileEntry subFileEntry = primaryEntry.getSubrangeFileEntry(subrange); + if (subFileEntry == null) { + continue; + } + String subfile = subFileEntry.getFileName(); + int subFileIdx = classEntry.localFilesIdx(subFileEntry); + long subLine = subrange.getLine(); + long subAddressLo = subrange.getLo(); + long subAddressHi = subrange.getHi(); + log(context, " [0x%08x] sub range [0x%08x, 0x%08x] %s:%d", pos, debugTextBase + subAddressLo, debugTextBase + subAddressHi, subrange.getFullMethodName(), subLine); + if (subLine < 0) { + /* + * No line info so stay at previous file:line. + */ + subLine = line; + subfile = file; + subFileIdx = fileIdx; + verboseLog(context, " [0x%08x] missing line info - staying put at %s:%d", pos, file, line); + } + /* + * There is a temptation to append end sequence at here when the hiAddress lies + * strictly between the current address and the start of the next subrange because, + * ostensibly, we have void space between the end of the current subrange and the + * start of the next one. however, debug works better if we treat all the insns up + * to the next range start as belonging to the current line. + * + * If we have to update to a new file then do so. + */ + if (subFileIdx != fileIdx) { + /* + * Update the current file. + */ + pos = writeSetFileOp(context, subfile, subFileIdx, buffer, pos); + file = subfile; + fileIdx = subFileIdx; + } + /* + * Check if we can advance line and/or address in one byte with a special opcode. + */ + long lineDelta = subLine - line; + long addressDelta = subAddressLo - address; + byte opcode = isSpecialOpcode(addressDelta, lineDelta); + if (opcode != DW_LNS_undefined) { + /* + * Ignore pointless write when addressDelta == lineDelta == 0. + */ + if (addressDelta != 0 || lineDelta != 0) { + pos = writeSpecialOpcode(context, opcode, buffer, pos); + } + } else { + /* + * Does it help to divide and conquer using a fixed address increment. + */ + int remainder = isConstAddPC(addressDelta); + if (remainder > 0) { + pos = writeConstAddPCOp(context, buffer, pos); + /* + * The remaining address can be handled with a special opcode but what about + * the line delta. + */ + opcode = isSpecialOpcode(remainder, lineDelta); + if (opcode != DW_LNS_undefined) { + /* + * Address remainder and line now fit. + */ + pos = writeSpecialOpcode(context, opcode, buffer, pos); + } else { + /* + * Ok, bump the line separately then use a special opcode for the + * address remainder. + */ + opcode = isSpecialOpcode(remainder, 0); + assert opcode != DW_LNS_undefined; + pos = writeAdvanceLineOp(context, lineDelta, buffer, pos); + pos = writeSpecialOpcode(context, opcode, buffer, pos); + } + } else { + /* + * Increment line and pc separately. + */ + if (lineDelta != 0) { + pos = writeAdvanceLineOp(context, lineDelta, buffer, pos); + } + /* + * n.b. we might just have had an out of range line increment with a zero + * address increment. + */ + if (addressDelta > 0) { + /* + * See if we can use a ushort for the increment. + */ + if (isFixedAdvancePC(addressDelta)) { + pos = writeFixedAdvancePCOp(context, (short) addressDelta, buffer, pos); + } else { + pos = writeAdvancePCOp(context, addressDelta, buffer, pos); + } + } + pos = writeCopyOp(context, buffer, pos); + } + } + /* + * Move line and address range on. + */ + line += lineDelta; + address += addressDelta; + } + /* + * Append a final end sequence just below the next primary range. + */ + if (address < primaryRange.getHi()) { + long addressDelta = primaryRange.getHi() - address; + /* + * Increment address before we write the end sequence. + */ + pos = writeAdvancePCOp(context, addressDelta, buffer, pos); + } + pos = writeEndSequenceOp(context, buffer, pos); + } + log(context, " [0x%08x] primary file processed %s", pos, primaryFileName); + + return pos; + } + + private int writeCopyOp(DebugContext context, byte[] buffer, int p) { + byte opcode = DW_LNS_copy; + int pos = p; + if (buffer == null) { + return pos + putByte(opcode, scratch, 0); + } else { + debugCopyCount++; + verboseLog(context, " [0x%08x] Copy %d", pos, debugCopyCount); + return putByte(opcode, buffer, pos); + } + } + + private int writeAdvancePCOp(DebugContext context, long uleb, byte[] buffer, int p) { + byte opcode = DW_LNS_advance_pc; + int pos = p; + if (buffer == null) { + pos = pos + putByte(opcode, scratch, 0); + return pos + putULEB(uleb, scratch, 0); + } else { + debugAddress += uleb; + verboseLog(context, " [0x%08x] Advance PC by %d to 0x%08x", pos, uleb, debugAddress); + pos = putByte(opcode, buffer, pos); + return putULEB(uleb, buffer, pos); + } + } + + private int writeAdvanceLineOp(DebugContext context, long sleb, byte[] buffer, int p) { + byte opcode = DW_LNS_advance_line; + int pos = p; + if (buffer == null) { + pos = pos + putByte(opcode, scratch, 0); + return pos + putSLEB(sleb, scratch, 0); + } else { + debugLine += sleb; + verboseLog(context, " [0x%08x] Advance Line by %d to %d", pos, sleb, debugLine); + pos = putByte(opcode, buffer, pos); + return putSLEB(sleb, buffer, pos); + } + } + + private int writeSetFileOp(DebugContext context, String file, long uleb, byte[] buffer, int p) { + byte opcode = DW_LNS_set_file; + int pos = p; + if (buffer == null) { + pos = pos + putByte(opcode, scratch, 0); + return pos + putULEB(uleb, scratch, 0); + } else { + verboseLog(context, " [0x%08x] Set File Name to entry %d in the File Name Table (%s)", pos, uleb, file); + pos = putByte(opcode, buffer, pos); + return putULEB(uleb, buffer, pos); + } + } + + @SuppressWarnings("unused") + private int writeSetColumnOp(DebugContext context, long uleb, byte[] buffer, int p) { + byte opcode = DW_LNS_set_column; + int pos = p; + if (buffer == null) { + pos = pos + putByte(opcode, scratch, 0); + return pos + putULEB(uleb, scratch, 0); + } else { + pos = putByte(opcode, buffer, pos); + return putULEB(uleb, buffer, pos); + } + } + + @SuppressWarnings("unused") + private int writeNegateStmtOp(DebugContext context, byte[] buffer, int p) { + byte opcode = DW_LNS_negate_stmt; + int pos = p; + if (buffer == null) { + return pos + putByte(opcode, scratch, 0); + } else { + return putByte(opcode, buffer, pos); + } + } + + private int writeSetBasicBlockOp(DebugContext context, byte[] buffer, int p) { + byte opcode = DW_LNS_set_basic_block; + int pos = p; + if (buffer == null) { + return pos + putByte(opcode, scratch, 0); + } else { + verboseLog(context, " [0x%08x] Set basic block", pos); + return putByte(opcode, buffer, pos); + } + } + + private int writeConstAddPCOp(DebugContext context, byte[] buffer, int p) { + byte opcode = DW_LNS_const_add_pc; + int pos = p; + if (buffer == null) { + return pos + putByte(opcode, scratch, 0); + } else { + int advance = opcodeAddress((byte) 255); + debugAddress += advance; + verboseLog(context, " [0x%08x] Advance PC by constant %d to 0x%08x", pos, advance, debugAddress); + return putByte(opcode, buffer, pos); + } + } + + private int writeFixedAdvancePCOp(DebugContext context, short arg, byte[] buffer, int p) { + byte opcode = DW_LNS_fixed_advance_pc; + int pos = p; + if (buffer == null) { + pos = pos + putByte(opcode, scratch, 0); + return pos + putShort(arg, scratch, 0); + } else { + debugAddress += arg; + verboseLog(context, " [0x%08x] Fixed advance Address by %d to 0x%08x", pos, arg, debugAddress); + pos = putByte(opcode, buffer, pos); + return putShort(arg, buffer, pos); + } + } + + private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) { + byte opcode = DW_LNE_end_sequence; + int pos = p; + if (buffer == null) { + pos = pos + putByte(DW_LNS_extended_prefix, scratch, 0); + /* + * Insert extended insn byte count as ULEB. + */ + pos = pos + putULEB(1, scratch, 0); + return pos + putByte(opcode, scratch, 0); + } else { + verboseLog(context, " [0x%08x] Extended opcode 1: End sequence", pos); + debugAddress = debugTextBase; + debugLine = 1; + debugCopyCount = 0; + pos = putByte(DW_LNS_extended_prefix, buffer, pos); + /* + * Insert extended insn byte count as ULEB. + */ + pos = putULEB(1, buffer, pos); + return putByte(opcode, buffer, pos); + } + } + + private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int p) { + byte opcode = DW_LNE_set_address; + int pos = p; + if (buffer == null) { + pos = pos + putByte(DW_LNS_extended_prefix, scratch, 0); + /* + * Insert extended insn byte count as ULEB. + */ + pos = pos + putULEB(9, scratch, 0); + pos = pos + putByte(opcode, scratch, 0); + return pos + putLong(arg, scratch, 0); + } else { + debugAddress = debugTextBase + (int) arg; + verboseLog(context, " [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, debugAddress); + pos = putByte(DW_LNS_extended_prefix, buffer, pos); + /* + * Insert extended insn byte count as ULEB. + */ + pos = putULEB(9, buffer, pos); + pos = putByte(opcode, buffer, pos); + return putRelocatableCodeOffset(arg, buffer, pos); + } + } + + @SuppressWarnings("unused") + private int writeDefineFileOp(DebugContext context, String file, long uleb1, long uleb2, long uleb3, byte[] buffer, int p) { + byte opcode = DW_LNE_define_file; + int pos = p; + /* + * Calculate bytes needed for opcode + args. + */ + int fileBytes = file.length() + 1; + long insnBytes = 1; + insnBytes += fileBytes; + insnBytes += putULEB(uleb1, scratch, 0); + insnBytes += putULEB(uleb2, scratch, 0); + insnBytes += putULEB(uleb3, scratch, 0); + if (buffer == null) { + pos = pos + putByte(DW_LNS_extended_prefix, scratch, 0); + /* + * Write insnBytes as a ULEB. + */ + pos += putULEB(insnBytes, scratch, 0); + return pos + (int) insnBytes; + } else { + verboseLog(context, " [0x%08x] Extended opcode 3: Define File %s idx %d ts1 %d ts2 %d", pos, file, uleb1, uleb2, uleb3); + pos = putByte(DW_LNS_extended_prefix, buffer, pos); + /* + * Insert insn length as uleb. + */ + pos = putULEB(insnBytes, buffer, pos); + /* + * Insert opcode and args. + */ + pos = putByte(opcode, buffer, pos); + pos = putAsciiStringBytes(file, buffer, pos); + pos = putULEB(uleb1, buffer, pos); + pos = putULEB(uleb2, buffer, pos); + return putULEB(uleb3, buffer, pos); + } + } + + private static int opcodeId(byte opcode) { + int iopcode = opcode & 0xff; + return iopcode - DW_LN_OPCODE_BASE; + } + + private static int opcodeAddress(byte opcode) { + int iopcode = opcode & 0xff; + return (iopcode - DW_LN_OPCODE_BASE) / DW_LN_LINE_RANGE; + } + + private static int opcodeLine(byte opcode) { + int iopcode = opcode & 0xff; + return ((iopcode - DW_LN_OPCODE_BASE) % DW_LN_LINE_RANGE) + DW_LN_LINE_BASE; + } + + private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer, int p) { + int pos = p; + if (buffer == null) { + return pos + putByte(opcode, scratch, 0); + } else { + if (debug && opcode == 0) { + verboseLog(context, " [0x%08x] ERROR Special Opcode %d: Address 0x%08x Line %d", debugAddress, debugLine); + } + debugAddress += opcodeAddress(opcode); + debugLine += opcodeLine(opcode); + verboseLog(context, " [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d", + pos, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine); + return putByte(opcode, buffer, pos); + } + } + + private static final int MAX_ADDRESS_ONLY_DELTA = (0xff - DW_LN_OPCODE_BASE) / DW_LN_LINE_RANGE; + private static final int MAX_ADDPC_DELTA = MAX_ADDRESS_ONLY_DELTA + (MAX_ADDRESS_ONLY_DELTA - 1); + + private static byte isSpecialOpcode(long addressDelta, long lineDelta) { + if (addressDelta < 0) { + return DW_LNS_undefined; + } + if (lineDelta >= DW_LN_LINE_BASE) { + long offsetLineDelta = lineDelta - DW_LN_LINE_BASE; + if (offsetLineDelta < DW_LN_LINE_RANGE) { + /* + * The line delta can be encoded. Check if address is ok. + */ + if (addressDelta <= MAX_ADDRESS_ONLY_DELTA) { + long opcode = DW_LN_OPCODE_BASE + (addressDelta * DW_LN_LINE_RANGE) + offsetLineDelta; + if (opcode <= 255) { + return (byte) opcode; + } + } + } + } + + /* + * Answer no by returning an invalid opcode. + */ + return DW_LNS_undefined; + } + + private static int isConstAddPC(long addressDelta) { + if (addressDelta < MAX_ADDRESS_ONLY_DELTA) { + return 0; + } + if (addressDelta <= MAX_ADDPC_DELTA) { + return (int) (addressDelta - MAX_ADDRESS_ONLY_DELTA); + } else { + return 0; + } + } + + private static boolean isFixedAdvancePC(long addressDiff) { + return addressDiff >= 0 && addressDiff < 0xffff; + } + + /** + * The debug_line section content depends on debug_str section content and offset. + */ + private static final String TARGET_SECTION_NAME = DW_STR_SECTION_NAME; + + @Override + public String targetSectionName() { + return TARGET_SECTION_NAME; + } + + private final LayoutDecision.Kind[] targetSectionKinds = { + LayoutDecision.Kind.CONTENT, + LayoutDecision.Kind.OFFSET, + }; + + @Override + public LayoutDecision.Kind[] targetSectionKinds() { + return targetSectionKinds; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java new file mode 100644 index 000000000000..fad408696d96 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.BuildDependency; +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.LayoutDecisionMap; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.elf.ELFObjectFile; +import org.graalvm.compiler.debug.DebugContext; + +import java.nio.ByteOrder; +import java.util.Map; +import java.util.Set; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.TEXT_SECTION_NAME; + +/** + * A class from which all DWARF debug sections inherit providing common behaviours. + */ +public abstract class DwarfSectionImpl extends BasicProgbitsSectionImpl { + protected DwarfDebugInfo dwarfSections; + protected boolean debug = false; + protected long debugTextBase = 0; + protected long debugAddress = 0; + protected int debugBase = 0; + + public DwarfSectionImpl(DwarfDebugInfo dwarfSections) { + this.dwarfSections = dwarfSections; + } + + /** + * Creates the target byte[] array used to define the section contents. + * + * The main task of this method is to precompute the size of the debug section. given the + * complexity of the data layouts that invariably requires performing a dummy write of the + * contents, inserting bytes into a small, scratch buffer only when absolutely necessary. + * subclasses may also cache some information for use when writing the contents. + */ + public abstract void createContent(); + + /** + * Populates the byte[] array used to contain the section contents. + * + * In most cases this task reruns the operations performed under createContent but this time + * actually writing data to the target byte[]. + */ + public abstract void writeContent(DebugContext debugContext); + + @Override + public boolean isLoadable() { + /* + * Even though we're a progbits section impl we're not actually loadable. + */ + return false; + } + + private String debugSectionLogName() { + /* + * Use prefix dwarf plus the section name (which already includes a dot separator) for the + * context key. For example messages for info section will be keyed using dwarf.debug_info. + * Other info formats use their own format-specific prefix. + */ + assert getSectionName().startsWith(".debug"); + return "dwarf" + getSectionName(); + } + + protected void enableLog(DebugContext context, int pos) { + /* + * Debug output is disabled during the first pass where we size the buffer. this is called + * to enable it during the second pass where the buffer gets written, but only if the scope + * is enabled. + */ + if (context.areScopesEnabled()) { + debug = true; + debugBase = pos; + debugAddress = debugTextBase; + } + } + + protected void log(DebugContext context, String format, Object... args) { + if (debug) { + context.logv(DebugContext.INFO_LEVEL, format, args); + } + } + + protected void verboseLog(DebugContext context, String format, Object... args) { + if (debug) { + context.logv(DebugContext.VERBOSE_LEVEL, format, args); + } + } + + protected boolean littleEndian() { + return dwarfSections.getByteOrder() == ByteOrder.LITTLE_ENDIAN; + } + + /* + * Base level put methods that assume a non-null buffer. + */ + + protected int putByte(byte b, byte[] buffer, int p) { + int pos = p; + buffer[pos++] = b; + return pos; + } + + protected int putShort(short s, byte[] buffer, int p) { + int pos = p; + if (littleEndian()) { + buffer[pos++] = (byte) (s & 0xff); + buffer[pos++] = (byte) ((s >> 8) & 0xff); + } else { + buffer[pos++] = (byte) ((s >> 8) & 0xff); + buffer[pos++] = (byte) (s & 0xff); + } + return pos; + } + + protected int putInt(int i, byte[] buffer, int p) { + int pos = p; + if (littleEndian()) { + buffer[pos++] = (byte) (i & 0xff); + buffer[pos++] = (byte) ((i >> 8) & 0xff); + buffer[pos++] = (byte) ((i >> 16) & 0xff); + buffer[pos++] = (byte) ((i >> 24) & 0xff); + } else { + buffer[pos++] = (byte) ((i >> 24) & 0xff); + buffer[pos++] = (byte) ((i >> 16) & 0xff); + buffer[pos++] = (byte) ((i >> 8) & 0xff); + buffer[pos++] = (byte) (i & 0xff); + } + return pos; + } + + protected int putLong(long l, byte[] buffer, int p) { + int pos = p; + if (littleEndian()) { + buffer[pos++] = (byte) (l & 0xff); + buffer[pos++] = (byte) ((l >> 8) & 0xff); + buffer[pos++] = (byte) ((l >> 16) & 0xff); + buffer[pos++] = (byte) ((l >> 24) & 0xff); + buffer[pos++] = (byte) ((l >> 32) & 0xff); + buffer[pos++] = (byte) ((l >> 40) & 0xff); + buffer[pos++] = (byte) ((l >> 48) & 0xff); + buffer[pos++] = (byte) ((l >> 56) & 0xff); + } else { + buffer[pos++] = (byte) ((l >> 56) & 0xff); + buffer[pos++] = (byte) ((l >> 48) & 0xff); + buffer[pos++] = (byte) ((l >> 40) & 0xff); + buffer[pos++] = (byte) ((l >> 32) & 0xff); + buffer[pos++] = (byte) ((l >> 16) & 0xff); + buffer[pos++] = (byte) ((l >> 24) & 0xff); + buffer[pos++] = (byte) ((l >> 8) & 0xff); + buffer[pos++] = (byte) (l & 0xff); + } + return pos; + } + + protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) { + int pos = p; + /* + * Mark address so it is relocated relative to the start of the text segment. + */ + markRelocationSite(pos, 8, ObjectFile.RelocationKind.DIRECT, TEXT_SECTION_NAME, false, Long.valueOf(l)); + pos = putLong(0, buffer, pos); + return pos; + } + + protected int putULEB(long val, byte[] buffer, int p) { + long l = val; + int pos = p; + for (int i = 0; i < 9; i++) { + byte b = (byte) (l & 0x7f); + l = l >>> 7; + boolean done = (l == 0); + if (!done) { + b = (byte) (b | 0x80); + } + pos = putByte(b, buffer, pos); + if (done) { + break; + } + } + return pos; + } + + protected int putSLEB(long val, byte[] buffer, int p) { + long l = val; + int pos = p; + for (int i = 0; i < 9; i++) { + byte b = (byte) (l & 0x7f); + l = l >> 7; + boolean bIsSigned = (b & 0x40) != 0; + boolean done = ((bIsSigned && l == -1) || (!bIsSigned && l == 0)); + if (!done) { + b = (byte) (b | 0x80); + } + pos = putByte(b, buffer, pos); + if (done) { + break; + } + } + return pos; + } + + protected int putAsciiStringBytes(String s, byte[] buffer, int pos) { + return putAsciiStringBytes(s, 0, buffer, pos); + } + + protected int putAsciiStringBytes(String s, int startChar, byte[] buffer, int p) { + int pos = p; + for (int l = startChar; l < s.length(); l++) { + char c = s.charAt(l); + if (c > 127) { + throw new RuntimeException("oops : expected ASCII string! " + s); + } + buffer[pos++] = (byte) c; + } + buffer[pos++] = '\0'; + return pos; + } + + /* + * Common write methods that check for a null buffer. + */ + + protected void patchLength(int lengthPos, byte[] buffer, int pos) { + if (buffer != null) { + int length = pos - (lengthPos + 4); + putInt(length, buffer, lengthPos); + } + } + + protected int writeAbbrevCode(long code, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putSLEB(code, scratch, 0); + } else { + return putSLEB(code, buffer, pos); + } + } + + protected int writeTag(long code, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putSLEB(code, scratch, 0); + } else { + return putSLEB(code, buffer, pos); + } + } + + protected int writeFlag(byte flag, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putByte(flag, scratch, 0); + } else { + return putByte(flag, buffer, pos); + } + } + + protected int writeAttrAddress(long address, byte[] buffer, int pos) { + if (buffer == null) { + return pos + 8; + } else { + return putRelocatableCodeOffset(address, buffer, pos); + } + } + + @SuppressWarnings("unused") + protected int writeAttrData8(long value, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putLong(value, scratch, 0); + } else { + return putLong(value, buffer, pos); + } + } + + protected int writeAttrData4(int value, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putInt(value, scratch, 0); + } else { + return putInt(value, buffer, pos); + } + } + + protected int writeAttrData1(byte value, byte[] buffer, int pos) { + if (buffer == null) { + return pos + putByte(value, scratch, 0); + } else { + return putByte(value, buffer, pos); + } + } + + protected int writeAttrNull(byte[] buffer, int pos) { + if (buffer == null) { + return pos + putSLEB(0, scratch, 0); + } else { + return putSLEB(0, buffer, pos); + } + } + + /** + * Identify the section after which this debug section needs to be ordered when sizing and + * creating content. + * + * @return the name of the preceding section. + */ + public abstract String targetSectionName(); + + /** + * Identify the layout properties of the target section which need to have been decided before + * the contents of this section can be created. + * + * @return an array of the relevant decision kinds. + */ + public abstract LayoutDecision.Kind[] targetSectionKinds(); + + /** + * Identify this debug section by name. + * + * @return the name of the debug section. + */ + public abstract String getSectionName(); + + @Override + public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { + /* + * Ensure content byte[] has been created before calling super method. + */ + createContent(); + + /* + * Ensure content byte[] has been written before calling super method. + * + * we do this in a nested debug scope derived from the one set up under the object file + * write + */ + getOwner().debugContext(debugSectionLogName(), this::writeContent); + + return super.getOrDecideContent(alreadyDecided, contentHint); + } + + @Override + public Set getDependencies(Map decisions) { + Set deps = super.getDependencies(decisions); + String targetName = targetSectionName(); + ELFObjectFile.ELFSection targetSection = (ELFObjectFile.ELFSection) getElement().getOwner().elementForName(targetName); + LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT); + LayoutDecision ourSize = decisions.get(getElement()).getDecision(LayoutDecision.Kind.SIZE); + LayoutDecision.Kind[] targetKinds = targetSectionKinds(); + /* + * Make our content depend on the size and content of the target. + */ + for (LayoutDecision.Kind targetKind : targetKinds) { + LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind); + deps.add(BuildDependency.createOrGet(ourContent, targetDecision)); + } + /* + * Make our size depend on our content. + */ + deps.add(BuildDependency.createOrGet(ourSize, ourContent)); + + return deps; + } + + /** + * A scratch buffer used during computation of a section's size. + */ + protected static final byte[] scratch = new byte[10]; + + protected Iterable getPrimaryClasses() { + return dwarfSections.getPrimaryClasses(); + } + + protected int debugStringIndex(String str) { + return dwarfSections.debugStringIndex(str); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java new file mode 100644 index 000000000000..ee68bcb71050 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.debugentry.StringEntry; +import org.graalvm.compiler.debug.DebugContext; + +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.DW_STR_SECTION_NAME; +import static com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.TEXT_SECTION_NAME; + +/** + * Generator for debug_str section. + */ +public class DwarfStrSectionImpl extends DwarfSectionImpl { + public DwarfStrSectionImpl(DwarfDebugInfo dwarfSections) { + super(dwarfSections); + } + + @Override + public String getSectionName() { + return DW_STR_SECTION_NAME; + } + + @Override + public void createContent() { + int pos = 0; + for (StringEntry stringEntry : dwarfSections.getStringTable()) { + if (stringEntry.isAddToStrSection()) { + stringEntry.setOffset(pos); + String string = stringEntry.getString(); + pos += string.length() + 1; + } + } + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public void writeContent(DebugContext context) { + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context, pos); + + for (StringEntry stringEntry : dwarfSections.getStringTable()) { + if (stringEntry.isAddToStrSection()) { + assert stringEntry.getOffset() == pos; + String string = stringEntry.getString(); + pos = putAsciiStringBytes(string, buffer, pos); + } + } + assert pos == size; + } + + /** + * The debug_str section content depends on text section content and offset. + */ + private static final String TARGET_SECTION_NAME = TEXT_SECTION_NAME; + + @Override + public String targetSectionName() { + return TARGET_SECTION_NAME; + } + + /** + * The debug_str section content depends on text section content and offset. + */ + private final LayoutDecision.Kind[] targetSectionKinds = { + LayoutDecision.Kind.CONTENT, + LayoutDecision.Kind.OFFSET, + /* Add this so we can use the text section base address for debug. */ + LayoutDecision.Kind.VADDR, + }; + + @Override + public LayoutDecision.Kind[] targetSectionKinds() { + return targetSectionKinds; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index a1000b3125f5..61fbd491316c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -52,6 +52,8 @@ import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.XOptions; +import static org.graalvm.compiler.core.common.GraalOptions.TrackNodeSourcePosition; + public class SubstrateOptions { @Option(help = "Class containing the default entry point method. Optional if --shared is used.", type = OptionType.User)// @@ -446,4 +448,18 @@ public static int codeAlignment() { @Option(help = "Populate reference queues in a separate thread rather than after a garbage collection.", type = OptionType.Expert) // public static final HostedOptionKey UseReferenceHandlerThread = new HostedOptionKey<>(false); + + @Option(help = "Insert debug info into the generated native image or library")// + public static final HostedOptionKey GenerateDebugInfo = new HostedOptionKey(0) { + @Override + protected void onValueUpdate(EconomicMap, Object> values, Integer oldValue, Integer newValue) { + // force update of TrackNodeSourcePosition + if (newValue > 0 && !Boolean.TRUE.equals(values.get(TrackNodeSourcePosition))) { + TrackNodeSourcePosition.update(values, true); + } + } + }; + @Option(help = "Search path for source files for Application or GraalVM classes (list of comma-separated directories or jar files)")// + public static final HostedOptionKey DebugInfoSourceSearchPath = new HostedOptionKey(null) { + }; } diff --git a/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml b/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml index 2ad8df2430cd..788e1fa0b557 100644 --- a/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml +++ b/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml @@ -208,8 +208,8 @@
- - + + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImage.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImage.java index 5f121d3ad5ee..50078e9ea84e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImage.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImage.java @@ -72,6 +72,7 @@ import com.oracle.objectfile.ObjectFile.RelocationKind; import com.oracle.objectfile.ObjectFile.Section; import com.oracle.objectfile.SectionName; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; import com.oracle.objectfile.macho.MachOObjectFile; import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.Isolates; @@ -92,6 +93,7 @@ import com.oracle.svm.core.image.ImageHeapLayouter; import com.oracle.svm.core.image.ImageHeapPartition; import com.oracle.svm.core.meta.SubstrateObjectConstant; +import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.hosted.c.CGlobalDataFeature; @@ -104,6 +106,7 @@ import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; import com.oracle.svm.hosted.image.RelocatableBuffer.Info; +import com.oracle.svm.hosted.image.sources.SourceManager; import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedUniverse; @@ -161,14 +164,16 @@ public Section getTextSection() { @Override public abstract String[] makeLaunchCommand(NativeImageKind k, String imageName, Path binPath, Path workPath, java.lang.reflect.Method method); - protected final void write(Path outputFile) { + protected final void write(DebugContext context, Path outputFile) { try { Path outFileParent = outputFile.normalize().getParent(); if (outFileParent != null) { Files.createDirectories(outFileParent); } try (FileChannel channel = FileChannel.open(outputFile, StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { - objectFile.write(channel); + objectFile.withDebugContext(context, "ObjectFile.write", () -> { + objectFile.write(channel); + }); } } catch (Exception ex) { throw shouldNotReachHere(ex); @@ -456,6 +461,14 @@ public void build(DebugContext debug, ImageHeapLayouter layouter) { cGlobals.writeData(rwDataBuffer, (offset, symbolName) -> defineDataSymbol(symbolName, rwDataSection, offset + RWDATA_CGLOBALS_PARTITION_OFFSET)); defineDataSymbol(CGlobalDataInfo.CGLOBALDATA_BASE_SYMBOL_NAME, rwDataSection, RWDATA_CGLOBALS_PARTITION_OFFSET); + /* + * If we constructed debug info give the object file a chance to install it + */ + if (SubstrateOptions.GenerateDebugInfo.getValue(HostedOptionValues.singleton()) > 0) { + ImageSingletons.add(SourceManager.class, new SourceManager()); + DebugInfoProvider provider = new NativeImageDebugInfoProvider(debug, codeCache, heap); + objectFile.installDebugInfo(provider); + } // - Write the heap, either to its own section, or to the ro and rw data sections. RelocatableBuffer heapSectionBuffer = null; ProgbitsSectionImpl heapSectionImpl = null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImageViaCC.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImageViaCC.java index 5b540ed60333..7d7aa41e57df 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImageViaCC.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeBootImageViaCC.java @@ -357,7 +357,7 @@ private static List diagnoseLinkerFailure(String linkerOutput) { public LinkerInvocation write(DebugContext debug, Path outputDirectory, Path tempDirectory, String imageName, BeforeImageWriteAccessImpl config) { try (Indent indent = debug.logAndIndent("Writing native image")) { // 1. write the relocatable file - write(tempDirectory.resolve(imageName + ObjectFile.getFilenameSuffix())); + write(debug, tempDirectory.resolve(imageName + ObjectFile.getFilenameSuffix())); if (NativeImageOptions.ExitAfterRelocatableImageWrite.getValue()) { return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java new file mode 100644 index 000000000000..24d748b54daf --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.image; + +import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT; +import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND; + +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.graalvm.compiler.code.CompilationResult; +import org.graalvm.compiler.code.SourceMapping; +import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.graph.NodeSourcePosition; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.svm.hosted.image.sources.SourceManager; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedType; + +import jdk.vm.ci.code.site.Mark; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info + * to be passed to an ObjectFile when generation of debug info is enabled. + */ +class NativeImageDebugInfoProvider implements DebugInfoProvider { + private final DebugContext debugContext; + private final NativeImageCodeCache codeCache; + @SuppressWarnings("unused") private final NativeImageHeap heap; + + NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap) { + super(); + this.debugContext = debugContext; + this.codeCache = codeCache; + this.heap = heap; + } + + @Override + public Stream typeInfoProvider() { + return Stream.empty(); + } + + @Override + public Stream codeInfoProvider() { + return codeCache.compilations.entrySet().stream().map(entry -> new NativeImageDebugCodeInfo(entry.getKey(), entry.getValue())); + } + + @Override + public Stream dataInfoProvider() { + return Stream.empty(); + } + + /** + * Implementation of the DebugCodeInfo API interface that allows code info to be passed to an + * ObjectFile when generation of debug info is enabled. + */ + private class NativeImageDebugCodeInfo implements DebugCodeInfo { + private final HostedMethod method; + private final ResolvedJavaType javaType; + private final CompilationResult compilation; + private Path fullFilePath; + + NativeImageDebugCodeInfo(HostedMethod method, CompilationResult compilation) { + this.method = method; + HostedType declaringClass = method.getDeclaringClass(); + Class clazz = declaringClass.getJavaClass(); + this.javaType = declaringClass.getWrapped(); + this.compilation = compilation; + fullFilePath = ImageSingletons.lookup(SourceManager.class).findAndCacheSource(javaType, clazz); + } + + @SuppressWarnings("try") + @Override + public void debugContext(Consumer action) { + try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", method)) { + action.accept(debugContext); + } catch (Throwable e) { + throw debugContext.handle(e); + } + } + + @Override + public String fileName() { + if (fullFilePath != null) { + Path filename = fullFilePath.getFileName(); + if (filename != null) { + return filename.toString(); + } + } + return ""; + } + + @Override + public Path filePath() { + if (fullFilePath != null) { + return fullFilePath.getParent(); + } + return null; + } + + @Override + public String className() { + return javaType.toClassName(); + } + + @Override + public String methodName() { + return method.format("%n"); + } + + @Override + public String paramNames() { + return method.format("%P"); + } + + @Override + public String returnTypeName() { + return method.format("%R"); + } + + @Override + public int addressLo() { + return method.getCodeAddressOffset(); + } + + @Override + public int addressHi() { + return method.getCodeAddressOffset() + compilation.getTargetCodeSize(); + } + + @Override + public int line() { + LineNumberTable lineNumberTable = method.getLineNumberTable(); + if (lineNumberTable != null) { + return lineNumberTable.getLineNumber(0); + } + return -1; + } + + @Override + public Stream lineInfoProvider() { + if (fileName().length() == 0) { + return Stream.empty(); + } + return compilation.getSourceMappings().stream().map(sourceMapping -> new NativeImageDebugLineInfo(sourceMapping)); + } + + @Override + public int getFrameSize() { + return compilation.getTotalFrameSize(); + } + + @Override + public List getFrameSizeChanges() { + List frameSizeChanges = new LinkedList<>(); + for (Mark mark : compilation.getMarks()) { + // we only need to observe stack increment or decrement points + if (mark.id.equals("PROLOGUE_DECD_RSP")) { + NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND); + frameSizeChanges.add(sizeChange); + // } else if (mark.id.equals("PROLOGUE_END")) { + // can ignore these + // } else if (mark.id.equals("EPILOGUE_START")) { + // can ignore these + } else if (mark.id.equals("EPILOGUE_INCD_RSP")) { + NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, CONTRACT); + frameSizeChanges.add(sizeChange); + // } else if(mark.id.equals("EPILOGUE_END")) { + } + } + return frameSizeChanges; + } + } + + /** + * Implementation of the DebugLineInfo API interface that allows line number info to be passed + * to an ObjectFile when generation of debug info is enabled. + */ + private class NativeImageDebugLineInfo implements DebugLineInfo { + private final int bci; + private final ResolvedJavaMethod method; + private final int lo; + private final int hi; + private Path fullFilePath; + + NativeImageDebugLineInfo(SourceMapping sourceMapping) { + NodeSourcePosition position = sourceMapping.getSourcePosition(); + int posbci = position.getBCI(); + this.bci = (posbci >= 0 ? posbci : 0); + this.method = position.getMethod(); + this.lo = sourceMapping.getStartOffset(); + this.hi = sourceMapping.getEndOffset(); + computeFullFilePath(); + } + + @Override + public String fileName() { + if (fullFilePath != null) { + Path fileName = fullFilePath.getFileName(); + if (fileName != null) { + return fileName.toString(); + } + } + return null; + } + + @Override + public Path filePath() { + if (fullFilePath != null) { + return fullFilePath.getParent(); + } + return null; + } + + @Override + public String className() { + return method.format("%H"); + } + + @Override + public String methodName() { + return method.format("%n"); + } + + @Override + public int addressLo() { + return lo; + } + + @Override + public int addressHi() { + return hi; + } + + @Override + public int line() { + LineNumberTable lineNumberTable = method.getLineNumberTable(); + if (lineNumberTable != null) { + return lineNumberTable.getLineNumber(bci); + } + return -1; + } + + private void computeFullFilePath() { + ResolvedJavaType declaringClass = method.getDeclaringClass(); + Class clazz = null; + if (declaringClass instanceof OriginalClassProvider) { + clazz = ((OriginalClassProvider) declaringClass).getJavaClass(); + } + /* + * HostedType and AnalysisType punt calls to getSourceFilename to the wrapped class so + * for consistency we need to do the path lookup relative to the wrapped class. + */ + if (declaringClass instanceof HostedType) { + declaringClass = ((HostedType) declaringClass).getWrapped(); + } + if (declaringClass instanceof AnalysisType) { + declaringClass = ((AnalysisType) declaringClass).getWrapped(); + } + fullFilePath = ImageSingletons.lookup(SourceManager.class).findAndCacheSource(declaringClass, clazz); + } + + } + + /** + * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change + * info to be passed to an ObjectFile when generation of debug info is enabled. + */ + private class NativeImageDebugFrameSizeChange implements DebugFrameSizeChange { + private int offset; + private Type type; + + NativeImageDebugFrameSizeChange(int offset, Type type) { + this.offset = offset; + this.type = type; + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public Type getType() { + return type; + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/ApplicationSourceCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/ApplicationSourceCache.java new file mode 100644 index 000000000000..83cda2b0b38a --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/ApplicationSourceCache.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted.image.sources; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static com.oracle.svm.hosted.image.sources.SourceCacheType.APPLICATION; + +public class ApplicationSourceCache extends SourceCache { + /** + * Create an application source cache. + */ + protected ApplicationSourceCache() { + initSrcRoots(); + } + + @Override + protected final SourceCacheType getType() { + return APPLICATION; + } + + private void initSrcRoots() { + /* Add dirs or jars found in the classpath */ + for (String classPathEntry : classPathEntries) { + tryClassPathRoot(classPathEntry); + } + for (String sourcePathEntry : sourcePathEntries) { + trySourceRoot(sourcePathEntry); + } + } + + private void tryClassPathRoot(String classPathEntry) { + trySourceRoot(classPathEntry, true); + } + + private void trySourceRoot(String sourcePathEntry) { + trySourceRoot(sourcePathEntry, false); + } + + private void trySourceRoot(String sourceRoot, boolean fromClassPath) { + try { + Path sourcePath = Paths.get(sourceRoot); + String fileNameString = sourcePath.getFileName().toString(); + if (fileNameString.endsWith(".jar") || fileNameString.endsWith(".zip")) { + if (fromClassPath && fileNameString.endsWith(".jar")) { + /* + * application jar /path/to/xxx.jar should have sources /path/to/xxx-sources.jar + */ + int length = fileNameString.length(); + fileNameString = fileNameString.substring(0, length - 4) + "-sources.zip"; + } + sourcePath = sourcePath.getParent().resolve(fileNameString); + if (sourcePath.toFile().exists()) { + try { + FileSystem fileSystem = FileSystems.newFileSystem(sourcePath, null); + for (Path root : fileSystem.getRootDirectories()) { + srcRoots.add(root); + } + } catch (IOException | FileSystemNotFoundException ioe) { + /* ignore this entry */ + } + } + } else { + if (fromClassPath) { + /* + * for dir entries ending in classes or target/classes translate to a parallel + * src tree + */ + if (sourcePath.endsWith("classes")) { + Path parent = sourcePath.getParent(); + if (parent.endsWith("target")) { + parent = parent.getParent(); + } + sourcePath = (parent.resolve("src")); + } + } + // try the path as provided + File file = sourcePath.toFile(); + if (file.exists() && file.isDirectory()) { + srcRoots.add(sourcePath); + } + } + } catch (NullPointerException npe) { + // do nothing + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/GraalVMSourceCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/GraalVMSourceCache.java new file mode 100644 index 000000000000..c583a300dbbb --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/GraalVMSourceCache.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted.image.sources; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static com.oracle.svm.hosted.image.sources.SourceCacheType.GRAALVM; +import static com.oracle.svm.hosted.image.sources.SourceManager.GRAALVM_SRC_PACKAGE_PREFIXES; + +public class GraalVMSourceCache extends SourceCache { + /** + * Create a GraalVM source cache. + */ + protected GraalVMSourceCache() { + initSrcRoots(); + } + + @Override + protected final SourceCacheType getType() { + return GRAALVM; + } + + private void initSrcRoots() { + for (String classPathEntry : classPathEntries) { + tryClassPathRoot(classPathEntry); + } + for (String sourcePathEntry : sourcePathEntries) { + trySourceRoot(sourcePathEntry); + } + } + + private void tryClassPathRoot(String classPathEntry) { + trySourceRoot(classPathEntry, true); + } + + private void trySourceRoot(String sourcePathEntry) { + trySourceRoot(sourcePathEntry, false); + } + + private void trySourceRoot(String sourceRoot, boolean fromClassPath) { + try { + Path sourcePath = Paths.get(sourceRoot); + String fileNameString = sourcePath.getFileName().toString(); + if (fileNameString.endsWith(".jar") || fileNameString.endsWith(".src.zip")) { + if (fromClassPath && fileNameString.endsWith(".jar")) { + /* + * GraalVM jar /path/to/xxx.jar in classpath should have sources + * /path/to/xxx.src.zip + */ + int length = fileNameString.length(); + fileNameString = fileNameString.substring(0, length - 3) + "src.zip"; + } + Path srcPath = sourcePath.getParent().resolve(fileNameString); + if (srcPath.toFile().exists()) { + try { + FileSystem fileSystem = FileSystems.newFileSystem(srcPath, null); + for (Path root : fileSystem.getRootDirectories()) { + if (filterSrcRoot(root)) { + srcRoots.add(root); + } + } + } catch (IOException | FileSystemNotFoundException ioe) { + /* ignore this entry */ + } + } + } else { + if (fromClassPath) { + /* graal classpath dir entries should have a src and/or src_gen subdirectory */ + Path srcPath = sourcePath.resolve("src"); + if (filterSrcRoot(srcPath)) { + srcRoots.add(srcPath); + } + srcPath = sourcePath.resolve("src_gen"); + if (filterSrcRoot(srcPath)) { + srcRoots.add(srcPath); + } + } else { + // try the path as provided + if (filterSrcRoot(sourcePath)) { + srcRoots.add(sourcePath); + } + } + } + } catch (NullPointerException npe) { + // do nothing + } + } + + /** + * Ensure that the supplied root dir contains at least one subdirectory that matches one of the + * expected Graal package dir hierarchies. + * + * @param root A root path under which to locate the desired subdirectory + * @return true if a + */ + private static boolean filterSrcRoot(Path root) { + String separator = root.getFileSystem().getSeparator(); + + /* if any of the graal paths exist accept this root */ + for (String prefix : GRAALVM_SRC_PACKAGE_PREFIXES) { + String subDir = prefix.replaceAll("\\.", separator); + if (Files.isDirectory(root.resolve(subDir))) { + return true; + } + } + + return false; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/JDKSourceCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/JDKSourceCache.java new file mode 100644 index 000000000000..4bc3bd208898 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/JDKSourceCache.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted.image.sources; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static com.oracle.svm.hosted.image.sources.SourceCacheType.JDK; + +public class JDKSourceCache extends SourceCache { + /** + * Create a JDK runtime class source cache.. + */ + protected JDKSourceCache() { + initSrcRoots(); + } + + @Override + protected final SourceCacheType getType() { + return JDK; + } + + /* + * properties needed to locate relevant JDK and app source roots + */ + private static final String JAVA_HOME_PROP = "java.home"; + private static final String JAVA_SPEC_VERSION_PROP = "java.specification.version"; + + private void initSrcRoots() { + String javaHome = System.getProperty(JAVA_HOME_PROP); + assert javaHome != null; + Path javaHomePath = Paths.get("", javaHome); + Path srcZipPath; + String javaSpecVersion = System.getProperty(JAVA_SPEC_VERSION_PROP); + if (javaSpecVersion.equals("1.8")) { + srcZipPath = javaHomePath.resolve("src.zip"); + } else { + assert javaSpecVersion.matches("[1-9][0-9]"); + srcZipPath = javaHomePath.resolve("lib").resolve("src.zip"); + } + if (srcZipPath.toFile().exists()) { + try { + FileSystem srcFileSystem = FileSystems.newFileSystem(srcZipPath, null); + for (Path root : srcFileSystem.getRootDirectories()) { + srcRoots.add(root); + } + } catch (IOException | FileSystemNotFoundException ioe) { + /* ignore this entry */ + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java new file mode 100644 index 000000000000..ffd353ed43a9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted.image.sources; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.option.OptionUtils; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import org.graalvm.nativeimage.hosted.Feature; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.List; + +/** + * An abstract cache manager for some subspace of the JDK, GraalVM or application source file space. + * This class implements core behaviours that manage a cache of source files in a specific + * subdirectory of the local sources directory. It allows source files to be located when present in + * the local cache or cached when not already present. Subclasses are responsible for providing + * behaviours that identify an original source for addition to the cache and for verifying that a + * cached file is not out of date with respect to its original. + */ + +public abstract class SourceCache { + + /** + * A list of all entries in the classpath used by the native image classloader. + */ + protected static final List classPathEntries = new ArrayList<>(); + /** + * A list of all entries in the classpath used by the native image classloader. + */ + protected static final List sourcePathEntries = new ArrayList<>(); + /** + * A list of root directories which may contain source files from which this cache can be + * populated. + */ + protected List srcRoots; + + /** + * Create some flavour of source cache. + */ + protected SourceCache() { + basePath = Paths.get(SOURCE_CACHE_ROOT_DIR).resolve(getType().getSubdir()); + srcRoots = new ArrayList<>(); + } + + /** + * Identify the specific type of this source cache. + * + * @return the source cache type + */ + protected abstract SourceCacheType getType(); + + /** + * A local directory serving as the root for all source trees maintained by the different + * available source caches. + */ + private static final String SOURCE_CACHE_ROOT_DIR = "sources"; + /** + * The top level path relative to the root directory under which files belonging to this + * specific cache are located. + */ + private final Path basePath; + + /** + * Cache the source file identified by the supplied prototype path if a legitimate candidate for + * inclusion in this cache can be identified and is not yet included in the cache or + * alternatively identify and validate any existing candidate cache entry to ensure it is not + * out of date refreshing it if need be. + * + * @param filePath a prototype path for a file to be included in the cache derived from the name + * of some associated class. + * @return a path identifying the cached file or null if the candidate cannot be found. + */ + public Path resolve(Path filePath) { + File cachedFile = findCandidate(filePath); + if (cachedFile == null) { + return tryCacheFile(filePath); + } else { + return checkCacheFile(filePath); + } + } + + /** + * Given a prototype path for a file to be resolved return a File identifying a cached candidate + * for for that Path or null if no cached candidate exists. + * + * @param filePath a prototype path for a file to be included in the cache derived from the name + * of some associated class. + * @return a File identifying a cached candidate or null. + */ + public File findCandidate(Path filePath) { + /* + * JDK source candidates are stored in the src.zip file using the path we are being asked + * for. A cached version should exist under this cache's root using that same path. + */ + File file = cachedFile(filePath); + if (file.exists()) { + return file; + } + return null; + } + + /** + * Attempt to copy a source file from one of this cache's source roots to the local sources + * directory storing it in the subdirectory that belongs to this cache. + * + * @param filePath a path appended to each of the cache's source roots in turn until an + * acceptable source file is found and copied to the local source directory. + * @return the supplied path if the file has been located and copied to the local sources + * directory or null if it was not found or the copy failed. + */ + public Path tryCacheFile(Path filePath) { + for (Path root : srcRoots) { + Path targetPath = cachedPath(filePath); + Path sourcePath = extendPath(root, filePath); + try { + if (checkSourcePath(sourcePath)) { + ensureTargetDirs(targetPath.getParent()); + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + // return the original filePath + // we don't want the sources/jdk prefix to go into the debuginfo + return filePath; + } + } catch (IOException e) { + } + } + return null; + } + + /** + * Check whether the copy of a given source file in the local source cache is up to date with + * respect to any original located in this cache's and if not copy the original to the + * subdirectory that belongs to this cache. + * + * @param filePath a path appended to each of the cache's source roots in turn until an matching + * original source is found for comparison against the local source directory. + * @return the supplied path if the file is up to date or if an updated version has been copied + * to the local sources directory or null if was not found or the copy failed. + */ + public Path checkCacheFile(Path filePath) { + Path targetPath = cachedPath(filePath); + for (Path root : srcRoots) { + Path sourcePath = extendPath(root, filePath); + try { + if (checkSourcePath(sourcePath)) { + FileTime sourceTime = Files.getLastModifiedTime(sourcePath); + FileTime destTime = Files.getLastModifiedTime(targetPath); + if (destTime.compareTo(sourceTime) < 0) { + try { + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + } catch (IOException e) { + /* delete the target file as it is invalid */ + targetPath.toFile().delete(); + return null; + } + } + return filePath; + } + } catch (IOException e) { + /* delete the target file as it is invalid */ + targetPath.toFile().delete(); + /* have another go at caching it */ + return tryCacheFile(filePath); + } + } + /* delete the target file as it is invalid */ + targetPath.toFile().delete(); + + return null; + } + + /** + * Create and intialize the source cache used to locate and cache sources of a given type as + * determined by the supplied key. + * + * @param type an enum identifying both the type of Java sources cached by the returned cache + * and the subdir of the cached source subdirectory in which those sources are + * located. + * @return the desired source cache. + */ + public static SourceCache createSourceCache(SourceCacheType type) { + SourceCache sourceCache = null; + switch (type) { + case JDK: + sourceCache = new JDKSourceCache(); + break; + case GRAALVM: + sourceCache = new GraalVMSourceCache(); + break; + case APPLICATION: + sourceCache = new ApplicationSourceCache(); + break; + default: + assert false; + } + return sourceCache; + } + + /** + * Extend a root path form one file system using a path potentially derived from another file + * system by converting he latter to a text string and replacing the file separator if + * necessary. + * + * @param root the path to be extended + * @param filePath the subpath to extend it with + * @return the extended path + */ + protected Path extendPath(Path root, Path filePath) { + String filePathString = filePath.toString(); + String fileSeparator = filePath.getFileSystem().getSeparator(); + String newSeparator = root.getFileSystem().getSeparator(); + if (!fileSeparator.equals(newSeparator)) { + filePathString = filePathString.replace(fileSeparator, newSeparator); + } + return root.resolve(filePathString); + } + + /** + * Convert a potential resolved candidate path to the corresponding local Path in this cache. + * + * @param candidate a resolved candidate path for some given resolution request + * @return the corresponding local Path + */ + protected Path cachedPath(Path candidate) { + return basePath.resolve(candidate); + } + + /** + * Convert a potential resolved candidate path to the corresponding local File in this cache. + * + * @param candidate a resolved candidate path for some given resolution request + * @return the corresponding local File + */ + protected File cachedFile(Path candidate) { + return cachedPath(candidate).toFile(); + } + + /** + * Indicate whether a source path identifies a file in the associated file system. + * + * @param sourcePath the path to check + * @return true if the path identifies a file or false if no such file can be found. + */ + private static boolean checkSourcePath(Path sourcePath) { + return Files.isRegularFile(sourcePath); + } + + /** + * Ensure the directory hierarchy for a path exists creating any missing directories if needed. + * + * @param targetDir a path to the desired directory + */ + private static void ensureTargetDirs(Path targetDir) { + if (targetDir != null) { + File targetFile = targetDir.toFile(); + if (!targetFile.exists()) { + targetDir.toFile().mkdirs(); + } + } + } + + /** + * Add a path to the list of classpath entries. + * + * @param path The path to add. + */ + private static void addClassPathEntry(String path) { + classPathEntries.add(path); + } + + /** + * Add a path to the list of source path entries. + * + * @param path The path to add. + */ + private static void addSourcePathEntry(String path) { + sourcePathEntries.add(path); + } + + /** + * An automatic feature class which acquires the image loader class path via the afterAnalysis + * callback. + */ + @AutomaticFeature + @SuppressWarnings("unused") + public static class SourceCacheFeature implements Feature { + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + FeatureImpl.AfterAnalysisAccessImpl accessImpl = (FeatureImpl.AfterAnalysisAccessImpl) access; + ImageClassLoader loader = accessImpl.getImageClassLoader(); + for (String entry : loader.getClasspath()) { + addClassPathEntry(entry); + } + // also add any necessary source path entries + if (SubstrateOptions.DebugInfoSourceSearchPath.getValue() != null) { + for (String searchPathEntry : OptionUtils.flatten(",", SubstrateOptions.DebugInfoSourceSearchPath.getValue())) { + addSourcePathEntry(searchPathEntry); + } + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCacheType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCacheType.java new file mode 100644 index 000000000000..5e37d3883141 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCacheType.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted.image.sources; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public enum SourceCacheType { + JDK("jdk"), + GRAALVM("graal"), + APPLICATION("src"); + + final Path subdir; + + SourceCacheType(String subdir) { + this.subdir = Paths.get(subdir); + } + + public Path getSubdir() { + return subdir; + } + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java new file mode 100644 index 000000000000..9cdb738fd6f5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted.image.sources; + +import com.oracle.svm.util.ModuleSupport; +import jdk.vm.ci.meta.ResolvedJavaType; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; + +/** + * A singleton class responsible for locating source files for classes included in a native image + * and copying them into the local sources. + */ +public class SourceManager { + /** + * Find and cache a source file for a give Java class and return a Path to the file relative to + * the source. + * + * @param resolvedType the Java type whose source file should be located and cached + * @param clazz the Java class associated with the resolved type + * @return a path identifying the location of a successfully cached file for inclusion in the + * generated debug info or null if a source file cannot be found or cached. + */ + public Path findAndCacheSource(ResolvedJavaType resolvedType, Class clazz) { + /* short circuit if we have already seen this type */ + Path path = verifiedPaths.get(resolvedType); + if (path != null) { + return (path != INVALID_PATH ? path : null); + } + + String fileName = computeBaseName(resolvedType); + /* + * null for the name means this class will not have a source so we skip on that + */ + if (fileName != null) { + /* + * we can only provide sources for known classes and interfaces + */ + if (resolvedType.isInstanceClass() || resolvedType.isInterface()) { + String packageName = computePackageName(resolvedType); + SourceCacheType sourceCacheType = sourceCacheType(packageName); + path = locateSource(fileName, packageName, sourceCacheType, clazz); + if (path == null) { + // as a last ditch effort derive path from the Java class name + if (packageName.length() > 0) { + path = Paths.get("", packageName.split("\\.")); + path = path.resolve(fileName); + } + } + } + } + /* memoize the lookup */ + verifiedPaths.put(resolvedType, (path != null ? path : INVALID_PATH)); + + return path; + } + + /** + * Construct the base file name for a resolved Java class excluding path elements using either + * the source name embedded in the class file or the class name itself. + * + * @param resolvedType the resolved java type whose source file name is required + * @return the file name or null if it the class cannot be associated with a source file + */ + private static String computeBaseName(ResolvedJavaType resolvedType) { + String fileName = resolvedType.getSourceFileName(); + if (fileName == null) { + /* ok, try to construct it from the class name */ + fileName = resolvedType.toJavaName(); + int idx = fileName.lastIndexOf('.'); + if (idx > 0) { + // strip off package prefix + fileName = fileName.substring(idx + 1); + } + idx = fileName.indexOf('$'); + if (idx == 0) { + // name is $XXX so cannot associate with a file + // + fileName = null; + } else { + if (idx > 0) { + // name is XXX$YYY so use outer class to derive file name + fileName = fileName.substring(0, idx); + } + fileName = fileName + ".java"; + } + } + return fileName; + } + + /** + * Construct the package name for a Java type or the empty String if it has no package. + * + * @param javaType the Java type whose package name is required + * @return the package name or the empty String if it has no package + */ + private static String computePackageName(ResolvedJavaType javaType) { + String name = javaType.toClassName(); + int idx = name.lastIndexOf('.'); + if (idx > 0) { + return name.substring(0, idx); + } else { + return ""; + } + } + + /** + * Construct the prototype name for a Java source file which can be used to resolve and cache an + * actual source file. + * + * @param fileName the base file name for the source file + * @param packageName the name of the package for the associated Java class + * @param sourceCacheType the sourceCacheType of cache in which to lookup or cache this class's + * source file + * @param clazz the class associated with the sourceCacheType used to identify the module prefix + * for JDK classes + * @return a protoype name for the source file + */ + private static Path computePrototypeName(String fileName, String packageName, SourceCacheType sourceCacheType, Class clazz) { + String prefix = ""; + if (sourceCacheType == SourceCacheType.JDK && clazz != null) { + /* JDK11+ paths will require the module name as prefix */ + String moduleName = ModuleSupport.getModuleName(clazz); + if (moduleName != null) { + prefix = moduleName; + } + } + if (packageName.length() == 0) { + return Paths.get("", fileName); + } else { + return Paths.get(prefix, packageName.split("\\.")).resolve(fileName); + } + } + + /** + * A whitelist of packages prefixes used to pre-filter JDK runtime class lookups. + */ + public static final String[] JDK_SRC_PACKAGE_PREFIXES = { + "java.", + "jdk.", + "javax.", + "sun.", + "com.sun.", + "org.ietf.", + "org.jcp.", + "org.omg.", + "org.w3c.", + "org.xml", + }; + /** + * A whitelist of packages prefixes used to pre-filter GraalVM class lookups. + */ + public static final String[] GRAALVM_SRC_PACKAGE_PREFIXES = { + "com.oracle.graal.", + "com.oracle.objectfile.", + "com.oracle.svm.", + "com.oracle.truffle.", + "org.graalvm.", + }; + + /** + * Check a package name against a whitelist of acceptable packages. + * + * @param packageName the package name of the class to be checked + * @param whitelist a list of prefixes one of which may form the initial prefix of the package + * name being checked + * @return true if the package name matches an entry in the whitelist otherwise false + */ + private static boolean whiteListPackage(String packageName, String[] whitelist) { + for (String prefix : whitelist) { + if (packageName.startsWith(prefix)) { + return true; + } + } + return false; + } + + /** + * Identify which type of source cache should be used to locate a given class's source code as + * determined by it's package name. + * + * @param packageName the package name of the class. + * @return the corresponding source cache type + */ + private static SourceCacheType sourceCacheType(String packageName) { + if (whiteListPackage(packageName, JDK_SRC_PACKAGE_PREFIXES)) { + return SourceCacheType.JDK; + } + if (whiteListPackage(packageName, GRAALVM_SRC_PACKAGE_PREFIXES)) { + return SourceCacheType.GRAALVM; + } + return SourceCacheType.APPLICATION; + } + + /** + * A map from each of the top level root keys to a cache that knows how to handle lookup and + * caching of the associated type of source file. + */ + private static HashMap caches = new HashMap<>(); + + /** + * A map from a Java type to an associated source paths which is known to have an up to date + * entry in the relevant source file cache. This is used to memoize previous lookups. + */ + private static HashMap verifiedPaths = new HashMap<>(); + + /** + * An invalid path used as a marker to track failed lookups so we don't waste time looking up + * the source again. Note that all legitimate paths will end with a ".java" suffix. + */ + private static final Path INVALID_PATH = Paths.get("invalid"); + + /** + * Retrieve the source cache used to locate and cache sources of a given type as determined by + * the supplied key, creating and initializing it if it does not already exist. + * + * @param type an enum identifying the type of Java sources cached by the returned cache. + * @return the desired source cache. + */ + private static SourceCache getOrCreateCache(SourceCacheType type) { + SourceCache sourceCache = caches.get(type); + if (sourceCache == null) { + sourceCache = SourceCache.createSourceCache(type); + caches.put(type, sourceCache); + } + return sourceCache; + } + + private static Path locateSource(String fileName, String packagename, SourceCacheType cacheType, Class clazz) { + SourceCache cache = getOrCreateCache(cacheType); + Path prototypeName = computePrototypeName(fileName, packagename, cacheType, clazz); + if (prototypeName != null) { + return cache.resolve(prototypeName); + } else { + return null; + } + } +}