util.go 9.0 KB

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