mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-08-04 03:46:57 -04:00
Enable HMCL to export jstack dump file 让 HMCL 能够导出游戏运行栈文件 (#2582)
* 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 <zjx001202@gmail.com>
This commit is contained in:
parent
4149876e04
commit
5d3660ffb8
@ -208,7 +208,8 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("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(" ")
|
||||
)
|
||||
|
||||
|
@ -747,7 +747,7 @@ public final class LauncherHelper {
|
||||
|
||||
if (showLogs)
|
||||
Platform.runLater(() -> {
|
||||
logWindow = new LogWindow();
|
||||
logWindow = new LogWindow(process);
|
||||
logWindow.showNormal();
|
||||
logWindowLatch.countDown();
|
||||
});
|
||||
|
@ -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)
|
||||
|
@ -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)));
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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=首頁
|
||||
|
||||
|
@ -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=主页
|
||||
|
||||
|
@ -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<JavaCompile>("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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> 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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ subprojects {
|
||||
}
|
||||
mavenCentral()
|
||||
maven(url = "https://jitpack.io")
|
||||
maven(url = "https://libraries.minecraft.net")
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user