| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- package containers
- import (
- "bufio"
- "bytes"
- "debug/elf"
- "errors"
- "fmt"
- "github.com/coroot/coroot-node-agent/flags"
- . "github.com/coroot/coroot-node-agent/utils/modelse"
- klog "github.com/sirupsen/logrus"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
- "unicode"
- )
- var libjvmRegex = regexp.MustCompile(`.*/libjvm\.so`)
- func GetExeType(pid uint32, rootfs string) CodeType {
- mapsFilePath := fmt.Sprintf("%sproc/%d/maps", "/", pid)
- data, err := os.ReadFile(mapsFilePath)
- if err != nil {
- klog.WithError(err).Errorf("Failed to read %s: %s", mapsFilePath)
- return CodeTypeUnknown
- }
- content := string(data)
- if libjvmRegex.MatchString(content) {
- //fmt.Println("is java process")
- if isJavaAotProcess(pid, rootfs) {
- //fmt.Println("is javaAot process")
- return CodeTypeJavaAot
- }
- return CodeTypeJava
- } else if isJavaAotProcess(pid, rootfs) {
- //fmt.Println("is javaAot process")
- return CodeTypeJavaAot
- } else if isGoProcess(pid, rootfs) {
- //fmt.Println("is go process")
- return CodeTypeGo
- } else if isNetCoreProcess(pid, rootfs) {
- // fmt.Println("is netcore process")
- return CodeTypeNetCoreAot
- } else if isPythonProcess(pid, rootfs) {
- return CodeTypePython
- } else if isNodeProcess(pid, rootfs) {
- return CodeTypeNode
- }
- return CodeTypeUnknown
- }
- // isJavaAotProcess checks if the process with the given PID is a GraalVM native image
- func isJavaAotProcess(pid uint32, rootfs string) bool {
- // Get the executable path for the given PID
- exePath, err := os.Readlink(fmt.Sprintf("%sproc/%d/exe", "/", pid))
- if err != nil {
- //fmt.Printf("Error reading executable path for PID %d: %v\n", pid, err)
- klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable path for PID [%d]", pid)
- return false
- }
- // Read the content of the executable file
- pathPrefix := rootfs
- if pathPrefix == "" && *flags.RunInContainer {
- pathPrefix = *flags.HostDirPathPrefix
- }
- content, err := os.ReadFile(fmt.Sprintf("%s%s", pathPrefix, exePath))
- if err != nil {
- if _, err = os.Stat(exePath); err == nil {
- content, err = os.ReadFile(exePath)
- if err != nil {
- klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable file local for PID [%d]", pid)
- return false
- }
- } else {
- klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable file for PID [%d]", pid)
- return false
- }
- }
- // Check if the file contains the "graal_attach_thread" string
- if strings.Contains(string(content), "graal_attach_thread") {
- return true
- }
- return false
- }
- func isNetCoreProcess(pid uint32, rootfs string) bool {
- path, err := getProcessPath(pid)
- if err != nil {
- //fmt.Printf("无法获取进程路径:%s\n", err)
- klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary path for PID [%d]", pid)
- return false
- }
- pathPrefix := rootfs
- if pathPrefix == "" && *flags.RunInContainer {
- pathPrefix = *flags.HostDirPathPrefix
- }
- ef, err := elf.Open(pathPrefix + path)
- if err != nil {
- if _, err = os.Stat(path); err == nil {
- ef, err = elf.Open(path)
- if err != nil {
- klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary file local for PID [%d]", pid)
- return false
- }
- } else {
- klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary file for PID [%d]", pid)
- return false
- }
- }
- defer ef.Close()
- __managedcode := ef.Section("__managedcode")
- if __managedcode != nil {
- fmt.Println("is a netcore process")
- return true
- }
- return false
- }
- func isGoProcess(pid uint32, rootfs string) bool {
- path, err := getProcessPath(pid)
- if err != nil {
- klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary path for PID [%d]", pid)
- return false
- }
- pathPrefix := rootfs
- if pathPrefix == "" && *flags.RunInContainer {
- pathPrefix = *flags.HostDirPathPrefix
- }
- ef, err := elf.Open(pathPrefix + path)
- if err != nil {
- if _, err = os.Stat(path); err == nil {
- ef, err = elf.Open(path)
- if err != nil {
- klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary file local for PID [%d]", pid)
- return false
- }
- } else {
- klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary file for PID [%d]", pid)
- return false
- }
- }
- defer ef.Close()
- gopclntabSection := ef.Section(".gopclntab")
- if gopclntabSection != nil {
- //fmt.Println("is a go process")
- return true
- }
- return false
- }
- func isPythonProcess(pid uint32, rootfs string) bool {
- pathPrefix := rootfs
- if pathPrefix == "" && *flags.RunInContainer {
- pathPrefix = *flags.HostDirPathPrefix
- }
- procMapsPath := fmt.Sprintf("%s/proc/%d/maps", pathPrefix, pid)
- f, err := os.Open(procMapsPath)
- if err != nil {
- klog.Error(err)
- return false
- }
- defer f.Close()
- // 读取 maps(限制读取上限,防止非常大的文件)
- const maxRead = 256 * 1024 // 256 KB should be plenty for maps
- buf := make([]byte, maxRead)
- n, err := f.Read(buf)
- if err != nil && err != io.EOF {
- return false
- }
- data := buf[:n]
- lower := bytes.ToLower(data)
- // 快速子串预筛(避免不必要的正则)
- if !(bytes.Contains(lower, []byte("python")) ||
- bytes.Contains(lower, []byte("cpython")) ||
- bytes.Contains(lower, []byte("libpython"))) {
- return false
- }
- // 精确正则匹配:若任一匹配成功,则认定为 Python 进程
- var patterns = []*regexp.Regexp{
- // 可执行 /usr/bin/python 或 /usr/local/bin/python 等
- regexp.MustCompile(`(?i)/[a-z0-9_\-/.]*\bpython[0-9]*(\.[0-9]+)*\b`),
- // libpythonX.Y.so 或 libpython3.8.so.1.0 之类
- regexp.MustCompile(`(?i)libpython[0-9]*(\.[0-9]+)*\.so(\.[0-9]+)*`),
- // cpython-310 / cpython-39 等扩展模块名
- regexp.MustCompile(`(?i)cpython-[0-9]+`),
- // python3.10 的路径片段(更精确匹配版本)
- regexp.MustCompile(`(?i)python[0-9]+\.[0-9]+`),
- }
- for _, re := range patterns {
- if re.Find(lower) != nil {
- return true
- }
- }
- // 若没有任何精确模式匹配,则认为不是 Python(或无法确定)
- return false
- }
- func isNodeProcess(pid uint32, rootfs string) bool {
- pathPrefix := rootfs
- if pathPrefix == "" && *flags.RunInContainer {
- pathPrefix = *flags.HostDirPathPrefix
- }
- procExe := fmt.Sprintf("%s/proc/%d/exe", pathPrefix, pid)
- // 1) readlink exe
- exePath, err := os.Readlink(procExe)
- if err != nil {
- return false
- }
- // 2) 判断 exe 名称/路径是否为 node
- base := filepath.Base(exePath)
- lowBase := strings.ToLower(base)
- lowPath := strings.ToLower(exePath)
- if !(lowBase == "node" || lowBase == "nodejs" || strings.Contains(lowPath, "/node") || strings.Contains(lowPath, "nodejs")) {
- return false
- }
- // 3) 打开可执行文件并进行 strings 风格扫描
- f, err := os.Open(exePath)
- if err != nil {
- // 无法打开二进制,视为未找到关键字
- return false
- }
- defer f.Close()
- // 要匹配的关键字正则(严格、降低误报)
- patterns := []*regexp.Regexp{
- regexp.MustCompile(`(?i)nodejsVersion`),
- regexp.MustCompile(`(?i)node_version`),
- regexp.MustCompile(`(?i)node[^\w]?js`),
- }
- checkMatch := func(s string) bool {
- for _, p := range patterns {
- if p.FindStringIndex(s) != nil {
- return true
- }
- }
- return false
- }
- const bufSize = 64 * 1024
- reader := bufio.NewReaderSize(f, bufSize)
- const minRunLen = 4
- var leftover []byte
- for {
- chunk := make([]byte, bufSize)
- n, rerr := reader.Read(chunk)
- if n > 0 {
- data := chunk[:n]
- if len(leftover) > 0 {
- data = append(leftover, data...)
- leftover = nil
- }
- start := -1
- for i, b := range data {
- if (b >= 32 && b < 127) || b == '\n' || b == '\t' || b == '\r' {
- if start == -1 {
- start = i
- }
- } else {
- if start != -1 {
- if i-start >= minRunLen {
- s := string(data[start:i])
- s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
- if checkMatch(s) {
- return true
- }
- }
- start = -1
- }
- }
- }
- if start != -1 {
- // 保存块尾部可打印残留
- leftover = append([]byte{}, data[start:]...)
- if len(leftover) > 1<<20 {
- leftover = leftover[len(leftover)-(1<<20):]
- }
- }
- }
- if rerr != nil {
- if errors.Is(rerr, io.EOF) {
- // 检查 leftover
- if len(leftover) >= minRunLen {
- s := string(leftover)
- s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
- if checkMatch(s) {
- return true
- }
- }
- // 读完未找到
- return false
- }
- // 读错误,返回 false
- return false
- }
- }
- }
- func getProcessPath(pid uint32) (string, error) {
- switch runtime.GOOS {
- case "linux":
- return getLinuxProcessPath(pid)
- default:
- return "", fmt.Errorf("不支持的操作系统:%s", runtime.GOOS)
- }
- }
- func getLinuxProcessPath(pid uint32) (string, error) {
- procPath := fmt.Sprintf("/proc/%d/exe", pid)
- path, err := os.Readlink(procPath)
- if err != nil {
- return "", err
- }
- return path, nil
- }
- func executeCommand(name string, args ...string) (string, error) {
- cmd := exec.Command(name, args...)
- out, err := cmd.Output()
- if err != nil {
- return "", err
- }
- return string(out), nil
- }
|