From 5d3660ffb8a827a286192e1aed03e5709b87f50e Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:35:46 +0800 Subject: [PATCH] =?UTF-8?q?Enable=20HMCL=20to=20export=20jstack=20dump=20f?= =?UTF-8?q?ile=20=E8=AE=A9=20HMCL=20=E8=83=BD=E5=A4=9F=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E8=BF=90=E8=A1=8C=E6=A0=88=E6=96=87=E4=BB=B6?= =?UTF-8?q?=20(#2582)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable HMCL to create game thread dump while game is running * Fix checkstyle * Hide accessToken * Code cleanup * Code cleanup * Enhance I18N and declare the charset (UTF-8) of output file * Inline variables * Update the modifier of org.jackhuang.hmcl.game.GameDumpCreator#writeDumpHeadTo from public to private * Refactor * Add license for GameDumpCreator, remove support for Java 8 * Remove unnecessary Arrays.copyOf * Fix checkstyle * Use system charset to read the inputstream from JVM * opt GameDumpCreator * retry on failed attach to vm * update GameDumpCreator * Opt GameDumpCreator * Fix * Include BCIG * Use BCIG to get PID. * Fix. * Fix again. * Code cleanup. Fix bugs. --------- Co-authored-by: Glavo --- HMCL/build.gradle.kts | 3 +- .../jackhuang/hmcl/game/LauncherHelper.java | 2 +- .../jackhuang/hmcl/ui/GameCrashWindow.java | 2 +- .../java/org/jackhuang/hmcl/ui/LogWindow.java | 59 +++++- .../resources/assets/lang/I18N.properties | 4 + .../resources/assets/lang/I18N_es.properties | 5 + .../resources/assets/lang/I18N_zh.properties | 4 + .../assets/lang/I18N_zh_CN.properties | 4 + HMCLCore/build.gradle.kts | 18 ++ .../hmcl/game/GameDumpGenerator.java | 174 ++++++++++++++++++ .../hmcl/util/platform/ManagedProcess.java | 56 +++++- .../hmcl/util/platform/SystemUtils.java | 6 +- build.gradle.kts | 1 + 13 files changed, 326 insertions(+), 12 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameDumpGenerator.java diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 19aae7277..3577fbc57 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -208,7 +208,8 @@ tasks.getByName("sha "javafx.graphics/com.sun.prism", "javafx.controls/com.sun.javafx.scene.control", "javafx.controls/com.sun.javafx.scene.control.behavior", - "javafx.controls/javafx.scene.control.skin" + "javafx.controls/javafx.scene.control.skin", + "jdk.attach/sun.tools.attach" ).joinToString(" ") ) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 13989d727..effec9df8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -747,7 +747,7 @@ public final class LauncherHelper { if (showLogs) Platform.runLater(() -> { - logWindow = new LogWindow(); + logWindow = new LogWindow(process); logWindow.showNormal(); logWindowLatch.countDown(); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java index cf6709b97..9b719766c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java @@ -267,7 +267,7 @@ public class GameCrashWindow extends Stage { } private void showLogWindow() { - LogWindow logWindow = new LogWindow(); + LogWindow logWindow = new LogWindow(managedProcess); logWindow.logLine(Logging.filterForbiddenToken("Command: " + new CommandBuilder().addAll(managedProcess.getCommands())), Log4jLevel.INFO); if (managedProcess.getClasspath() != null) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 4af43cac8..798e25d83 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -36,13 +36,16 @@ import javafx.scene.control.*; import javafx.scene.input.KeyCode; import javafx.scene.layout.*; import javafx.stage.Stage; +import org.jackhuang.hmcl.game.GameDumpGenerator; import org.jackhuang.hmcl.game.LauncherHelper; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.CircularArrayList; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Log4jLevel; +import org.jackhuang.hmcl.util.platform.ManagedProcess; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; import java.io.IOException; import java.nio.file.Files; @@ -64,7 +67,6 @@ import static org.jackhuang.hmcl.util.StringUtils.parseEscapeSequence; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; /** - * * @author huangyuhui */ public final class LogWindow extends Stage { @@ -90,13 +92,17 @@ public final class LogWindow extends Stage { private boolean stopCheckLogCount = false; - public LogWindow() { + private final ManagedProcess gameProcess; + + public LogWindow(ManagedProcess gameProcess) { setScene(new Scene(impl, 800, 480)); getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); setTitle(i18n("logwindow.title")); getIcons().add(newBuiltinImage("/assets/img/icon.png")); levelShownMap.values().forEach(property -> property.addListener((a, b, newValue) -> shakeLogs())); + + this.gameProcess = gameProcess; } public void logLine(String filteredLine, Log4jLevel level) { @@ -219,6 +225,35 @@ public final class LogWindow extends Stage { }); } + private void onExportDump(JFXButton button) { + thread(() -> { + if (button.getText().equals(i18n("logwindow.export_dump.dependency_ok.button"))) { + if (SystemUtils.supportJVMAttachment()) { + Platform.runLater(() -> button.setText(i18n("logwindow.export_dump.dependency_ok.doing_button"))); + + Path dumpFile = Paths.get("minecraft-exported-jstack-dump-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath(); + + try { + if (gameProcess.isRunning()) { + GameDumpGenerator.writeDumpTo(gameProcess.getPID(), dumpFile); + FXUtils.showFileInExplorer(dumpFile); + } + } catch (Throwable e) { + LOG.log(Level.WARNING, "Failed to create minecraft jstack dump", e); + + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump.dependency_ok.button")); + alert.setTitle(i18n("message.error")); + alert.showAndWait(); + }); + } + + Platform.runLater(() -> button.setText(i18n("logwindow.export_dump.dependency_ok.button"))); + } + } + }); + } + private void onExportGameCrashInfo() { if (exportGameCrashInfoCallback == null) return; exportGameCrashInfoCallback.accept(logs.stream().map(x -> x.log).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR))); @@ -303,7 +338,7 @@ public final class LogWindow extends Stage { listView.setCellFactory(x -> new ListCell() { { getStyleClass().add("log-window-list-cell"); - Region clippedContainer = (Region)listView.lookup(".clipped-container"); + Region clippedContainer = (Region) listView.lookup(".clipped-container"); if (clippedContainer != null) { maxWidthProperty().bind(clippedContainer.widthProperty()); prefWidthProperty().bind(clippedContainer.widthProperty()); @@ -398,15 +433,25 @@ public final class LogWindow extends Stage { autoScrollCheckBox.setSelected(true); control.autoScroll.bind(autoScrollCheckBox.selectedProperty()); - JFXButton terminateButton = new JFXButton(i18n("logwindow.terminate_game")); - terminateButton.setOnMouseClicked(e -> getSkinnable().onTerminateGame()); - JFXButton exportLogsButton = new JFXButton(i18n("button.export")); exportLogsButton.setOnMouseClicked(e -> getSkinnable().onExportLogs()); + JFXButton terminateButton = new JFXButton(i18n("logwindow.terminate_game")); + terminateButton.setOnMouseClicked(e -> getSkinnable().onTerminateGame()); + + JFXButton exportDumpButton = new JFXButton(); + if (SystemUtils.supportJVMAttachment()) { + exportDumpButton.setText(i18n("logwindow.export_dump.dependency_ok.button")); + exportDumpButton.setOnAction(e -> getSkinnable().onExportDump(exportDumpButton)); + } else { + exportDumpButton.setText(i18n("logwindow.export_dump.no_dependency.button")); + exportDumpButton.setTooltip(new Tooltip(i18n("logwindow.export_dump.no_dependency.tooltip"))); + exportDumpButton.setDisable(true); + } + JFXButton clearButton = new JFXButton(i18n("button.clear")); clearButton.setOnMouseClicked(e -> getSkinnable().onClear()); - hBox.getChildren().setAll(autoScrollCheckBox, exportLogsButton, terminateButton, clearButton); + hBox.getChildren().setAll(autoScrollCheckBox, exportLogsButton, terminateButton, exportDumpButton, clearButton); vbox.getChildren().add(bottom); } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 8226870e7..5bb71f849 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -723,6 +723,10 @@ logwindow.title=Log logwindow.help=You can go to the HMCL community and find others to help logwindow.autoscroll=Auto-scroll logwindow.export_game_crash_logs=Export Crash Logs +logwindow.export_dump.dependency_ok.button=Export Game Stack Dump +logwindow.export_dump.dependency_ok.doing_button=Exporting Game Stack Dump (May take up to 15 seconds) +logwindow.export_dump.no_dependency.button=Export Game Stack Dump (Not compatible) +logwindow.export_dump.no_dependency.tooltip=Your Java does not contain the dependencies to create the stack dump. Please turn to HMCL KOOK or HMCL Discord for help. main_page=Home diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 14ceefc3a..80d32dd4d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -668,6 +668,11 @@ logwindow.title=Registro logwindow.help=Puede ir a la comunidad HMCL y encontrar a otros para ayudar logwindow.autoscroll=Desplazamiento automático logwindow.export_game_crash_logs=Exportar registros de errores +logwindow.export_dump.dependency_ok.button=Exportar volcado de pila de juegos +logwindow.export_dump.dependency_ok.doing_button=Exportación de volcado de pila de juego (puede tardar hasta 15 segundos) +logwindow.export_dump.no_dependency.button=Exportar volcado de pila de juegos (no compatible) +logwindow.export_dump.no_dependency.tooltip=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a HMCL KOOK o HMCL Discord para obtener ayuda. + main_page=Inicio diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 45c5e535b..df3792210 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -597,6 +597,10 @@ logwindow.title=記錄 logwindow.help=你可以前往 HMCL 社區,尋找他人幫助 logwindow.autoscroll=自動滾動 logwindow.export_game_crash_logs=導出遊戲崩潰訊息 +logwindow.export_dump.dependency_ok.button=導出遊戲運行棧 +logwindow.export_dump.dependency_ok.doing_button=正在導出遊戲運行棧(可能需要 15 秒) +logwindow.export_dump.no_dependency.button=導出遊戲運行棧(不兼容) +logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用於創建遊戲運行棧的依賴。請前往 HMCL KOOK 或 HMCL Discord 尋求幫助。 main_page=首頁 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 95a2b0f98..48e8fd130 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -596,6 +596,10 @@ logwindow.title=日志 logwindow.help=你可以前往 HMCL 社区,寻找他人帮助 logwindow.autoscroll=自动滚动 logwindow.export_game_crash_logs=导出游戏崩溃信息 +logwindow.export_dump.dependency_ok.button=导出游戏运行栈 +logwindow.export_dump.dependency_ok.doing_button=正在导出游戏运行栈(可能需要 15 秒) +logwindow.export_dump.no_dependency.button=导出游戏运行栈(不兼容) +logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL KOOK 或 HMCL Discord 寻求帮助。 main_page=主页 diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index 6d69373e8..b55b29e2b 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -1,3 +1,5 @@ +import kotlin.streams.toList + plugins { `java-library` } @@ -13,4 +15,20 @@ dependencies { api("org.nanohttpd:nanohttpd:2.3.1") api("org.apache.commons:commons-compress:1.23.0") compileOnlyApi("org.jetbrains:annotations:24.0.1") + compileOnlyApi("com.github.burningtnt:BytecodeImplGenerator:b45b6638eeaeb903aa22ea947d37c45e5716a18c") +} + +tasks.getByName("compileJava") { + val bytecodeClasses = listOf( + "org/jackhuang/hmcl/util/platform/ManagedProcess" + ) + + doLast { + javaexec { + classpath(project.sourceSets["main"].compileClasspath) + mainClass.set("net.burningtnt.bcigenerator.BytecodeImplGenerator") + System.getProperty("bci.debug.address")?.let { address -> jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,address=$address,suspend=y") } + args(bytecodeClasses.stream().map { s -> project.layout.buildDirectory.file("classes/java/main/$s.class").get().asFile.path }.toList()) + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameDumpGenerator.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameDumpGenerator.java new file mode 100644 index 000000000..984cdfd79 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameDumpGenerator.java @@ -0,0 +1,174 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jackhuang.hmcl.game; + +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.platform.OperatingSystem; + +import java.io.*; +import java.nio.CharBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Level; + +import static org.jackhuang.hmcl.util.Logging.LOG; + +/** + * Generate a JVM dump on a process. + * WARNING: Initializing this class may cause NoClassDefFoundError. + */ +public final class GameDumpGenerator { + private GameDumpGenerator() { + } + + private static final int TOOL_VERSION = 9; + + private static final int DUMP_TIME = 3; + + private static final int RETRY_TIME = 3; + + public static void writeDumpTo(long pid, Path path) throws IOException, InterruptedException { + try (Writer writer = Files.newBufferedWriter(path)) { + // On a local machine, the lvmid and the pid are the same. + VirtualMachine vm = attachVM(String.valueOf(pid), writer); + + try { + writeDumpHeadTo(vm, writer); + + for (int i = 0; i < DUMP_TIME; i++) { + if (i > 0) + Thread.sleep(3000); + + writer.write("====================\n"); + writeDumpBodyTo(vm, writer); + } + } finally { + vm.detach(); + } + } + } + + private static void writeDumpHeadTo(VirtualMachine vm, Writer writer) throws IOException { + writer.write("===== Minecraft JStack Dump =====\n"); + + writeDumpHeadKeyValueTo("Tool Version", String.valueOf(TOOL_VERSION), writer, false); + writeDumpHeadKeyValueTo("VM PID", vm.id(), writer, false); + + StringBuilder stringBuilder = new StringBuilder(); + { + execute(vm, "VM.command_line", stringBuilder); + writeDumpHeadKeyValueTo( + "VM Command Line", + Logging.filterForbiddenToken(stringBuilder.toString()), + writer, + true + ); + } + { + stringBuilder.setLength(0); + execute(vm, "VM.version", stringBuilder); + writeDumpHeadKeyValueTo("VM Version", stringBuilder.toString(), writer, true); + } + + writer.write("\n\n"); + } + + public static void writeDumpHeadKeyValueTo(String key, String value, Writer writer, boolean multiline) throws IOException { + writer.write(key); + writer.write(':'); + writer.write(' '); + + if (multiline) { + writer.write('{'); + writer.write('\n'); + + int lineStart = 0; + int lineEnd = value.indexOf("\n", lineStart); + + while (true) { + if (lineEnd == -1) { + if (lineStart < value.length()) { + writer.write(" "); + writer.write(value, lineStart, value.length() - lineStart); + writer.write('\n'); + } + break; + } else { + writer.write(" "); + writer.write(value, lineStart, lineEnd - lineStart); + writer.write('\n'); + lineStart = lineEnd + 1; + lineEnd = value.indexOf("\n", lineStart); + } + } + + writer.write('}'); + } else { + writer.write(value); + } + writer.write('\n'); + } + + private static void writeDumpBodyTo(VirtualMachine vm, Writer writer) throws IOException { + execute(vm, "Thread.print", writer); + } + + private static VirtualMachine attachVM(String lvmid, Writer writer) throws IOException, InterruptedException { + for (int i = 0; i < RETRY_TIME; i++) { + try { + return VirtualMachine.attach(lvmid); + } catch (Throwable e) { + LOG.log(Level.WARNING, "An exception encountered while attaching vm " + lvmid, e); + writer.write(StringUtils.getStackTrace(e)); + writer.write('\n'); + Thread.sleep(3000); + } + } + + String message = "Cannot attach VM " + lvmid; + writer.write(message); + throw new IOException(message); + } + + private static void execute(VirtualMachine vm, String command, Appendable target) throws IOException { + try (Reader reader = new InputStreamReader(executeJVMCommand(vm, command), OperatingSystem.NATIVE_CHARSET)) { + char[] data = new char[256]; + CharBuffer cb = CharBuffer.wrap(data); + int len; + while ((len = reader.read(data)) > 0) { // Directly read the data into a CharBuffer would cause useless array copy actions. + target.append(cb, 0, len); + } + } catch (Throwable throwable) { + LOG.log(Level.WARNING, "An exception encountered while executing jcmd " + vm.id(), throwable); + target.append(StringUtils.getStackTrace(throwable)); + target.append('\n'); + } + } + + private static InputStream executeJVMCommand(VirtualMachine vm, String command) throws IOException, AttachNotSupportedException { + if (vm instanceof sun.tools.attach.HotSpotVirtualMachine) { + return ((sun.tools.attach.HotSpotVirtualMachine) vm).executeJCmd(command); + } else { + throw new AttachNotSupportedException("Unsupported VM implementation " + vm.getClass().getName()); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java index cd2ff1080..29dcb038f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java @@ -17,10 +17,13 @@ */ package org.jackhuang.hmcl.util.platform; +import net.burningtnt.bcigenerator.api.BytecodeImpl; +import net.burningtnt.bcigenerator.api.BytecodeImplError; import org.jackhuang.hmcl.launch.StreamPump; import org.jackhuang.hmcl.util.Lang; import java.io.IOException; +import java.lang.reflect.Field; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -33,7 +36,6 @@ import java.util.function.Predicate; * @see org.jackhuang.hmcl.launch.StreamPump */ public class ManagedProcess { - private final Process process; private final List commands; private final String classpath; @@ -81,6 +83,58 @@ public class ManagedProcess { return process; } + /** + * The PID of the raw system process + * + * @throws UnsupportedOperationException if current Java environment is not supported. + * @return PID + */ + public long getPID() throws UnsupportedOperationException { + if (JavaVersion.CURRENT_JAVA.getParsedVersion() >= 9) { + // Method Process.pid() is provided (Java 9 or later). Invoke it to get the pid. + return getPID0(process); + } else { + // Method Process.pid() is not provided. (Java 8). + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { + // On Windows, we can invoke method Process.pid() to get the pid. + // However, this method is supplied since Java 9. + // So, there is no ways to get the pid. + throw new UnsupportedOperationException("Cannot get the pid of a Process on Java 8 on Windows."); + } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX || OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { + // On Linux or Mac, we can get field UnixProcess.pid field to get the pid. + // All the Java version is accepted. + // See https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/solaris/classes/java/lang/UNIXProcess.java.linux + try { + Field pidField = process.getClass().getDeclaredField("pid"); + pidField.setAccessible(true); + return pidField.getInt(process); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new UnsupportedOperationException("Cannot get the pid of a Process on Java 8 on OSX / Linux.", e); + } + } else { + // Unknown Operating System, no fallback available. + throw new UnsupportedOperationException(String.format("Cannot get the pid of a Process on Java 8 on Unknown Operating System (%s).", System.getProperty("os.name"))); + } + } + } + + /** + * Get the PID of a process with BytecodeImplGenerator + */ + @BytecodeImpl({ + "LABEL METHOD_HEAD", + "ALOAD 0", + "INVOKEVIRTUAL Ljava/lang/Process;pid()J", + "LABEL RELEASE_PARAMETER", + "LRETURN", + "LOCALVARIABLE process [Ljava/lang/Process; METHOD_HEAD RELEASE_PARAMETER 0", + "MAXS 2 1" + }) + @SuppressWarnings("unused") + private static long getPID0(Process process) { + throw new BytecodeImplError(); + } + /** * The command line. * diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java index 69bae15dc..791170f00 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java @@ -41,8 +41,12 @@ public final class SystemUtils { return managedProcess.getProcess().waitFor(); } + public static boolean supportJVMAttachment() { + return JavaVersion.CURRENT_JAVA.getParsedVersion() >= 9 + && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null; + } + private static void onLogLine(String log) { LOG.info(log); } - } diff --git a/build.gradle.kts b/build.gradle.kts index 071f52bca..50a4f2c00 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ subprojects { } mavenCentral() maven(url = "https://jitpack.io") + maven(url = "https://libraries.minecraft.net") } tasks.withType {