util.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. package containers
  2. import (
  3. "bufio"
  4. "bytes"
  5. "debug/elf"
  6. "errors"
  7. "fmt"
  8. "github.com/coroot/coroot-node-agent/flags"
  9. . "github.com/coroot/coroot-node-agent/utils/modelse"
  10. klog "github.com/sirupsen/logrus"
  11. "io"
  12. "os"
  13. "os/exec"
  14. "path/filepath"
  15. "regexp"
  16. "runtime"
  17. "strings"
  18. "unicode"
  19. )
  20. var libjvmRegex = regexp.MustCompile(`.*/libjvm\.so`)
  21. func GetExeType(pid uint32, rootfs string) CodeType {
  22. mapsFilePath := fmt.Sprintf("%sproc/%d/maps", "/", pid)
  23. data, err := os.ReadFile(mapsFilePath)
  24. if err != nil {
  25. klog.WithError(err).Errorf("Failed to read %s: %s", mapsFilePath)
  26. return CodeTypeUnknown
  27. }
  28. content := string(data)
  29. if libjvmRegex.MatchString(content) {
  30. //fmt.Println("is java process")
  31. if isJavaAotProcess(pid, rootfs) {
  32. //fmt.Println("is javaAot process")
  33. return CodeTypeJavaAot
  34. }
  35. return CodeTypeJava
  36. } else if isJavaAotProcess(pid, rootfs) {
  37. //fmt.Println("is javaAot process")
  38. return CodeTypeJavaAot
  39. } else if isGoProcess(pid, rootfs) {
  40. //fmt.Println("is go process")
  41. return CodeTypeGo
  42. } else if isNetCoreProcess(pid, rootfs) {
  43. // fmt.Println("is netcore process")
  44. return CodeTypeNetCoreAot
  45. } else if isPythonProcess(pid, rootfs) {
  46. return CodeTypePython
  47. } else if isNodeProcess(pid, rootfs) {
  48. return CodeTypeNode
  49. }
  50. return CodeTypeUnknown
  51. }
  52. // isJavaAotProcess checks if the process with the given PID is a GraalVM native image
  53. func isJavaAotProcess(pid uint32, rootfs string) bool {
  54. // Get the executable path for the given PID
  55. exePath, err := os.Readlink(fmt.Sprintf("%sproc/%d/exe", "/", pid))
  56. if err != nil {
  57. //fmt.Printf("Error reading executable path for PID %d: %v\n", pid, err)
  58. klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable path for PID [%d]", pid)
  59. return false
  60. }
  61. // Read the content of the executable file
  62. pathPrefix := rootfs
  63. if pathPrefix == "" && *flags.RunInContainer {
  64. pathPrefix = *flags.HostDirPathPrefix
  65. }
  66. content, err := os.ReadFile(fmt.Sprintf("%s%s", pathPrefix, exePath))
  67. if err != nil {
  68. if _, err = os.Stat(exePath); err == nil {
  69. content, err = os.ReadFile(exePath)
  70. if err != nil {
  71. klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable file local for PID [%d]", pid)
  72. return false
  73. }
  74. } else {
  75. klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable file for PID [%d]", pid)
  76. return false
  77. }
  78. }
  79. // Check if the file contains the "graal_attach_thread" string
  80. if strings.Contains(string(content), "graal_attach_thread") {
  81. return true
  82. }
  83. return false
  84. }
  85. func isNetCoreProcess(pid uint32, rootfs string) bool {
  86. path, err := getProcessPath(pid)
  87. if err != nil {
  88. //fmt.Printf("无法获取进程路径:%s\n", err)
  89. klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary path for PID [%d]", pid)
  90. return false
  91. }
  92. pathPrefix := rootfs
  93. if pathPrefix == "" && *flags.RunInContainer {
  94. pathPrefix = *flags.HostDirPathPrefix
  95. }
  96. ef, err := elf.Open(pathPrefix + path)
  97. if err != nil {
  98. if _, err = os.Stat(path); err == nil {
  99. ef, err = elf.Open(path)
  100. if err != nil {
  101. klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary file local for PID [%d]", pid)
  102. return false
  103. }
  104. } else {
  105. klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary file for PID [%d]", pid)
  106. return false
  107. }
  108. }
  109. defer ef.Close()
  110. __managedcode := ef.Section("__managedcode")
  111. if __managedcode != nil {
  112. fmt.Println("is a netcore process")
  113. return true
  114. }
  115. return false
  116. }
  117. func isGoProcess(pid uint32, rootfs string) bool {
  118. path, err := getProcessPath(pid)
  119. if err != nil {
  120. klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary path for PID [%d]", pid)
  121. return false
  122. }
  123. pathPrefix := rootfs
  124. if pathPrefix == "" && *flags.RunInContainer {
  125. pathPrefix = *flags.HostDirPathPrefix
  126. }
  127. ef, err := elf.Open(pathPrefix + path)
  128. if err != nil {
  129. if _, err = os.Stat(path); err == nil {
  130. ef, err = elf.Open(path)
  131. if err != nil {
  132. klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary file local for PID [%d]", pid)
  133. return false
  134. }
  135. } else {
  136. klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary file for PID [%d]", pid)
  137. return false
  138. }
  139. }
  140. defer ef.Close()
  141. gopclntabSection := ef.Section(".gopclntab")
  142. if gopclntabSection != nil {
  143. //fmt.Println("is a go process")
  144. return true
  145. }
  146. return false
  147. }
  148. func isPythonProcess(pid uint32, rootfs string) bool {
  149. pathPrefix := rootfs
  150. if pathPrefix == "" && *flags.RunInContainer {
  151. pathPrefix = *flags.HostDirPathPrefix
  152. }
  153. procMapsPath := fmt.Sprintf("%s/proc/%d/maps", pathPrefix, pid)
  154. f, err := os.Open(procMapsPath)
  155. if err != nil {
  156. klog.Error(err)
  157. return false
  158. }
  159. defer f.Close()
  160. // 读取 maps(限制读取上限,防止非常大的文件)
  161. const maxRead = 256 * 1024 // 256 KB should be plenty for maps
  162. buf := make([]byte, maxRead)
  163. n, err := f.Read(buf)
  164. if err != nil && err != io.EOF {
  165. return false
  166. }
  167. data := buf[:n]
  168. lower := bytes.ToLower(data)
  169. // 快速子串预筛(避免不必要的正则)
  170. if !(bytes.Contains(lower, []byte("python")) ||
  171. bytes.Contains(lower, []byte("cpython")) ||
  172. bytes.Contains(lower, []byte("libpython"))) {
  173. return false
  174. }
  175. // 精确正则匹配:若任一匹配成功,则认定为 Python 进程
  176. var patterns = []*regexp.Regexp{
  177. // 可执行 /usr/bin/python 或 /usr/local/bin/python 等
  178. regexp.MustCompile(`(?i)/[a-z0-9_\-/.]*\bpython[0-9]*(\.[0-9]+)*\b`),
  179. // libpythonX.Y.so 或 libpython3.8.so.1.0 之类
  180. regexp.MustCompile(`(?i)libpython[0-9]*(\.[0-9]+)*\.so(\.[0-9]+)*`),
  181. // cpython-310 / cpython-39 等扩展模块名
  182. regexp.MustCompile(`(?i)cpython-[0-9]+`),
  183. // python3.10 的路径片段(更精确匹配版本)
  184. regexp.MustCompile(`(?i)python[0-9]+\.[0-9]+`),
  185. }
  186. for _, re := range patterns {
  187. if re.Find(lower) != nil {
  188. return true
  189. }
  190. }
  191. // 若没有任何精确模式匹配,则认为不是 Python(或无法确定)
  192. return false
  193. }
  194. func isNodeProcess(pid uint32, rootfs string) bool {
  195. pathPrefix := rootfs
  196. if pathPrefix == "" && *flags.RunInContainer {
  197. pathPrefix = *flags.HostDirPathPrefix
  198. }
  199. procExe := fmt.Sprintf("%s/proc/%d/exe", pathPrefix, pid)
  200. // 1) readlink exe
  201. exePath, err := os.Readlink(procExe)
  202. if err != nil {
  203. return false
  204. }
  205. // 2) 判断 exe 名称/路径是否为 node
  206. base := filepath.Base(exePath)
  207. lowBase := strings.ToLower(base)
  208. lowPath := strings.ToLower(exePath)
  209. if !(lowBase == "node" || lowBase == "nodejs" || strings.Contains(lowPath, "/node") || strings.Contains(lowPath, "nodejs")) {
  210. return false
  211. }
  212. // 3) 打开可执行文件并进行 strings 风格扫描
  213. f, err := os.Open(exePath)
  214. if err != nil {
  215. // 无法打开二进制,视为未找到关键字
  216. return false
  217. }
  218. defer f.Close()
  219. // 要匹配的关键字正则(严格、降低误报)
  220. patterns := []*regexp.Regexp{
  221. regexp.MustCompile(`(?i)nodejsVersion`),
  222. regexp.MustCompile(`(?i)node_version`),
  223. regexp.MustCompile(`(?i)node[^\w]?js`),
  224. }
  225. checkMatch := func(s string) bool {
  226. for _, p := range patterns {
  227. if p.FindStringIndex(s) != nil {
  228. return true
  229. }
  230. }
  231. return false
  232. }
  233. const bufSize = 64 * 1024
  234. reader := bufio.NewReaderSize(f, bufSize)
  235. const minRunLen = 4
  236. var leftover []byte
  237. for {
  238. chunk := make([]byte, bufSize)
  239. n, rerr := reader.Read(chunk)
  240. if n > 0 {
  241. data := chunk[:n]
  242. if len(leftover) > 0 {
  243. data = append(leftover, data...)
  244. leftover = nil
  245. }
  246. start := -1
  247. for i, b := range data {
  248. if (b >= 32 && b < 127) || b == '\n' || b == '\t' || b == '\r' {
  249. if start == -1 {
  250. start = i
  251. }
  252. } else {
  253. if start != -1 {
  254. if i-start >= minRunLen {
  255. s := string(data[start:i])
  256. s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
  257. if checkMatch(s) {
  258. return true
  259. }
  260. }
  261. start = -1
  262. }
  263. }
  264. }
  265. if start != -1 {
  266. // 保存块尾部可打印残留
  267. leftover = append([]byte{}, data[start:]...)
  268. if len(leftover) > 1<<20 {
  269. leftover = leftover[len(leftover)-(1<<20):]
  270. }
  271. }
  272. }
  273. if rerr != nil {
  274. if errors.Is(rerr, io.EOF) {
  275. // 检查 leftover
  276. if len(leftover) >= minRunLen {
  277. s := string(leftover)
  278. s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
  279. if checkMatch(s) {
  280. return true
  281. }
  282. }
  283. // 读完未找到
  284. return false
  285. }
  286. // 读错误,返回 false
  287. return false
  288. }
  289. }
  290. }
  291. func getProcessPath(pid uint32) (string, error) {
  292. switch runtime.GOOS {
  293. case "linux":
  294. return getLinuxProcessPath(pid)
  295. default:
  296. return "", fmt.Errorf("不支持的操作系统:%s", runtime.GOOS)
  297. }
  298. }
  299. func getLinuxProcessPath(pid uint32) (string, error) {
  300. procPath := fmt.Sprintf("/proc/%d/exe", pid)
  301. path, err := os.Readlink(procPath)
  302. if err != nil {
  303. return "", err
  304. }
  305. return path, nil
  306. }
  307. func executeCommand(name string, args ...string) (string, error) {
  308. cmd := exec.Command(name, args...)
  309. out, err := cmd.Output()
  310. if err != nil {
  311. return "", err
  312. }
  313. return string(out), nil
  314. }