diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index 7776780d4dda..3b1e011018fc 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -132,9 +132,9 @@ def test(): wildcard_pattern = '.*' # disable prompting to continue output execute("set pagination off") - # set a break point at hello.Hello::main + # set a break point at hello.Hello.main # expect "Breakpoint 1 at 0x[0-9a-f]+: file hello.Hello.java, line 67." - exec_string = execute("break hello.Hello::main") + exec_string = execute("break hello.Hello.main") rexp = r"Breakpoint 1 at %s: file hello/Hello\.java, line 67\."%address_pattern checker = Checker('break main', rexp) checker.check(exec_string) @@ -149,36 +149,36 @@ def test(): checker.check(exec_string, skip_fails=False) # run a backtrace - # expect "#0 hello.Hello::main(java.lang.String[]).* at hello.Hello.java:67" - # expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub::JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" + # expect "#0 hello.Hello.main(java.lang.String[]).* at hello.Hello.java:67" + # expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") - checker = Checker("backtrace hello.Hello::main", - [r"#0%shello\.Hello::main\(java\.lang\.String\[\]\)%s at hello/Hello\.java:67"%(spaces_pattern, wildcard_pattern), - r"#1%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, wildcard_pattern, package_pattern) - ]) + checker = Checker("backtrace hello.Hello.main", + [r"#0%shello\.Hello\.main\(java\.lang\.String\[\]\)%s at hello/Hello\.java:67"%(spaces_pattern, wildcard_pattern), + r"#1%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub\.JavaMainWrapper_run_%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, wildcard_pattern, package_pattern) + ]) checker.check(exec_string, skip_fails=False) - # look up PrintStream::println methods - # expect "All functions matching regular expression "java.io.PrintStream::println":" + # look up PrintStream.println methods + # expect "All functions matching regular expression "java.io.PrintStream.println":" # expect "" # expect "File java.base/java/io/PrintStream.java:" - # expect " void java.io.PrintStream::println(java.lang.Object)(void);" - # expect " void java.io.PrintStream::println(java.lang.String)(void);" - exec_string = execute("info func java.io.PrintStream::println") -# checker = Checker("info func java.io.PrintStream::println", -# ["All functions matching regular expression \"java\\.io\\.PrintStream::println\":", -# "", -# "File .*java/io/PrintStream.java:", -# "[ \t]*void java.io.PrintStream::println\\(java\\.lang\\.Object\\)\\(void\\);", -# "[ \t]*void java.io.PrintStream::println\\(java\\.lang\\.String\\)\\(void\\);", -# ]) - checker = Checker("info func java.io.PrintStream::println", - r"%svoid java.io.PrintStream::println\(java\.lang\.String\)"%maybe_spaces_pattern) + # expect " void java.io.PrintStream.println(java.lang.Object)(void);" + # expect " void java.io.PrintStream.println(java.lang.String)(void);" + exec_string = execute("info func java.io.PrintStream.println") + # checker = Checker("info func java.io.PrintStream.println", + # ["All functions matching regular expression \"java\\.io\\.PrintStream\.println\":", + # "", + # "File .*java/io/PrintStream.java:", + # "[ \t]*void java.io.PrintStream\.println\\(java\\.lang\\.Object\\)\\(void\\);", + # "[ \t]*void java.io.PrintStream\.println\\(java\\.lang\\.String\\)\\(void\\);", + # ]) + checker = Checker("info func java.io.PrintStream.println", + r"%svoid java.io.PrintStream\.println\(java\.lang\.String\)"%maybe_spaces_pattern) checker.check(exec_string) - # set a break point at PrintStream::println(String) + # set a break point at PrintStream.println(String) # expect "Breakpoint 2 at 0x[0-9a-f]+: java.base/java/io/PrintStream.java, line [0-9]+." - exec_string = execute("break java.io.PrintStream::println(java.lang.String)") + exec_string = execute("break java.io.PrintStream.println(java.lang.String)") rexp = r"Breakpoint 2 at %s: file .*java/io/PrintStream\.java, line %s\."%(address_pattern, digits_pattern) checker = Checker('break println', rexp) checker.check(exec_string, skip_fails=False) @@ -190,28 +190,28 @@ def test(): # expect "34 if (args.length == 0) {" exec_string = execute("list") rexp = r"34%sif \(args\.length == 0\) {"%spaces_pattern - checker = Checker('list hello.Hello.Greeter::greeter', rexp) + checker = Checker('list hello.Hello.Greeter.greeter', rexp) checker.check(exec_string, skip_fails=False) # run a backtrace - # expect "#0 hello.Hello.greeter::greeter(java.lang.String[]).* at hello.Hello.java:34" - # expect "#1 0x[0-9a-f]+ in hello.Hello::main(java.lang.String[]).* at hello.Hello.java:67" - # expect "#2 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub::JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" + # expect "#0 hello.Hello.greeter.greeter(java.lang.String[]).* at hello.Hello.java:34" + # expect "#1 0x[0-9a-f]+ in hello.Hello.main(java.lang.String[]).* at hello.Hello.java:67" + # expect "#2 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") - checker = Checker("backtrace hello.Hello.Greeter::greeter", - [r"#0%shello\.Hello\.Greeter::greeter\(java\.lang\.String\[\]\)%s at hello/Hello\.java:34"%(spaces_pattern, wildcard_pattern), - r"#1%s%s in hello\.Hello::main\(java\.lang\.String\[\]\)%s at hello/Hello\.java:67"%(spaces_pattern, address_pattern, wildcard_pattern), - r"#2%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s at [a-z/]+/JavaMainWrapper\.java:%s"%(spaces_pattern, address_pattern, wildcard_pattern, digits_pattern) - ]) + checker = Checker("backtrace hello.Hello.Greeter.greeter", + [r"#0%shello\.Hello\.Greeter\.greeter\(java\.lang\.String\[\]\)%s at hello/Hello\.java:34"%(spaces_pattern, wildcard_pattern), + r"#1%s%s in hello\.Hello\.main\(java\.lang\.String\[\]\)%s at hello/Hello\.java:67"%(spaces_pattern, address_pattern, wildcard_pattern), + r"#2%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub\.JavaMainWrapper_run_%s at [a-z/]+/JavaMainWrapper\.java:%s"%(spaces_pattern, address_pattern, wildcard_pattern, digits_pattern) + ]) checker.check(exec_string, skip_fails=False) # now step into inlined code execute("next") - # check we are still in hello.Hello.Greeter::greeter but no longer in hello.Hello.java + # check we are still in hello.Hello.Greeter.greeter but no longer in hello.Hello.java exec_string = execute("backtrace 1") checker = Checker("backtrace inline", - [r"#0%shello\.Hello\.Greeter::greeter\(java\.lang\.String\[\]\)%s at (%s):%s"%(spaces_pattern, wildcard_pattern, package_file_pattern, digits_pattern)]) + [r"#0%shello\.Hello\.Greeter\.greeter\(java\.lang\.String\[\]\)%s at (%s):%s"%(spaces_pattern, wildcard_pattern, package_file_pattern, digits_pattern)]) matches = checker.check(exec_string, skip_fails=False) # n.b. can only get back here with one match match = matches[0] @@ -224,11 +224,11 @@ def test(): # continue to next breakpoint execute("continue") - # run backtrace to check we are in java.io.PrintStream::println(java.lang.String) - # expect "#0 java.io.PrintStream::println(java.lang.String).* at java.base/java/io/PrintStream.java:[0-9]+" + # run backtrace to check we are in java.io.PrintStream.println(java.lang.String) + # expect "#0 java.io.PrintStream.println(java.lang.String).* at java.base/java/io/PrintStream.java:[0-9]+" exec_string = execute("backtrace 1") - checker = Checker("backtrace 1 PrintStream::println", - [r"#0%sjava\.io\.PrintStream::println\(java\.lang\.String\)%s at %sjava/io/PrintStream.java:%s"%(spaces_pattern, wildcard_pattern, wildcard_pattern, digits_pattern)]) + checker = Checker("backtrace 1 PrintStream.println", + [r"#0%sjava\.io\.PrintStream\.println\(java\.lang\.String\)%s at %sjava/io/PrintStream.java:%s"%(spaces_pattern, wildcard_pattern, wildcard_pattern, digits_pattern)]) checker.check(exec_string, skip_fails=False) # list current line 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 ab00cc8836e3..4e78d473f74d 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 @@ -252,6 +252,16 @@ public enum RelocationKind { DIRECT_2, DIRECT_4, DIRECT_8, + /** + * The index of the object file section containing the relocation's symbol supplies the + * fixup bytes. (used in CodeView debug information) + */ + SECTION_2, + /** + * The address of the object file section containing the relocation's symbol (plus addend) + * supplies the fixup bytes. (used in CodeView debug information) + */ + SECREL_4, /** * The relocation's symbol provides an address whose PC-relative value (plus addend) * supplies the fixup bytes. @@ -325,9 +335,11 @@ public static int getRelocationSize(RelocationKind kind) { return 1; case DIRECT_2: case PC_RELATIVE_2: + case SECTION_2: return 2; case DIRECT_4: case PC_RELATIVE_4: + case SECREL_4: return 4; case DIRECT_8: case PC_RELATIVE_8: 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 index 8b88fd46f718..8cca4480b555 100644 --- 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 @@ -133,6 +133,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { // switch '$' in class names for '.' String className = debugCodeInfo.className().replaceAll("\\$", "."); String methodName = debugCodeInfo.methodName(); + String symbolName = debugCodeInfo.symbolNameForMethod(); String paramNames = debugCodeInfo.paramNames(); String returnTypeName = debugCodeInfo.returnTypeName(); int lo = debugCodeInfo.addressLo(); @@ -140,7 +141,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { int primaryLine = debugCodeInfo.line(); boolean isDeoptTarget = debugCodeInfo.isDeoptTarget(); - Range primaryRange = new Range(fileName, filePath, cachePath, className, methodName, paramNames, returnTypeName, stringTable, lo, hi, primaryLine, isDeoptTarget); + Range primaryRange = new Range(fileName, filePath, cachePath, className, methodName, symbolName, paramNames, returnTypeName, stringTable, lo, hi, primaryLine, isDeoptTarget); 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 -> { @@ -149,6 +150,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { // Switch '$' in class names for '.' String classNameAtLine = debugLineInfo.className().replaceAll("\\$", "."); String methodNameAtLine = debugLineInfo.methodName(); + String symbolNameAtLine = debugLineInfo.symbolNameForMethod(); int loAtLine = lo + debugLineInfo.addressLo(); int hiAtLine = lo + debugLineInfo.addressHi(); int line = debugLineInfo.line(); @@ -157,7 +159,8 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { * 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, cachePathAtLine, classNameAtLine, methodNameAtLine, "", "", stringTable, loAtLine, hiAtLine, line, primaryRange); + Range subRange = new Range(fileNameAtLine, filePathAtLine, cachePathAtLine, classNameAtLine, methodNameAtLine, symbolNameAtLine, "", "", 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); 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 index f9c5333d8a73..f81eadc6a30c 100644 --- 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 @@ -52,7 +52,7 @@ public String getPathName() { } public String getFullName() { - return getDirEntry().getPath().resolve(getFileName()).toString(); + return getDirEntry() != null ? getDirEntry().getPath().resolve(getFileName()).toString() : getFileName(); } /** @@ -68,4 +68,14 @@ public DirEntry getDirEntry() { public String getCachePath() { return cachePath; } + + @Override + public String toString() { + if (getDirEntry() == null) { + return getFileName() == null ? "-" : getFileName(); + } else if (getFileName() == null) { + return "--"; + } + return String.format("FileEntry(%s)", getFullName()); + } } 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 index 635cb9a6ea14..d8166f2ddfad 100644 --- 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 @@ -36,11 +36,15 @@ */ public class Range { + + private static final String CLASS_DELIMITER = "."; + private final String cachePath; private String fileName; private Path filePath; private String className; private String methodName; + private String symbolName; private String paramNames; private String returnTypeName; private String fullMethodName; @@ -56,24 +60,24 @@ public class Range { /* * Create a primary range. */ - public Range(String fileName, Path filePath, Path cachePath, String className, String methodName, String paramNames, String returnTypeName, StringTable stringTable, int lo, int hi, int line, - boolean isDeoptTarget) { - this(fileName, filePath, cachePath, className, methodName, paramNames, returnTypeName, stringTable, lo, hi, line, isDeoptTarget, null); + public Range(String fileName, Path filePath, Path cachePath, String className, String methodName, String symbolName, String paramNames, String returnTypeName, StringTable stringTable, int lo, + int hi, int line, boolean isDeoptTarget) { + this(fileName, filePath, cachePath, className, methodName, symbolName, paramNames, returnTypeName, stringTable, lo, hi, line, isDeoptTarget, null); } /* * Create a secondary range. */ - public Range(String fileName, Path filePath, Path cachePath, String className, String methodName, String paramNames, String returnTypeName, StringTable stringTable, int lo, int hi, int line, - Range primary) { - this(fileName, filePath, cachePath, className, methodName, paramNames, returnTypeName, stringTable, lo, hi, line, false, primary); + public Range(String fileName, Path filePath, Path cachePath, String className, String methodName, String symbolName, String paramNames, String returnTypeName, StringTable stringTable, int lo, + int hi, int line, Range primary) { + this(fileName, filePath, cachePath, className, methodName, symbolName, paramNames, returnTypeName, stringTable, lo, hi, line, false, primary); } /* * Create a primary or secondary range. */ - private Range(String fileName, Path filePath, Path cachePath, String className, String methodName, String paramNames, String returnTypeName, StringTable stringTable, int lo, int hi, int line, - boolean isDeoptTarget, Range primary) { + private Range(String fileName, Path filePath, Path cachePath, String className, String methodName, String symbolName, String paramNames, String returnTypeName, StringTable stringTable, int lo, + int hi, int line, boolean isDeoptTarget, 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. @@ -83,6 +87,7 @@ private Range(String fileName, Path filePath, Path cachePath, String className, this.cachePath = (cachePath == null ? "" : stringTable.uniqueDebugString(cachePath.toString())); this.className = stringTable.uniqueString(className); this.methodName = stringTable.uniqueString(methodName); + this.symbolName = stringTable.uniqueString(symbolName); this.paramNames = stringTable.uniqueString(paramNames); this.returnTypeName = stringTable.uniqueString(returnTypeName); this.fullMethodName = stringTable.uniqueDebugString(constructClassAndMethodNameWithParams()); @@ -131,6 +136,10 @@ public String getMethodName() { return methodName; } + public String getSymbolName() { + return symbolName; + } + public int getHi() { return hi; } @@ -151,6 +160,14 @@ public boolean isDeoptTarget() { return isDeoptTarget; } + public String getParamNames() { + return paramNames; + } + + public String getClassAndMethodName() { + return getExtendedMethodName(false, false); + } + private String getExtendedMethodName(boolean includeParams, boolean includeReturnType) { StringBuilder builder = new StringBuilder(); if (includeReturnType && returnTypeName.length() > 0) { @@ -159,7 +176,7 @@ private String getExtendedMethodName(boolean includeParams, boolean includeRetur } if (className != null) { builder.append(className); - builder.append("::"); + builder.append(CLASS_DELIMITER); } builder.append(methodName); if (includeParams && !paramNames.isEmpty()) { @@ -180,4 +197,9 @@ private String constructClassAndMethodNameWithParams() { public String getCachePath() { return cachePath; } + + @Override + public String toString() { + return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", lo, hi, constructClassAndMethodNameWithParams(), getFileAsPath(), line); + } } 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 index 4222180f40d2..c2516f0e4027 100644 --- 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 @@ -46,7 +46,7 @@ public StringTable() { * 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 */ @@ -58,7 +58,7 @@ public String uniqueString(String string) { * 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 @@ -82,7 +82,7 @@ private String ensureString(String string, boolean addToStrSection) { /** * 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 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 index 4f46cbd63602..89da1bc263ad 100644 --- 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 @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2020, Red Hat Inc. 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 @@ -77,6 +77,11 @@ interface DebugCodeInfo { */ String methodName(); + /** + * @return the symbolNameForMethod string + */ + String symbolNameForMethod(); + /** * @return the lowest address containing code generated for the method represented as an * offset into the code segment. @@ -165,6 +170,11 @@ interface DebugLineInfo { */ String methodName(); + /** + * @return the symbolNameForMethod string + */ + String symbolNameForMethod(); + /** * @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. diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java index 6925ab456f69..f057384794d8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java @@ -124,6 +124,7 @@ enum IMAGE_SECTION_HEADER { static final int IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000; + static final int IMAGE_SCN_MEM_DISCARDABLE = 0x02000000; static final int IMAGE_SCN_MEM_SHARED = 0x10000000; static final int IMAGE_SCN_MEM_EXECUTE = 0x20000000; static final int IMAGE_SCN_MEM_READ = 0x40000000; @@ -202,7 +203,8 @@ enum IMAGE_RELOCATION { static final int IMAGE_REL_AMD64_REL32_3 = 0x7; static final int IMAGE_REL_AMD64_REL32_4 = 0x8; static final int IMAGE_REL_AMD64_REL32_5 = 0x9; - + static final int IMAGE_REL_AMD64_SECTION = 0xa; + static final int IMAGE_REL_AMD64_SECREL = 0xb; } //@formatter:on // Checkstyle: resume diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java index 9d72c703be84..5aa80dce0669 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2018, Oracle and/or its affiliates. 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 @@ -56,8 +56,14 @@ public static PECoffRelocationMethod getRelocation(PECoffMachine m, RelocationKi switch (k) { case DIRECT_8: return PECoffX86_64Relocation.ADDR64; + case DIRECT_4: + return PECoffX86_64Relocation.ADDR32; case PC_RELATIVE_4: return PECoffX86_64Relocation.REL32; + case SECTION_2: + return PECoffX86_64Relocation.SECTION; + case SECREL_4: + return PECoffX86_64Relocation.SECREL; case UNKNOWN: default: throw new IllegalArgumentException("cannot map unknown relocation kind to an PECoff x86-64 relocation type"); @@ -146,6 +152,24 @@ public long toLong() { return IMAGE_RELOCATION.IMAGE_REL_AMD64_ADDR64; } }, + ADDR32 { + @Override + public long toLong() { + return IMAGE_RELOCATION.IMAGE_REL_AMD64_ADDR32; + } + }, + SECREL { + @Override + public long toLong() { + return IMAGE_RELOCATION.IMAGE_REL_AMD64_SECREL; + } + }, + SECTION { + @Override + public long toLong() { + return IMAGE_RELOCATION.IMAGE_REL_AMD64_SECTION; + } + }, REL32 { @Override public long toLong() { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java index 83ce337ae3d6..138ad8457e33 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java @@ -39,10 +39,14 @@ import com.oracle.objectfile.LayoutDecisionMap; import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.SymbolTable; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; import com.oracle.objectfile.io.AssemblyBuffer; import com.oracle.objectfile.io.OutputAssembler; import com.oracle.objectfile.pecoff.PECoff.IMAGE_FILE_HEADER; import com.oracle.objectfile.pecoff.PECoff.IMAGE_SECTION_HEADER; +import com.oracle.objectfile.pecoff.cv.CVDebugInfo; +import com.oracle.objectfile.pecoff.cv.CVSymbolSectionImpl; +import com.oracle.objectfile.pecoff.cv.CVTypeSectionImpl; /** * Represents a PECoff object file. @@ -379,6 +383,7 @@ public enum PECoffSectionFlag implements ValueEnum { READ(IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_READ), WRITE(IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_WRITE), EXECUTE(IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_EXECUTE), + DISCARDABLE(IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_DISCARDABLE), LINKER(IMAGE_SECTION_HEADER.IMAGE_SCN_LNK_INFO | IMAGE_SECTION_HEADER.IMAGE_SCN_LNK_REMOVE); private final int value; @@ -683,4 +688,41 @@ public PECoffRelocationTable getRelocationTable() { protected int getMinimumFileSize() { return 0; } + + @Override + public Section newDebugSection(String name, ElementImpl impl) { + PECoffSection coffSection = (PECoffSection) super.newDebugSection(name, impl); + coffSection.getFlags().add(PECoffSectionFlag.DISCARDABLE); + coffSection.getFlags().add(PECoffSectionFlag.READ); + coffSection.getFlags().add(PECoffSectionFlag.INITIALIZED_DATA); + return coffSection; + } + + @Override + public void installDebugInfo(DebugInfoProvider debugInfoProvider) { + CVDebugInfo cvDebugInfo = new CVDebugInfo(getByteOrder()); + + // we need an implementation for each section + CVSymbolSectionImpl cvSymbolSectionImpl = cvDebugInfo.getCVSymbolSection(); + CVTypeSectionImpl cvTypeSectionImpl = cvDebugInfo.getCVTypeSection(); + + // now we can create the section elements with empty content + newDebugSection(cvSymbolSectionImpl.getSectionName(), cvSymbolSectionImpl); + newDebugSection(cvTypeSectionImpl.getSectionName(), cvTypeSectionImpl); + + // 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 + cvSymbolSectionImpl.getOrCreateRelocationElement(false); + cvTypeSectionImpl.getOrCreateRelocationElement(false); + + // ok now we can populate the implementations + cvDebugInfo.installDebugInfo(debugInfoProvider); + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVConstants.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVConstants.java new file mode 100644 index 000000000000..5465da967165 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVConstants.java @@ -0,0 +1,38 @@ +/* + * 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.pecoff.cv; + +public abstract class CVConstants { + + /* The names of relevant CodeView sections. */ + static final String CV_SECTION_NAME_PREFIX = ".debug$"; + static final String CV_SYMBOL_SECTION_NAME = CV_SECTION_NAME_PREFIX + "S"; + static final String CV_TYPE_SECTION_NAME = CV_SECTION_NAME_PREFIX + "T"; + + /* CodeView section header signature */ + static final int CV_SIGNATURE_C13 = 4; +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugConstants.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugConstants.java new file mode 100644 index 000000000000..26f01446bb1d --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugConstants.java @@ -0,0 +1,43 @@ +/* + * 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.pecoff.cv; + +public abstract class CVDebugConstants { + + static final int DEBUG_S_SYMBOLS = 0xf1; + static final int DEBUG_S_LINES = 0xf2; + static final int DEBUG_S_STRINGTABLE = 0xf3; + static final int DEBUG_S_FILECHKSMS = 0xf4; + + /* Subcommands in DEBUG_S_SYMBOLS section. */ + static final short S_END = 0x0006; + static final short S_OBJNAME = 0x1101; + static final short S_FRAMEPROC = 0x1012; + static final short S_GPROC32 = 0x1110; + static final short S_COMPILE3 = 0x113c; + static final short S_ENVBLOCK = 0x113d; +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java new file mode 100644 index 000000000000..635f084fff1a --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java @@ -0,0 +1,56 @@ +/* + * 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.pecoff.cv; + +import com.oracle.objectfile.debugentry.DebugInfoBase; + +import java.nio.ByteOrder; + +/** + * CVDebugInfo is a container class for all the CodeView sections to be emitted in the object file. + * Currently, those are.debug$S (CVSymbolSectionImpl) and .debug$T (CVTypeSectionImpl). + */ +public final class CVDebugInfo extends DebugInfoBase { + + private CVSymbolSectionImpl cvSymbolSection; + private CVTypeSectionImpl cvTypeSection; + + public CVDebugInfo(ByteOrder byteOrder) { + super(byteOrder); + cvSymbolSection = new CVSymbolSectionImpl(this); + cvTypeSection = new CVTypeSectionImpl(); + } + + public CVSymbolSectionImpl getCVSymbolSection() { + return cvSymbolSection; + } + + public CVTypeSectionImpl getCVTypeSection() { + return cvTypeSection; + } + +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVFileTableRecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVFileTableRecord.java new file mode 100644 index 000000000000..4c77a935806b --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVFileTableRecord.java @@ -0,0 +1,147 @@ +/* + * 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.pecoff.cv; + +import com.oracle.objectfile.debugentry.FileEntry; +import org.graalvm.compiler.debug.GraalError; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.LinkedHashMap; +import java.util.Map; + +final class CVFileTableRecord extends CVSymbolRecord { + + private static final int FILE_TABLE_INITIAL_SIZE = 200; + + private final CVSymbolSectionImpl.CVStringTable strings; + + private int currentOffset = 0; + + /* Use a LinkedHashMap to maintain insertion order. */ + private Map fileEntryToRecordMap = new LinkedHashMap<>(FILE_TABLE_INITIAL_SIZE); + + CVFileTableRecord(CVDebugInfo cvDebugInfo, CVSymbolSectionImpl.CVStringTable strings) { + super(cvDebugInfo, CVDebugConstants.DEBUG_S_FILECHKSMS); + this.strings = strings; + } + + int addFile(FileEntry entry) { + if (fileEntryToRecordMap.containsKey(entry)) { + return fileEntryToRecordMap.get(entry).getFileTableId(); + } else { + /* Create required stringtable entry. */ + int stringTableOffset = strings.add(entry.getFullName()); + fileEntryToRecordMap.put(entry, new FileRecord(entry, currentOffset, stringTableOffset)); + currentOffset += FileRecord.FILE_RECORD_LENGTH; + return currentOffset - FileRecord.FILE_RECORD_LENGTH; + } + } + + @Override + public int computeSize(int initialPos) { + return initialPos + (fileEntryToRecordMap.size() * FileRecord.FILE_RECORD_LENGTH); + } + + @Override + public int computeContents(byte[] buffer, int initialPos) { + int pos = initialPos; + for (FileRecord record : fileEntryToRecordMap.values()) { + pos = record.put(buffer, pos); + } + return pos; + } + + @Override + public String toString() { + return "CVFileRecord(type=" + type + ",pos=" + recordStartPosition + ", size=" + fileEntryToRecordMap.size() + ")"; + } + + private static final class FileRecord { + + static final int FILE_RECORD_LENGTH = 24; + + private static final byte CB_VALUE = 0x10; + private static final int CHECKSUM_LENGTH = 16; + private static final byte CHECKSUM_NONE = 0x00; + private static final byte CHECKSUM_MD5 = 0x01; + private static final byte[] EMPTY_CHECKSUM = new byte[CHECKSUM_LENGTH]; + + private FileEntry entry; + private int fileTableId; + private int stringTableId; + + FileRecord(FileEntry entry, int fileTableId, int stringTableId) { + this.entry = entry; + this.fileTableId = fileTableId; + this.stringTableId = stringTableId; + } + + private int put(byte[] buffer, int initialPos) { + String fn = entry.getFullName(); + int pos = CVUtil.putInt(stringTableId, buffer, initialPos); /* Stringtable index. */ + pos = CVUtil.putByte(CB_VALUE, buffer, pos); /* Cb (unknown what this is). */ + byte[] checksum = calculateMD5Sum(fn); + if (checksum != null) { + pos = CVUtil.putByte(CHECKSUM_MD5, buffer, pos); /* Checksum type (0x01 == MD5). */ + pos = CVUtil.putBytes(checksum, buffer, pos); + } else { + pos = CVUtil.putByte(CHECKSUM_NONE, buffer, pos); + pos = CVUtil.putBytes(EMPTY_CHECKSUM, buffer, pos); + } + pos = CVUtil.align4(pos); + assert pos == initialPos + FILE_RECORD_LENGTH; + return pos; + } + + int getFileTableId() { + return fileTableId; + } + + /** + * Calculate the MD5 checksum of a file. + * + * @param fn path to file + * @return a byte array containing the checksum, or null if there was an error reading the + * file. + */ + private static byte[] calculateMD5Sum(String fn) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(Files.readAllBytes(Paths.get(fn))); + return md.digest(); + } catch (IOException e) { + return null; + } catch (NoSuchAlgorithmException e) { + throw GraalError.shouldNotReachHere(); + } + } + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecord.java new file mode 100644 index 000000000000..090f30fe4acb --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecord.java @@ -0,0 +1,197 @@ +/* + * 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.pecoff.cv; + +import com.oracle.objectfile.ObjectFile; + +import java.util.ArrayList; + +/* + * A line record (DEBUG_S_LINES) consists of a list of (file block record + subrecords). + * Graal will generate one CVLineRecord per function. + */ +final class CVLineRecord extends CVSymbolRecord { + + /* Header: addr (4 bytes):section (2 bytes) flags (2 bytes) chunck length (4 bytes). */ + private static final int LINE_RECORD_HEADER_SIZE = Integer.BYTES + Short.BYTES * 2 + Integer.BYTES; + + private static final int DEFAULT_LINE_BLOCK_COUNT = 100; + private static final int DEFAULT_LINE_ENTRY_COUNT = 100; + + /* Has columns flag = 0x80 - not supported. */ + private static final short CB_HAS_NO_COLUMNS_FLAG = 0x00; + + private String symbolName; + private ArrayList fileBlocks = new ArrayList<>(DEFAULT_LINE_BLOCK_COUNT); + + CVLineRecord(CVDebugInfo cvDebugInfo, String symbolName) { + super(cvDebugInfo, CVDebugConstants.DEBUG_S_LINES); + this.symbolName = symbolName; + } + + void addNewFile(int fileId) { + fileBlocks.add(new FileBlock(fileId)); + } + + void addNewLine(int addr, int line) { + fileBlocks.get(fileBlocks.size() - 1).addEntry(new LineEntry(addr, line)); + } + + int getCurrentFileId() { + assert !fileBlocks.isEmpty(); + return fileBlocks.get(fileBlocks.size() - 1).fileId; + } + + @Override + protected int computeSize(int initialPos) { + return computeContents(null, initialPos); + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + /* Line record header. */ + int pos = computeHeader(buffer, initialPos); + /* All blocks. */ + for (FileBlock fileBlock : fileBlocks) { + pos = fileBlock.computeContents(buffer, pos); + } + return pos; + } + + private int computeHeader(byte[] buffer, int initialPos) { + + if (buffer == null) { + return initialPos + LINE_RECORD_HEADER_SIZE; + } + + assert symbolName != null; + int pos = initialPos; + + /* Emit addr:section relocation records. */ + cvDebugInfo.getCVSymbolSection().markRelocationSite(pos, ObjectFile.RelocationKind.SECREL_4, symbolName, false, 1L); + pos = CVUtil.putInt(0, buffer, pos); + cvDebugInfo.getCVSymbolSection().markRelocationSite(pos, ObjectFile.RelocationKind.SECTION_2, symbolName, false, 1L); + pos = CVUtil.putShort((short) 0, buffer, pos); + + /* Emit flags. */ + pos = CVUtil.putShort(CB_HAS_NO_COLUMNS_FLAG, buffer, pos); + + /* Length of this chunk in object file (= highAddr since it's zero based. */ + assert !fileBlocks.isEmpty(); + int length = fileBlocks.get(fileBlocks.size() - 1).getHighAddr(); + pos = CVUtil.putInt(length, buffer, pos); + return pos; + } + + boolean isEmpty() { + return fileBlocks.isEmpty(); + } + + @Override + public String toString() { + return String.format("CVLineRecord(type=0x%04x pos=0x%05x size=0x%d)", type, recordStartPosition, fileBlocks.size()); + } + + /* + * FileBlock is a section of contiguous code in a compilation unit, associated with a single + * source file. If a function includes inlined code, that code needs its own FileBlock, + * surrounded by FileBlocks describing the enclosing source file. A FileBlock consists of a list + * of LineEntries. + */ + private static class FileBlock { + + /* Fileblock header: fileId (4 bytes) lineEntry count (4 bytes) tablesize (4 bytes) */ + static final int FILE_BLOCK_HEADER_SIZE = Integer.BYTES * 3; + + private ArrayList lineEntries = new ArrayList<>(DEFAULT_LINE_ENTRY_COUNT); + private int fileId; + + FileBlock(int fileId) { + this.fileId = fileId; + } + + void addEntry(LineEntry le) { + lineEntries.add(le); + } + + int computeContents(byte[] buffer, int initialPos) { + if (buffer == null) { + return computeSize(initialPos); + } + int pos = initialPos; + pos = CVUtil.putInt(fileId, buffer, pos); + pos = CVUtil.putInt(lineEntries.size(), buffer, pos); + pos = CVUtil.putInt(computeSize(0), buffer, pos); + for (LineEntry lineEntry : lineEntries) { + pos = lineEntry.computeContents(buffer, pos); + } + return pos; + } + + int computeSize(int initialPos) { + return initialPos + FILE_BLOCK_HEADER_SIZE + LineEntry.LINE_ENTRY_SIZE * lineEntries.size(); + } + + int getHighAddr() { + assert !lineEntries.isEmpty(); + return lineEntries.get(lineEntries.size() - 1).addr; + } + } + + /* + * LineEntry associates some object code (at 'addr', relative to the start of this DEBUG_S_LINES + * record) with a source line in the current FileBlock file. + */ + private static class LineEntry { + + /* Entry: address(4 bytes) line number+flags (4 bytes) */ + static final int LINE_ENTRY_SIZE = 2 * Integer.BYTES; + + int addr; + int lineAndFLags; + + LineEntry(int addr, int line, int deltaEnd, boolean isStatement) { + this.addr = addr; + assert line <= 0xffffff; + assert line >= 0; + assert deltaEnd <= 0x7f; + assert deltaEnd >= 0; + lineAndFLags = line | (deltaEnd << 24) | (isStatement ? 0x80000000 : 0); + } + + LineEntry(int addr, int line) { + this(addr, line, 0, false); + } + + int computeContents(byte[] buffer, int initialPos) { + int pos = initialPos; + pos = CVUtil.putInt(addr, buffer, pos); + pos = CVUtil.putInt(lineAndFLags, buffer, pos); + return pos; + } + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java new file mode 100644 index 000000000000..1156522fb8e3 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.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.pecoff.cv; + +import org.graalvm.compiler.debug.DebugContext; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.PrimaryEntry; +import com.oracle.objectfile.debugentry.Range; + +/* + * In CV4, the line table consists of a series of file headers followed by line number entries. + * If this is a different file, then update the length of the previous file header, write the + * new file header and write the new range At the very end, make sure we update the last file header. + */ +public class CVLineRecordBuilder { + + private CVDebugInfo cvDebugInfo; + private DebugContext debugContext; + private CVLineRecord lineRecord; + private PrimaryEntry primaryEntry; + + CVLineRecordBuilder(DebugContext theDebugContext, CVDebugInfo cvDebugInfo) { + this.debugContext = theDebugContext; + this.cvDebugInfo = cvDebugInfo; + } + + public void debug(String format, Object... args) { + cvDebugInfo.getCVSymbolSection().verboseLog(debugContext, format, args); + } + + /** + * Build line number records for a function. + * + * @param entry function to build line number table for + * @return CVLineRecord containing any entries generated, or null if no entries generated + */ + CVLineRecord build(PrimaryEntry entry) { + this.primaryEntry = entry; + Range primaryRange = primaryEntry.getPrimary(); + + debug("DEBUG_S_LINES linerecord for 0x%05x file: %s:%d\n", primaryRange.getLo(), primaryRange.getFileName(), primaryRange.getLine()); + this.lineRecord = new CVLineRecord(cvDebugInfo, primaryRange.getSymbolName()); + debug("CVLineRecord.computeContents: processing primary range %s\n", primaryRange); + + processRange(primaryRange); + for (Range subRange : primaryEntry.getSubranges()) { + debug("CVLineRecord.computeContents: processing range %s\n", subRange); + processRange(subRange); + } + return lineRecord; + } + + /** + * Merge input Range structures into line number table. The Range structures are assumed to be + * ordered by ascending address. + * + * @param range to be merged or added to line number record + */ + private void processRange(Range range) { + + FileEntry file = cvDebugInfo.findFile(range.getFileAsPath()); + if (file == null) { + debug("processRange: range has no file: %s\n", range); + return; + } + + if (range.getLine() == -1) { + debug("processRange: ignoring: bad line number\n"); + return; + } + + int fileId = cvDebugInfo.getCVSymbolSection().getFileTableRecord().addFile(file); + if (lineRecord.isEmpty() || lineRecord.getCurrentFileId() != fileId) { + debug("processRange: addNewFile: %s\n", file); + lineRecord.addNewFile(fileId); + } + + /* Add line record. */ + /* An optimization would be to merge adjacent line records. */ + int lineLoAddr = range.getLo() - primaryEntry.getPrimary().getLo(); + int line = Math.max(range.getLine(), 1); + debug("processRange: addNewLine: 0x%05x %s\n", lineLoAddr, line); + lineRecord.addNewLine(lineLoAddr, line); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSectionImpl.java new file mode 100644 index 000000000000..a149e38c99ca --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSectionImpl.java @@ -0,0 +1,104 @@ +/* + * 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.pecoff.cv; + +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 org.graalvm.compiler.debug.DebugContext; + +import java.util.Map; +import java.util.Set; + +abstract class CVSectionImpl extends BasicProgbitsSectionImpl { + + boolean debug = false; + + CVSectionImpl() { + } + + private String debugSectionLogName() { + /* + * Log messages for the symbol section will be enabled using "PeCoffdebug$S". Log messages + * for the type section will be enabled using "PeCoffdebug$T". + */ + assert getSectionName().startsWith(CVConstants.CV_SECTION_NAME_PREFIX); + return "PeCoff" + getSectionName().replace(".", ""); + } + + protected void enableLog(DebugContext context) { + /* + * Unlike in the Dwarf debug code, debug output may be enabled in both the sizing and + * writing phases. (Currently turned off in the sizing state) + */ + if (context.areScopesEnabled()) { + debug = true; + } + } + + 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); + } + } + + @Override + public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { + + /* Ensure content byte[] has been created before calling super method. */ + getOwner().debugContext(debugSectionLogName(), this::createContent); + + /* Ensure content byte[] has been written before calling super method. */ + getOwner().debugContext(debugSectionLogName(), this::writeContent); + + return super.getOrDecideContent(alreadyDecided, contentHint); + } + + @Override + public Set getDependencies(Map decisions) { + Set deps = super.getDependencies(decisions); + LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT); + LayoutDecision ourSize = decisions.get(getElement()).getDecision(LayoutDecision.Kind.SIZE); + /* Make our size depend on our content. */ + deps.add(BuildDependency.createOrGet(ourSize, ourContent)); + return deps; + } + + public abstract void createContent(DebugContext debugContext); + + public abstract void writeContent(DebugContext debugContext); + + public abstract String getSectionName(); +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVStringTableRecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVStringTableRecord.java new file mode 100644 index 000000000000..d0fa57c82c0b --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVStringTableRecord.java @@ -0,0 +1,56 @@ +/* + * 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.pecoff.cv; + +final class CVStringTableRecord extends CVSymbolRecord { + + private final CVSymbolSectionImpl.CVStringTable stringTable; + + CVStringTableRecord(CVDebugInfo cvDebugInfo, CVSymbolSectionImpl.CVStringTable stringTable) { + super(cvDebugInfo, CVDebugConstants.DEBUG_S_STRINGTABLE); + this.stringTable = stringTable; + } + + @Override + public int computeSize(int pos) { + return pos + stringTable.getCurrentOffset(); + } + + @Override + public int computeContents(byte[] buffer, int initialPos) { + int pos = initialPos; + for (CVSymbolSectionImpl.CVStringTable.StringTableEntry entry : stringTable.values()) { + pos = CVUtil.putUTF8StringBytes(entry.text, buffer, pos); + } + return pos; + } + + @Override + public String toString() { + return String.format("CVStringTableRecord(type=0x%04x pos=0x%06x count=%d)", type, recordStartPosition, stringTable.size()); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolRecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolRecord.java new file mode 100644 index 000000000000..38b263649a38 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolRecord.java @@ -0,0 +1,82 @@ +/* + * 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.pecoff.cv; + +import org.graalvm.compiler.debug.DebugContext; + +/* + * A Symbol record is a top-level record in the CodeView .debug$S section. + */ +abstract class CVSymbolRecord { + + /* Symbol record header: record type (4 bytes), length (4 bytes). */ + private static final int SYMBOL_RECORD_HEADER_SIZE = Integer.BYTES * 2; + + protected final CVDebugInfo cvDebugInfo; + protected final int type; + protected int recordStartPosition; + + CVSymbolRecord(CVDebugInfo cvDebugInfo, int type) { + this.cvDebugInfo = cvDebugInfo; + this.type = type; + } + + final int computeFullSize(int initialPos) { + this.recordStartPosition = initialPos; + int pos = initialPos + SYMBOL_RECORD_HEADER_SIZE; + return computeSize(pos); + } + + final int computeFullContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putInt(type, buffer, initialPos); + int lenPos = pos; + pos = computeContents(buffer, initialPos + SYMBOL_RECORD_HEADER_SIZE); + /* Length does not include debug record header (4 bytes record id + 4 bytes length). */ + CVUtil.putInt(pos - initialPos - SYMBOL_RECORD_HEADER_SIZE, buffer, lenPos); + return pos; + } + + public int getPos() { + return recordStartPosition; + } + + public int getCommand() { + return type; + } + + protected abstract int computeSize(int pos); + + protected abstract int computeContents(byte[] buffer, int pos); + + @Override + public String toString() { + return "CVSymbolRecord(type=" + type + ",pos=" + recordStartPosition + ")"; + } + + public void logContents(@SuppressWarnings("unused") DebugContext debugContext) { + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSectionImpl.java new file mode 100644 index 000000000000..ecf1a17c870a --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSectionImpl.java @@ -0,0 +1,189 @@ +/* + * 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.pecoff.cv; + +import org.graalvm.compiler.debug.DebugContext; + +import com.oracle.objectfile.io.Utf8; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; + +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_SIGNATURE_C13; +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_SYMBOL_SECTION_NAME; + +public final class CVSymbolSectionImpl extends CVSectionImpl { + + private static final int CV_VECTOR_DEFAULT_SIZE = 200; + private static final int CV_STRINGTABLE_DEFAULT_SIZE = 200; + + private final CVDebugInfo cvDebugInfo; + private final ArrayList cvRecords; + private final CVStringTable stringTable; + private final CVFileTableRecord fileTableRecord; + + CVSymbolSectionImpl(CVDebugInfo cvDebugInfo) { + this.cvDebugInfo = cvDebugInfo; + this.cvRecords = new ArrayList<>(CV_VECTOR_DEFAULT_SIZE); + this.stringTable = new CVStringTable(CV_STRINGTABLE_DEFAULT_SIZE); + this.fileTableRecord = new CVFileTableRecord(cvDebugInfo, stringTable); + } + + @Override + public String getSectionName() { + return CV_SYMBOL_SECTION_NAME; + } + + /* + * Any (there may be sewveral) CodeView symbol section ("debug$S") is actually a list of + * records, some of which containing sub-records. + */ + @Override + public void createContent(DebugContext debugContext) { + int pos = 0; + enableLog(debugContext); + log(debugContext, "CVSymbolSectionImpl.createContent() adding records"); + addRecords(debugContext); + log(debugContext, "CVSymbolSectionImpl.createContent() start"); + /* Add header size. */ + pos += Integer.BYTES; + /* Add sum of all record sizes. */ + for (CVSymbolRecord record : cvRecords) { + pos = CVUtil.align4(pos); + pos = record.computeFullSize(pos); + } + /* Create a buffer that holds it all. */ + byte[] buffer = new byte[pos]; + super.setContent(buffer); + log(debugContext, "CVSymbolSectionImpl.createContent() end"); + } + + @Override + public void writeContent(DebugContext debugContext) { + int pos = 0; + enableLog(debugContext); + log(debugContext, "CVSymbolSectionImpl.writeContent() start recordcount=%d", cvRecords.size()); + byte[] buffer = getContent(); + /* Write section header. */ + log(debugContext, " [0x%08x] CV_SIGNATURE_C13", pos); + pos = CVUtil.putInt(CV_SIGNATURE_C13, buffer, pos); + /* Write all records. */ + for (CVSymbolRecord record : cvRecords) { + pos = CVUtil.align4(pos); + log(debugContext, " [0x%08x] %s", pos, record.toString()); + record.logContents(debugContext); + pos = record.computeFullContents(buffer, pos); + } + log(debugContext, "CVSymbolSectionImpl.writeContent() end"); + } + + private void addRecords(DebugContext debugContext) { + addPrologueRecord(); + addFunctionRecords(debugContext); + addFileRecord(); + addStringTableRecord(); + } + + private void addPrologueRecord() { + CVSymbolSubsection prologue = new CVSymbolSubsection(cvDebugInfo); + CVSymbolSubrecord.CVObjectNameRecord objectNameRecord = new CVSymbolSubrecord.CVObjectNameRecord(cvDebugInfo); + if (objectNameRecord.isValid()) { + prologue.addRecord(objectNameRecord); + } + prologue.addRecord(new CVSymbolSubrecord.CVCompile3Record(cvDebugInfo)); + prologue.addRecord(new CVSymbolSubrecord.CVEnvBlockRecord(cvDebugInfo)); + addRecord(prologue); + } + + private void addFunctionRecords(DebugContext debugContext) { + /* This will build and add many records for each function. */ + new CVSymbolSubsectionBuilder(cvDebugInfo).build(debugContext); + } + + private void addFileRecord() { + /* Files are added to this record during function record building. */ + addRecord(fileTableRecord); + } + + CVFileTableRecord getFileTableRecord() { + return this.fileTableRecord; + } + + private void addStringTableRecord() { + CVSymbolRecord stringTableRecord = new CVStringTableRecord(cvDebugInfo, stringTable); + addRecord(stringTableRecord); + } + + static final class CVStringTable { + static final class StringTableEntry { + public int offset; + public String text; + + StringTableEntry(int offset, String text) { + this.offset = offset; + this.text = text; + } + } + + /* Use LinkedHashMap so order is maintained when writing string table. */ + private final HashMap strings; + private int currentOffset = 0; + + CVStringTable(int startSize) { + strings = new LinkedHashMap<>(startSize); + /* Ensure that the empty string has index 0. */ + add(""); + } + + int add(String s) { + StringTableEntry newEntry = new StringTableEntry(currentOffset, s); + StringTableEntry entry = strings.putIfAbsent(s, newEntry); + if (entry == null) { + currentOffset += Utf8.utf8Length(s) + 1; + } + return entry == null ? newEntry.offset : entry.offset; + } + + Collection values() { + return strings.values(); + } + + int size() { + return strings.size(); + } + + int getCurrentOffset() { + return currentOffset; + } + } + + void addRecord(CVSymbolRecord record) { + cvRecords.add(record); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java new file mode 100644 index 000000000000..ddb43d1039c7 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java @@ -0,0 +1,383 @@ +/* + * 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.pecoff.cv; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.debugentry.ClassEntry; + +import java.util.HashMap; +import java.util.Map; + +/* + * A CVSymbolSubrecord is a record in a DEBUG_S_SYMBOL record within a .debug$S section within a PECOFF file. + */ +abstract class CVSymbolSubrecord { + + private int subrecordStartPosition; + + private final short cmd; + CVDebugInfo cvDebugInfo; + + CVSymbolSubrecord(CVDebugInfo cvDebugInfo, short cmd) { + this.cvDebugInfo = cvDebugInfo; + this.cmd = cmd; + } + + final int computeFullContents(byte[] buffer, int initialPos) { + subrecordStartPosition = initialPos; + int pos = initialPos; + pos += Short.BYTES; /* Save room for length (not including length bytes). */ + pos = CVUtil.putShort(cmd, buffer, pos); + pos = computeContents(buffer, pos); + short length = (short) (pos - initialPos - Short.BYTES); + CVUtil.putShort(length, buffer, initialPos); + return pos; + } + + @Override + public String toString() { + return String.format("CVSymbolSubrecord(pos=0x%06x cmd=0x%04x)", subrecordStartPosition, cmd); + } + + public int getPos() { + return subrecordStartPosition; + } + + public int getCommand() { + return cmd; + } + + protected abstract int computeContents(byte[] buffer, int pos); + + public static final class CVObjectNameRecord extends CVSymbolSubrecord { + + String objName; /* find the full path to object file we will produce. */ + + CVObjectNameRecord(CVDebugInfo cvDebugInfo, String objName) { + super(cvDebugInfo, CVDebugConstants.S_OBJNAME); + this.objName = objName; + } + + CVObjectNameRecord(CVDebugInfo cvDebugInfo) { + this(cvDebugInfo, findObjectName(cvDebugInfo)); + } + + private static String findObjectName(CVDebugInfo cvDebugInfo) { + /* Infer object filename from first class definition. */ + String fn = null; + for (ClassEntry classEntry : cvDebugInfo.getPrimaryClasses()) { + if (classEntry.getFileName() != null) { + fn = classEntry.getFileEntry().getFileName(); + if (fn.endsWith(".java")) { + fn = fn.substring(0, fn.lastIndexOf(".java")) + ".obj"; + } + break; + } + } + return fn; + } + + boolean isValid() { + return objName != null; + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putInt(0, buffer, initialPos); /* Signature is currently set to 0. */ + pos = CVUtil.putUTF8StringBytes(objName, buffer, pos); + return pos; + } + + @Override + public String toString() { + return "S_OBJNAME " + objName; + } + } + + public static final class CVCompile3Record extends CVSymbolSubrecord { + + private static final byte HAS_DEBUG_FLAG = 0; + @SuppressWarnings("unused") private static final byte HAS_NO_DEBUG_FLAG = (byte) 0x80; + + private byte language; + private byte cf1; + private byte cf2; + private byte padding; + private short machine; + private short feMajor; + private short feMinor; + private short feBuild; + private short feQFE; + private short beMajor; + private short beMinor; + private short beBuild; + private short beQFE; + private String compiler; + + CVCompile3Record(CVDebugInfo cvDebugInfo) { + super(cvDebugInfo, CVDebugConstants.S_COMPILE3); + language = 0; + cf1 = HAS_DEBUG_FLAG; + cf2 = (byte) 0; + padding = (byte) 0; + machine = (short) 208; + feMajor = (short) 2; + feMinor = (short) 3; + feBuild = (short) 4; + feQFE = (short) 5; + beMajor = (short) 6; + beMinor = (short) 7; + beBuild = (short) 8; + beQFE = (short) 9; + compiler = "graal"; + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putByte(language, buffer, initialPos); + pos = CVUtil.putByte(cf1, buffer, pos); + pos = CVUtil.putByte(cf2, buffer, pos); + pos = CVUtil.putByte(padding, buffer, pos); + pos = CVUtil.putShort(machine, buffer, pos); + pos = CVUtil.putShort(feMajor, buffer, pos); + pos = CVUtil.putShort(feMinor, buffer, pos); + pos = CVUtil.putShort(feBuild, buffer, pos); + pos = CVUtil.putShort(feQFE, buffer, pos); + pos = CVUtil.putShort(beMajor, buffer, pos); + pos = CVUtil.putShort(beMinor, buffer, pos); + pos = CVUtil.putShort(beBuild, buffer, pos); + pos = CVUtil.putShort(beQFE, buffer, pos); + pos = CVUtil.putUTF8StringBytes(compiler, buffer, pos); // inline null terminated + return pos; + } + + @Override + public String toString() { + return String.format("S_COMPILE3 machine=%d fe=%d.%d.%d.%d be=%d.%d.%d%d compiler=%s", machine, feMajor, feMinor, feBuild, feQFE, beMajor, beMinor, beBuild, beQFE, compiler); + } + } + + public static final class CVEnvBlockRecord extends CVSymbolSubrecord { + + private static final int ENVMAP_INITIAL_CAPACITY = 10; + + private Map map = new HashMap<>(ENVMAP_INITIAL_CAPACITY); + + /*- + * Example contents of the environment block: + * cwd = C:\tmp\graal-8 + * cl = C:\tmp\graal-8\ojdkbuild\tools\toolchain\vs2010e\VC\Bin\x86_amd64\cl.exe + * cmd = -Zi -MT -IC:\tmp\graal-8\tools\toolchain\vs2010e\VC\INCLUDE -IC:\tmp\graal-8\tools\toolchain\sdk71\INCLUDE -IC:\tmp\graal-8\tools\toolchain\sdk71\INCLUDE\gl -TC -X + * src = helloworld.java + * pdb = C:\tmp\graal-8\vc100.pdb + */ + CVEnvBlockRecord(CVDebugInfo cvDebugInfo) { + super(cvDebugInfo, CVDebugConstants.S_ENVBLOCK); + + /* Current directory. */ + map.put("cwd", System.getProperty("user.dir")); + + /* + * Define the primary source file - ideally, the source file containing main(). (Note + * that if Graal were to be used to compile a library, there may not be a main()). Since + * Graal doesn't work with java source files, use the source file associated with the + * first class that has a source file. + */ + String fn = findFirstFile(cvDebugInfo); + if (fn != null) { + map.put("src", fn); + } + } + + private static String findFirstFile(CVDebugInfo cvDebugInfo) { + String fn = null; + for (ClassEntry classEntry : cvDebugInfo.getPrimaryClasses()) { + if (classEntry.getFileName() != null) { + fn = classEntry.getFileEntry().getFileName(); + break; + } + } + return fn; + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + /* Flags. */ + int pos = CVUtil.putByte((byte) 0, buffer, initialPos); + + /* Key/value pairs. */ + for (Map.Entry entry : map.entrySet()) { + pos = CVUtil.putUTF8StringBytes(entry.getKey(), buffer, pos); + pos = CVUtil.putUTF8StringBytes(entry.getValue(), buffer, pos); + } + + /* End marker. */ + pos = CVUtil.putUTF8StringBytes("", buffer, pos); + return pos; + } + + @Override + public String toString() { + return "S_ENVBLOCK " + map.size() + " entries"; + } + } + + /* + * Creating a proc32 record has a side effect: two relocation entries are added to the section + * relocation table; they refer back to the global symbol. + */ + public static class CVSymbolGProc32Record extends CVSymbolSubrecord { + + int pparent; + int pend; + int pnext; + int proclen; + int debugStart; + int debugEnd; + int typeIndex; + int offset; + short segment; + byte flags; + String externalName; + String debuggerName; + + CVSymbolGProc32Record(CVDebugInfo cvDebugInfo, short cmd, String externalName, String debuggerName, int pparent, int pend, int pnext, int proclen, int debugStart, int debugEnd, int typeIndex, + int offset, short segment, byte flags) { + super(cvDebugInfo, cmd); + this.externalName = externalName; + this.debuggerName = debuggerName; + this.pparent = pparent; + this.pend = pend; + this.pnext = pnext; + this.proclen = proclen; + this.debugStart = debugStart; + this.debugEnd = debugEnd; + this.typeIndex = typeIndex; + this.offset = offset; + this.segment = segment; + this.flags = flags; + } + + CVSymbolGProc32Record(CVDebugInfo cvDebugInfo, String externalName, String debuggerName, int pparent, int pend, int pnext, int proclen, int debugStart, int debugEnd, int typeIndex, int offset, + short segment, byte flags) { + this(cvDebugInfo, CVDebugConstants.S_GPROC32, externalName, debuggerName, pparent, pend, pnext, proclen, debugStart, debugEnd, typeIndex, offset, segment, flags); + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putInt(pparent, buffer, initialPos); + pos = CVUtil.putInt(pend, buffer, pos); + pos = CVUtil.putInt(pnext, buffer, pos); + pos = CVUtil.putInt(proclen, buffer, pos); + pos = CVUtil.putInt(debugStart, buffer, pos); + pos = CVUtil.putInt(debugEnd, buffer, pos); + pos = CVUtil.putInt(typeIndex, buffer, pos); + if (buffer != null) { + cvDebugInfo.getCVSymbolSection().markRelocationSite(pos, ObjectFile.RelocationKind.SECREL_4, externalName, false, 1L); + } + pos = CVUtil.putInt(0, buffer, pos); + if (buffer != null) { + cvDebugInfo.getCVSymbolSection().markRelocationSite(pos, ObjectFile.RelocationKind.SECTION_2, externalName, false, 1L); + } + pos = CVUtil.putShort((short) 0, buffer, pos); + pos = CVUtil.putByte(flags, buffer, pos); + pos = CVUtil.putUTF8StringBytes(debuggerName, buffer, pos); + return pos; + } + + @Override + public String toString() { + return String.format("S_GPROC32 name=%s/%s parent=%d debugstart=0x%x debugend=0x%x len=0x%x offset=0x%x type=0x%x flags=0x%x)", debuggerName, externalName, pparent, debugStart, debugEnd, + proclen, offset, typeIndex, flags); + } + } + + public static final class CVSymbolFrameProcRecord extends CVSymbolSubrecord { + + int framelen; + int padLen; + int padOffset; + int saveRegsCount; + int ehOffset; + short ehSection; + int flags; + + CVSymbolFrameProcRecord(CVDebugInfo cvDebugInfo, int framelen, int padLen, int padOffset, int saveRegsCount, int ehOffset, short ehSection, int flags) { + super(cvDebugInfo, CVDebugConstants.S_FRAMEPROC); + this.framelen = framelen; + this.padLen = padLen; + this.padOffset = padOffset; + this.saveRegsCount = saveRegsCount; + this.ehOffset = ehOffset; + this.ehSection = ehSection; + this.flags = flags; + } + + CVSymbolFrameProcRecord(CVDebugInfo cvDebugInfo, int framelen, int flags) { + this(cvDebugInfo, framelen, 0, 0, 0, 0, (short) 0, flags); + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putInt(framelen, buffer, initialPos); + pos = CVUtil.putInt(padLen, buffer, pos); + pos = CVUtil.putInt(padOffset, buffer, pos); + pos = CVUtil.putInt(saveRegsCount, buffer, pos); + pos = CVUtil.putInt(ehOffset, buffer, pos); + pos = CVUtil.putShort(ehSection, buffer, pos); + pos = CVUtil.putInt(flags, buffer, pos); + return pos; + } + + @Override + public String toString() { + return String.format("S_FRAMEPROC len=0x%x padlen=0x%x paddOffset=0x%x regCount=%d flags=0x%x ", framelen, padLen, padOffset, saveRegsCount, flags); + } + } + + public static class CVSymbolEndRecord extends CVSymbolSubrecord { + + CVSymbolEndRecord(CVDebugInfo cvDebugInfo, short cmd) { + super(cvDebugInfo, cmd); + } + + CVSymbolEndRecord(CVDebugInfo cvDebugInfo) { + this(cvDebugInfo, CVDebugConstants.S_END); + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + // nothing + return initialPos; + } + + @Override + public String toString() { + return "S_END"; + } + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsection.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsection.java new file mode 100644 index 000000000000..da422e8b6890 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsection.java @@ -0,0 +1,77 @@ +/* + * 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.pecoff.cv; + +import org.graalvm.compiler.debug.DebugContext; + +import java.util.ArrayList; + +/* + * A CVSymbolSubsection is s special record in debug$S containing nested symbol records. + * (the nested records inherit from CVSymbolSubrecord) + */ +final class CVSymbolSubsection extends CVSymbolRecord { + + private static final int SUBCMD_INITIAL_CAPACITY = 100; + + private ArrayList subcmds = new ArrayList<>(SUBCMD_INITIAL_CAPACITY); + + CVSymbolSubsection(CVDebugInfo cvDebugInfo) { + super(cvDebugInfo, CVDebugConstants.DEBUG_S_SYMBOLS); + } + + void addRecord(CVSymbolSubrecord subcmd) { + subcmds.add(subcmd); + } + + @Override + protected int computeSize(int initialPos) { + return computeContents(null, initialPos); + } + + @Override + protected int computeContents(byte[] buffer, int initialPos) { + int pos = initialPos; + for (CVSymbolSubrecord subcmd : subcmds) { + pos = subcmd.computeFullContents(buffer, pos); + } + return pos; + } + + @Override + public void logContents(DebugContext debugContext) { + CVSectionImpl section = cvDebugInfo.getCVSymbolSection(); + for (CVSymbolSubrecord subcmd : subcmds) { + section.log(debugContext, " [0x%08x] %s", subcmd.getPos(), subcmd.toString()); + } + } + + @Override + public String toString() { + return String.format("DEBUG_S_SYMBOLS type=0x%04x pos=0x%05x subrecordcount=%d", type, recordStartPosition, subcmds.size()); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java new file mode 100644 index 000000000000..f0eac5086e0c --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java @@ -0,0 +1,183 @@ +/* + * 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.pecoff.cv; + +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 static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_NOTYPE; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_VOID; + +final class CVSymbolSubsectionBuilder { + + private final CVDebugInfo cvDebugInfo; + private final CVSymbolSubsection cvSymbolSubsection; + private CVLineRecordBuilder lineRecordBuilder; + private DebugContext debugContext = null; + + private boolean noMainFound = true; + + CVSymbolSubsectionBuilder(CVDebugInfo cvDebugInfo) { + this.cvSymbolSubsection = new CVSymbolSubsection(cvDebugInfo); + this.cvDebugInfo = cvDebugInfo; + } + + /** + * Build DEBUG_S_SYMBOLS record from all classEntries. (CodeView 4 format allows us to build one + * per class or one per function or one big record - which is what we do here). + * + * The CodeView symbol section Prolog is also a CVSymbolSubsection, but it is not build in this + * class. + */ + void build(DebugContext theDebugContext) { + this.debugContext = theDebugContext; + this.lineRecordBuilder = new CVLineRecordBuilder(debugContext, cvDebugInfo); + /* loop over all classes defined in this module. */ + for (ClassEntry classEntry : cvDebugInfo.getPrimaryClasses()) { + build(classEntry); + } + cvDebugInfo.getCVSymbolSection().addRecord(cvSymbolSubsection); + } + + /** + * Build all debug info for a classEntry. (does not yet handle member variables). + * + * @param classEntry current class + */ + private void build(ClassEntry classEntry) { + /* Loop over all functions defined in this class. */ + for (PrimaryEntry primaryEntry : classEntry.getPrimaryEntries()) { + build(primaryEntry); + } + } + + /** + * Emit records for each function: PROC32 S_FRAMEPROC S_END and line number records. (later: + * type records as required). + * + * @param primaryEntry primary entry for this function + */ + private void build(PrimaryEntry primaryEntry) { + final Range primaryRange = primaryEntry.getPrimary(); + + /* The name as it will appear in the debugger. */ + final String debuggerName = getDebuggerName(primaryRange); + + /* The name as exposed to the linker. */ + final String externalName = primaryRange.getSymbolName(); + + /* S_PROC32 add function definition. */ + int functionTypeIndex = addTypeRecords(primaryEntry); + byte funcFlags = 0; + CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, primaryRange.getHi() - primaryRange.getLo(), 0, + 0, functionTypeIndex, primaryRange.getLo(), (short) 0, funcFlags); + addToSymbolSubsection(proc32); + + /* S_FRAMEPROC add frame definitions. */ + int asynceh = 1 << 9; /* Async exception handling (vc++ uses 1, clang uses 0). */ + int localBP = 1 << 14; /* Local base pointer = SP (0=none, 1=sp, 2=bp 3=r13). */ + int paramBP = 1 << 16; /* Param base pointer = SP. */ + int frameFlags = asynceh + localBP + paramBP; /* NB: LLVM uses 0x14000. */ + addToSymbolSubsection(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, primaryEntry.getFrameSize(), frameFlags)); + + /* TODO: add local variables, and their types. */ + /* TODO: add block definitions. */ + + /* S_END add end record. */ + addToSymbolSubsection(new CVSymbolSubrecord.CVSymbolEndRecord(cvDebugInfo)); + + /* Add line number records. */ + addLineNumberRecords(primaryEntry); + } + + /** + * Rename function names for usability or functionality. + * + * First encountered main function becomes class.main. This is for usability. + * + * All other functions become class.function.999 (where 999 is a hash of the arglist). This is + * because The standard link.exe can't handle odd characters (parentheses or commas, for + * example) in debug information. + * + * This does not affect external symbols used by linker. + * + * TODO: strip illegal characters from arg lists instead ("link.exe" - safe names) + * + * @param range Range contained in the method of interest + * @return user debugger friendly method name + */ + private String getDebuggerName(Range range) { + final String methodName; + if (noMainFound && range.getMethodName().equals("main")) { + noMainFound = false; + methodName = range.getClassAndMethodName(); + } else { + /* In the future, use a more user-friendly name instead of a hash function. */ + methodName = range.getSymbolName(); + } + return methodName; + } + + private void addLineNumberRecords(PrimaryEntry primaryEntry) { + CVLineRecord record = lineRecordBuilder.build(primaryEntry); + /* + * If there are no file entries (perhaps for a synthetic function?), we don't add this + * record. + */ + if (!record.isEmpty()) { + cvDebugInfo.getCVSymbolSection().addRecord(record); + } + } + + /** + * Add a record to the symbol subsection. A symbol subsection is contained within the top level + * .debug$S symbol section. + * + * @param record the symbol subrecord to add. + */ + private void addToSymbolSubsection(CVSymbolSubrecord record) { + cvSymbolSubsection.addRecord(record); + } + + /** + * Add type records for function. (later add arglist, and return type and local types). + * + * @param entry primaryEntry containing entities whoses type records must be added + * @return type index of function type + */ + private int addTypeRecords(@SuppressWarnings("unused") PrimaryEntry entry) { + CVTypeRecord.CVTypeArglistRecord argListType = addTypeRecord(new CVTypeRecord.CVTypeArglistRecord().add(T_NOTYPE)); + CVTypeRecord funcType = addTypeRecord(new CVTypeRecord.CVTypeProcedureRecord().returnType(T_VOID).argList(argListType)); + return funcType.getSequenceNumber(); + } + + private T addTypeRecord(T record) { + return cvDebugInfo.getCVTypeSection().addOrReference(record); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeConstants.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeConstants.java new file mode 100644 index 000000000000..5db2e2e57bdf --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeConstants.java @@ -0,0 +1,45 @@ +/* + * 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.pecoff.cv; + +abstract class CVTypeConstants { + + /* + * Type table. Constants below 0x1000 are 'hardcoded', above are new type entries in the type + * section. + */ + static final short T_NOTYPE = 0x0000; + static final short T_VOID = 0x0003; + + static final short LF_PROCEDURE = 0x1008; + static final short LF_ARGLIST = 0x1201; + + /* Padding. */ + static final byte LF_PAD1 = (byte) 0xf1; + static final byte LF_PAD2 = (byte) 0xf2; + static final byte LF_PAD3 = (byte) 0xf3; +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeRecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeRecord.java new file mode 100644 index 000000000000..2b8f1de74e9d --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeRecord.java @@ -0,0 +1,238 @@ +/* + * 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.pecoff.cv; + +import java.util.ArrayList; + +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.LF_ARGLIST; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.LF_PAD1; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.LF_PAD2; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.LF_PAD3; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.LF_PROCEDURE; + +/* + * CV Type Record format (little-endian): + * uint16 length + * uint16 leaf (a.k.a. record type) + * (contents) + */ +abstract class CVTypeRecord { + + protected final short type; + private int startPosition; + private int sequenceNumber; /* CodeView type records are numbered 1000 on up. */ + + CVTypeRecord(short type) { + this.type = type; + this.startPosition = -1; + this.sequenceNumber = -1; + } + + int getSequenceNumber() { + return sequenceNumber; + } + + void setSequenceNumber(int sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + int computeFullSize(int initialPos) { + this.startPosition = initialPos; + int pos = initialPos + Short.BYTES * 2; /* Save room for length and leaf type. */ + pos = computeSize(pos); + pos = alignPadded4(null, pos); + return pos; + } + + int computeFullContents(byte[] buffer, int initialPos) { + int pos = initialPos + Short.BYTES; /* Save room for length short. */ + pos = CVUtil.putShort(type, buffer, pos); + pos = computeContents(buffer, pos); + /* Length does not include record length (2 bytes)) but does include end padding. */ + pos = alignPadded4(buffer, pos); + int length = (short) (pos - initialPos - Short.BYTES); + CVUtil.putShort((short) length, buffer, initialPos); + return pos; + } + + protected abstract int computeSize(int initialPos); + + protected abstract int computeContents(byte[] buffer, int initialPos); + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return this.type == ((CVTypeRecord) obj).type; + } + + @Override + public abstract int hashCode(); + + @Override + public String toString() { + return String.format("CVTypeRecord seq=0x%04x type=0x%04x pos=0x%04x ", sequenceNumber, type, startPosition); + } + + private static int alignPadded4(byte[] buffer, int originalpos) { + int pos = originalpos; + int align = pos & 3; + if (align == 1) { + byte[] p3 = {LF_PAD3, LF_PAD2, LF_PAD1}; + pos = CVUtil.putBytes(p3, buffer, pos); + } else if (align == 2) { + pos = CVUtil.putByte(LF_PAD2, buffer, pos); + pos = CVUtil.putByte(LF_PAD1, buffer, pos); + } else if (align == 3) { + pos = CVUtil.putByte(LF_PAD1, buffer, pos); + } + return pos; + } + + static final class CVTypeProcedureRecord extends CVTypeRecord { + + int returnType = -1; + CVTypeArglistRecord argList = null; + + CVTypeProcedureRecord() { + super(LF_PROCEDURE); + } + + public CVTypeProcedureRecord returnType(int leaf) { + this.returnType = leaf; + return this; + } + + public CVTypeProcedureRecord returnType(CVTypeRecord leaf) { + this.returnType = leaf.getSequenceNumber(); + return this; + } + + CVTypeProcedureRecord argList(CVTypeArglistRecord leaf) { + this.argList = leaf; + return this; + } + + @Override + public int computeSize(int initialPos) { + return computeContents(null, initialPos); + } + + @Override + public int computeContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putInt(returnType, buffer, initialPos); + pos = CVUtil.putByte((byte) 0, buffer, pos); /* callType */ + pos = CVUtil.putByte((byte) 0, buffer, pos); /* funcAttr */ + pos = CVUtil.putShort((short) argList.getSize(), buffer, pos); + pos = CVUtil.putInt(argList.getSequenceNumber(), buffer, pos); + return pos; + } + + @Override + public String toString() { + return String.format("LF_PROCEDURE 0x%04x ret=0x%04x arg=0x%04x ", getSequenceNumber(), returnType, argList.getSequenceNumber()); + } + + @Override + public int hashCode() { + int h = type; + h = 31 * h + returnType; + h = 31 * h + argList.hashCode(); + /* callType and funcAttr are always zero so do not add them to the hash */ + return h; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + CVTypeProcedureRecord other = (CVTypeProcedureRecord) obj; + return this.returnType == other.returnType && this.argList == other.argList; + } + } + + static final class CVTypeArglistRecord extends CVTypeRecord { + + ArrayList args = new ArrayList<>(); + + CVTypeArglistRecord() { + super(LF_ARGLIST); + } + + CVTypeArglistRecord add(int argType) { + args.add(argType); + return this; + } + + @Override + public int computeSize(int initialPos) { + return initialPos + Integer.BYTES + Integer.BYTES * args.size(); + } + + @Override + public int computeContents(byte[] buffer, int initialPos) { + int pos = CVUtil.putInt(args.size(), buffer, initialPos); + for (Integer at : args) { + pos = CVUtil.putInt(at, buffer, pos); + } + return pos; + } + + int getSize() { + return args.size(); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(String.format("LF_ARGLIST 0x%04x [", getSequenceNumber())); + for (Integer at : args) { + s.append(String.format(" 0x%04x", at)); + } + s.append("])"); + return s.toString(); + } + + @Override + public int hashCode() { + return type * 31 + args.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + CVTypeArglistRecord other = (CVTypeArglistRecord) obj; + return this.args.equals(other.args); + } + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionImpl.java new file mode 100644 index 000000000000..ac22c2be305f --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionImpl.java @@ -0,0 +1,132 @@ +/* + * 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.pecoff.cv; + +import com.oracle.objectfile.BuildDependency; +import com.oracle.objectfile.LayoutDecision; +import com.oracle.objectfile.LayoutDecisionMap; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.pecoff.PECoffObjectFile; +import org.graalvm.compiler.debug.DebugContext; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_SIGNATURE_C13; +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_SYMBOL_SECTION_NAME; +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_TYPE_SECTION_NAME; + +public final class CVTypeSectionImpl extends CVSectionImpl { + + private static final int CV_RECORD_INITIAL_CAPACITY = 200; + + /* CodeView 4 type records below 1000 are pre-defined. */ + private int sequenceCounter = 0x1000; + + /* A sequential map of type records, starting at 1000 */ + private Map typeMap = new LinkedHashMap<>(CV_RECORD_INITIAL_CAPACITY); + + CVTypeSectionImpl() { + } + + @Override + public String getSectionName() { + return CV_TYPE_SECTION_NAME; + } + + @Override + public void createContent(DebugContext debugContext) { + int pos = 0; + enableLog(debugContext); + log(debugContext, "CVTypeSectionImpl.createContent() adding records"); + addRecords(); + log(debugContext, "CVTypeSectionImpl.createContent() start"); + pos = CVUtil.putInt(CV_SIGNATURE_C13, null, pos); + for (CVTypeRecord record : typeMap.values()) { + pos = record.computeFullSize(pos); + } + byte[] buffer = new byte[pos]; + super.setContent(buffer); + log(debugContext, "CVTypeSectionImpl.createContent() end"); + } + + @Override + public void writeContent(DebugContext debugContext) { + int pos = 0; + enableLog(debugContext); + log(debugContext, "CVTypeSectionImpl.writeContent() start"); + byte[] buffer = getContent(); + verboseLog(debugContext, " [0x%08x] CV_SIGNATURE_C13", pos); + pos = CVUtil.putInt(CV_SIGNATURE_C13, buffer, pos); + for (CVTypeRecord record : typeMap.values()) { + verboseLog(debugContext, " [0x%08x] 0x%06x %s", pos, record.getSequenceNumber(), record.toString()); + pos = record.computeFullContents(buffer, pos); + } + verboseLog(debugContext, "CVTypeSectionImpl.writeContent() end"); + } + + /** + * Add all relevant type records to the type section. + */ + private void addRecords() { + /* if an external PDB file is generated, add CVTypeServer2Record */ + /* for each class, add all members, types, etc */ + } + + /** + * Return either the caller-created instance or a matching existing instance. Every entry in + * typeMap is a T, because it is ONLY this function which inserts entries (of type T). + * + * @param type of new record + * @param newRecord record to add if an existing record with same hash hasn't already been added + * @return the record (if previously unseen) or old record + */ + @SuppressWarnings("unchecked") + public T addOrReference(T newRecord) { + final T record; + if (typeMap.containsKey(newRecord)) { + record = (T) typeMap.get(newRecord); + } else { + newRecord.setSequenceNumber(sequenceCounter++); + typeMap.put(newRecord, newRecord); + record = newRecord; + } + return record; + } + + @Override + public Set getDependencies(Map decisions) { + Set deps = super.getDependencies(decisions); + PECoffObjectFile.PECoffSection targetSection = (PECoffObjectFile.PECoffSection) getElement().getOwner().elementForName(CV_SYMBOL_SECTION_NAME); + LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT); + /* Make our content depend on the codeview symbol section. */ + deps.add(BuildDependency.createOrGet(ourContent, decisions.get(targetSection).getDecision(LayoutDecision.Kind.CONTENT))); + + return deps; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVUtil.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVUtil.java new file mode 100644 index 000000000000..d825143c67d1 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVUtil.java @@ -0,0 +1,118 @@ +/* + * 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.pecoff.cv; + +import com.oracle.objectfile.io.Utf8; + +import static java.nio.charset.StandardCharsets.UTF_8; + +abstract class CVUtil { + + static int putByte(byte b, byte[] buffer, int initialPos) { + if (buffer == null) { + return initialPos + Byte.BYTES; + } + int pos = initialPos; + buffer[pos++] = b; + return pos; + } + + static int putShort(short s, byte[] buffer, int initialPos) { + if (buffer == null) { + return initialPos + Short.BYTES; + } + int pos = initialPos; + buffer[pos++] = (byte) (s & 0xff); + buffer[pos++] = (byte) ((s >> 8) & 0xff); + return pos; + } + + static int putInt(int i, byte[] buffer, int initialPos) { + if (buffer == null) { + return initialPos + Integer.BYTES; + } + int pos = initialPos; + buffer[pos++] = (byte) (i & 0xff); + buffer[pos++] = (byte) ((i >> 8) & 0xff); + buffer[pos++] = (byte) ((i >> 16) & 0xff); + buffer[pos++] = (byte) ((i >> 24) & 0xff); + return pos; + } + + @SuppressWarnings("unused") + static int putLong(long l, byte[] buffer, int initialPos) { + if (buffer == null) { + return initialPos + Long.BYTES; + } + int pos = initialPos; + 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); + return pos; + } + + static int putBytes(byte[] inbuff, byte[] buffer, int initialPos) { + if (buffer == null) { + return initialPos + inbuff.length; + } + int pos = initialPos; + for (byte b : inbuff) { + buffer[pos++] = b; + } + return pos; + } + + static int putUTF8StringBytes(String s, byte[] buffer, int initialPos) { + assert !s.contains("\0"); + if (buffer == null) { + return initialPos + Utf8.utf8Length(s) + 1; + } + byte[] buff = s.getBytes(UTF_8); + int pos = putBytes(buff, buffer, initialPos); + buffer[pos++] = '\0'; + return pos; + } + + /** + * Align on 4 byte boundary. + * + * @param initialPos initial unaligned position + * @return pos aligned on 4 byte boundary + */ + static int align4(int initialPos) { + int pos = initialPos; + while ((pos & 0x3) != 0) { + pos++; + } + return pos; + } +} diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/FirstObjectTable.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/FirstObjectTable.java index 5da949f6dce5..736518029a29 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/FirstObjectTable.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/FirstObjectTable.java @@ -154,7 +154,7 @@ public final class FirstObjectTable { /** * The bias for exponential entry offsets to distinguish them from linear offset entries. - * + * * The limit for {@link #EXPONENT_MAX} seems to be that for the maximal object of 2^64 bytes, I * never have to skip back more than (2^64)/{@link #ENTRY_SIZE_BYTES} entries, so the maximum * exponent I'll need is 55. So the EXPONENT_BIAS just has to leave enough room for that many @@ -167,7 +167,7 @@ public final class FirstObjectTable { * In fact, the current largest object that can be allocated is an array of long (or double, or * Object) of size Integer.MAX_VALUE, so the largest skip back is (2^35)/512 or 2^24. That * leaves lots of room for special values. - * + * */ private static final int EXPONENT_BIAS = 1 + LINEAR_OFFSET_MAX - EXPONENT_MIN; 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 ea9197cb6faf..510640cd7de8 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 @@ -444,15 +444,15 @@ protected void onValueUpdate(EconomicMap, Object> values, Integer o }; private static void defaultDebugInfoValueUpdateHandler(EconomicMap, Object> values, @SuppressWarnings("unused") Integer oldValue, Integer newValue) { - // force update of TrackNodeSourcePosition - if (newValue > 0 && !Boolean.TRUE.equals(values.get(TrackNodeSourcePosition))) { - TrackNodeSourcePosition.update(values, true); - } + // force update of TrackNodeSourcePosition and DeleteLocalSymbols + TrackNodeSourcePosition.update(values, newValue > 0); + DeleteLocalSymbols.update(values, newValue == 0); } @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) { }; + @Option(help = "Directory under which to create source file cache for Application or GraalVM classes")// public static final HostedOptionKey DebugInfoSourceCacheRoot = new HostedOptionKey<>("sources"); 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 index 6ca2614bea11..211865717b70 100644 --- 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 @@ -160,6 +160,11 @@ public String methodName() { return method.format("%n"); } + @Override + public String symbolNameForMethod() { + return NativeBootImage.localSymbolNameForMethod(method); + } + @Override public String paramNames() { return method.format("%P"); @@ -289,6 +294,11 @@ public String methodName() { return method.format("%n"); } + @Override + public String symbolNameForMethod() { + return NativeBootImage.localSymbolNameForMethod(method); + } + @Override public int addressLo() { return lo;