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