package ebpftracer import ( "bufio" "bytes" "fmt" "io" "os" "os/exec" "regexp" "strconv" "strings" "unicode" ) func GetJvmVersion(filePath, libjvmPath string) (string, error) { minLength := 4 var maxFileSize int64 = 3 * 1024 * 1024 // 3MB // 打开文件 sofile, err := os.Open(filePath) if err != nil { return "", fmt.Errorf("failed to open file: %w", err) } defer sofile.Close() // 检查文件大小 fileInfo, err := sofile.Stat() if err != nil { return "", fmt.Errorf("failed to stat file: %w", err) } if fileInfo.Size() > maxFileSize { return "", fmt.Errorf("file size exceeds limit of %d bytes", maxFileSize) } var result []string var buffer []rune // 逐字节读取文件 buf := make([]byte, 4096) // 4KB 缓冲区 for { n, err := sofile.Read(buf) if n > 0 { for _, b := range buf[:n] { // 检查是否是可打印字符 if unicode.IsPrint(rune(b)) { buffer = append(buffer, rune(b)) } else if len(buffer) >= minLength { // 如果当前缓冲区长度满足要求,则存入结果 result = append(result, string(buffer)) buffer = nil } else { // 清空缓冲区 buffer = nil } } } if err != nil { if err.Error() != "EOF" { return "", fmt.Errorf("failed to read file: %w", err) } break } } // 如果缓冲区有剩余字符串,添加到结果中 if len(buffer) >= minLength { result = append(result, string(buffer)) } // 查找 `java.version` 的下一行 keyword := "java.version" for i, str := range result { if str == keyword && i+1 < len(result) { return result[i+1], nil // 返回关键字下一行内容 } } // 其他版本从jvm获取 f, err := os.Open(libjvmPath) if err != nil { return "", fmt.Errorf("failed to open file: %w", err) } defer f.Close() // 读取整个文件(libjvm.so 一般几十 MB 内) br := bufio.NewReader(f) data, err := io.ReadAll(br) if err != nil { return "", fmt.Errorf("failed to read file: %w", err) } if err != nil { return "", fmt.Errorf("failed to read file: %w", err) } text := string(stringsLike(data)) if m := regexp.MustCompile(`JRE \(([^)]+)\)`).FindStringSubmatch(text); len(m) == 2 { return m[1], nil } return "", fmt.Errorf("failed to extract java version from %s", filePath) } func stringsLike(b []byte) []byte { var out bytes.Buffer var buf []byte flush := func() { if len(buf) >= 4 { out.Write(buf) out.WriteByte('\n') } buf = buf[:0] } for _, c := range b { if c >= 32 && c <= 126 { // 可打印 ASCII(不含换行) buf = append(buf, c) } else { flush() } } flush() return out.Bytes() } // 解析版本号为大版本、中间版本和小版本 func ParseVersion(version string) (int, int, int, error) { re := regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)`) matches := re.FindStringSubmatch(version) if len(matches) != 4 { return 0, 0, 0, fmt.Errorf("invalid version format") } major, _ := strconv.Atoi(matches[1]) minor, _ := strconv.Atoi(matches[2]) patch, _ := strconv.Atoi(matches[3]) return major, minor, patch, nil } func parseQuotedString(input string) (string, error) { // 找到第一个双引号的位置 start := strings.Index(input, "\"") if start == -1 { return "", fmt.Errorf("no start quote found") } // 找到第二个双引号的位置 end := strings.Index(input[start+1:], "\"") if end == -1 { return "", fmt.Errorf("no end quote found") } // 提取开始和结束引号之间的内容 content := input[start+1 : start+1+end] return content, nil } // getExecutablePath 根据进程ID获取可执行文件的真实路径。 func getExecutablePath(pid uint32) (string, error) { // 构造文件路径 link := fmt.Sprintf("/proc/%d/exe", pid) // 使用os.Readlink获取符号链接指向的路径 execPath, err := os.Readlink(link) if err != nil { return "", err } return execPath, nil } func UsePIDToGetJDKVersion(pid uint32) string { execPath, err := getExecutablePath(pid) if err != nil { fmt.Println("Error:", err) return "" } // 构建Command对象 cmd := exec.Command(execPath, "-version") // 创建缓冲区保存输出 var out bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &out cmd.Stderr = &stderr // java -version 实际是写入Stderr // 运行命令 err = cmd.Run() if err != nil { fmt.Println("Command execution error:", err) fmt.Println("Error output:", stderr.String()) return "" } // 打印输出信息 version, err := parseQuotedString(stderr.String()) if err != nil { fmt.Println("Error:", err) return "" } return version }