|
|
@@ -1,16 +1,22 @@
|
|
|
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`)
|
|
|
@@ -42,6 +48,10 @@ func GetExeType(pid uint32, rootfs string) CodeType {
|
|
|
} 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
|
|
|
}
|
|
|
@@ -153,6 +163,167 @@ func isGoProcess(pid uint32, rootfs string) bool {
|
|
|
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":
|