1 /* 2 * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.hotspot.meta; 26 27 import java.io.BufferedReader; 28 import java.io.File; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.InputStreamReader; 33 import java.util.HashMap; 34 import java.util.Map; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 38 import org.graalvm.compiler.code.CompilationResult; 39 import org.graalvm.compiler.code.CompilationResult.CodeAnnotation; 40 import org.graalvm.compiler.code.DisassemblerProvider; 41 import org.graalvm.compiler.serviceprovider.ServiceProvider; 42 import org.graalvm.util.CollectionsUtil; 43 44 import jdk.vm.ci.code.CodeCacheProvider; 45 import jdk.vm.ci.code.CodeUtil; 46 import jdk.vm.ci.code.CodeUtil.DefaultRefMapFormatter; 47 import jdk.vm.ci.code.CodeUtil.RefMapFormatter; 48 import jdk.vm.ci.code.InstalledCode; 49 import jdk.vm.ci.code.Register; 50 import jdk.vm.ci.code.RegisterConfig; 51 import jdk.vm.ci.code.TargetDescription; 52 import jdk.vm.ci.code.site.Call; 53 import jdk.vm.ci.code.site.DataPatch; 54 import jdk.vm.ci.code.site.Infopoint; 55 import jdk.vm.ci.hotspot.HotSpotCodeCacheProvider; 56 import jdk.vm.ci.services.Services; 57 58 /** 59 * A provider that uses the {@code GNU objdump} utility to disassemble code. 60 */ 61 @ServiceProvider(DisassemblerProvider.class) 62 public class HotSpotObjdumpDisassemblerProvider extends HotSpotDisassemblerProvider { 63 64 private final String objdump = getObjdump(); 65 66 @Override disassembleCompiledCode(CodeCacheProvider codeCache, CompilationResult compResult)67 public String disassembleCompiledCode(CodeCacheProvider codeCache, CompilationResult compResult) { 68 if (objdump == null) { 69 return null; 70 } 71 File tmp = null; 72 try { 73 tmp = File.createTempFile("compiledBinary", ".bin"); 74 try (FileOutputStream fos = new FileOutputStream(tmp)) { 75 fos.write(compResult.getTargetCode()); 76 } 77 78 String[] cmdline; 79 String arch = Services.getSavedProperties().get("os.arch"); 80 if (arch.equals("amd64") || arch.equals("x86_64")) { 81 cmdline = new String[]{objdump, "-D", "-b", "binary", "-M", "x86-64", "-m", "i386", tmp.getAbsolutePath()}; 82 } else if (arch.equals("aarch64")) { 83 cmdline = new String[]{objdump, "-D", "-b", "binary", "-m", "aarch64", tmp.getAbsolutePath()}; 84 } else { 85 return null; 86 } 87 88 Pattern p = Pattern.compile(" *(([0-9a-fA-F]+):\t.*)"); 89 90 TargetDescription target = codeCache.getTarget(); 91 RegisterConfig regConfig = codeCache.getRegisterConfig(); 92 Register fp = regConfig.getFrameRegister(); 93 RefMapFormatter slotFormatter = new DefaultRefMapFormatter(target.wordSize, fp, 0); 94 95 Map<Integer, String> annotations = new HashMap<>(); 96 for (DataPatch site : compResult.getDataPatches()) { 97 putAnnotation(annotations, site.pcOffset, "{" + site.reference.toString() + "}"); 98 } 99 for (CompilationResult.CodeMark mark : compResult.getMarks()) { 100 putAnnotation(annotations, mark.pcOffset, mark.id.getName()); 101 } 102 for (CodeAnnotation a : compResult.getCodeAnnotations()) { 103 putAnnotation(annotations, a.getPosition(), a.toString()); 104 } 105 for (Infopoint infopoint : compResult.getInfopoints()) { 106 if (infopoint instanceof Call) { 107 Call call = (Call) infopoint; 108 if (call.debugInfo != null) { 109 putAnnotation(annotations, call.pcOffset + call.size, CodeUtil.append(new StringBuilder(100), call.debugInfo, slotFormatter).toString()); 110 } 111 putAnnotation(annotations, call.pcOffset, "{" + codeCache.getTargetName(call) + "}"); 112 } else { 113 if (infopoint.debugInfo != null) { 114 putAnnotation(annotations, infopoint.pcOffset, CodeUtil.append(new StringBuilder(100), infopoint.debugInfo, slotFormatter).toString()); 115 } 116 putAnnotation(annotations, infopoint.pcOffset, "{infopoint: " + infopoint.reason + "}"); 117 } 118 } 119 120 Process proc = Runtime.getRuntime().exec(cmdline); 121 InputStream is = proc.getInputStream(); 122 StringBuilder sb = new StringBuilder(); 123 124 InputStreamReader isr = new InputStreamReader(is); 125 try (BufferedReader br = new BufferedReader(isr)) { 126 String line; 127 while ((line = br.readLine()) != null) { 128 Matcher m = p.matcher(line); 129 if (m.find()) { 130 int address = Integer.parseInt(m.group(2), 16); 131 String annotation = annotations.get(address); 132 if (annotation != null) { 133 annotation = annotation.replace("\n", "\n; "); 134 sb.append("; ").append(annotation).append('\n'); 135 } 136 line = m.replaceAll("0x$1"); 137 } 138 sb.append(line).append("\n"); 139 } 140 } 141 try (BufferedReader ebr = new BufferedReader(new InputStreamReader(proc.getErrorStream()))) { 142 String errLine = ebr.readLine(); 143 if (errLine != null) { 144 System.err.println("Error output from executing: " + CollectionsUtil.mapAndJoin(cmdline, e -> quoteShellArg(String.valueOf(e)), " ")); 145 System.err.println(errLine); 146 while ((errLine = ebr.readLine()) != null) { 147 System.err.println(errLine); 148 } 149 } 150 } 151 return sb.toString(); 152 } catch (IOException e) { 153 e.printStackTrace(); 154 return null; 155 } finally { 156 if (tmp != null) { 157 tmp.delete(); 158 } 159 } 160 } 161 162 /** 163 * Pattern for a single shell command argument that does not need to quoted. 164 */ 165 private static final Pattern SAFE_SHELL_ARG = Pattern.compile("[A-Za-z0-9@%_\\-\\+=:,\\./]+"); 166 167 /** 168 * Reliably quote a string as a single shell command argument. 169 */ quoteShellArg(String arg)170 public static String quoteShellArg(String arg) { 171 if (arg.isEmpty()) { 172 return "\"\""; 173 } 174 Matcher m = SAFE_SHELL_ARG.matcher(arg); 175 if (m.matches()) { 176 return arg; 177 } 178 // See http://stackoverflow.com/a/1250279 179 return "'" + arg.replace("'", "'\"'\"'") + "'"; 180 } 181 182 /** 183 * Searches for a valid GNU objdump executable. 184 */ getObjdump()185 private static String getObjdump() { 186 // On macOS, `brew install binutils` will provide 187 // an executable named gobjdump 188 for (String candidate : new String[]{"objdump", "gobjdump"}) { 189 try { 190 String[] cmd = {candidate, "--version"}; 191 Process proc = Runtime.getRuntime().exec(cmd); 192 InputStream is = proc.getInputStream(); 193 int exitValue = proc.waitFor(); 194 if (exitValue == 0) { 195 byte[] buf = new byte[is.available()]; 196 int pos = 0; 197 while (pos < buf.length) { 198 int read = is.read(buf, pos, buf.length - pos); 199 pos += read; 200 } 201 String output = new String(buf); 202 if (output.contains("GNU objdump")) { 203 return candidate; 204 } 205 } 206 } catch (IOException | InterruptedException e) { 207 } 208 } 209 return null; 210 } 211 putAnnotation(Map<Integer, String> annotations, int idx, String txt)212 private static void putAnnotation(Map<Integer, String> annotations, int idx, String txt) { 213 String newAnnotation = annotations.getOrDefault(idx, "") + "\n" + txt; 214 annotations.put(idx, newAnnotation); 215 } 216 217 @Override disassembleInstalledCode(CodeCacheProvider codeCache, CompilationResult compResult, InstalledCode code)218 public String disassembleInstalledCode(CodeCacheProvider codeCache, CompilationResult compResult, InstalledCode code) { 219 return ((HotSpotCodeCacheProvider) codeCache).disassemble(code); 220 } 221 222 @Override getName()223 public String getName() { 224 return "hsdis-objdump"; 225 } 226 } 227