ptrace_linux.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Copyright The OpenTelemetry Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package ptrace
  15. import (
  16. "fmt"
  17. "github.com/coroot/coroot-node-agent/utils"
  18. klog "github.com/sirupsen/logrus"
  19. "os"
  20. "strconv"
  21. "strings"
  22. "syscall"
  23. "golang.org/x/sys/unix"
  24. "github.com/go-logr/logr"
  25. "github.com/hashicorp/go-version"
  26. "github.com/pkg/errors"
  27. )
  28. const waitPidErrorMessage = "waitpid ret value: %d"
  29. const (
  30. // MADV_POPULATE_READ.
  31. MadvisePopulateRead = 0x16
  32. // MADV_POPULATE_WRITE.
  33. MadvisePopulateWrite = 0x17
  34. )
  35. var threadRetryLimit = 10
  36. // TracedProgram is a program traced by ptrace.
  37. type TracedProgram struct {
  38. pid int
  39. tids []int
  40. backupRegs *syscall.PtraceRegs
  41. backupCode []byte
  42. logger logr.Logger
  43. }
  44. // Pid return the pid of traced program.
  45. func (p *TracedProgram) Pid() int {
  46. return p.pid
  47. }
  48. func waitPid(pid int) error {
  49. ret, err := unix.Wait4(pid, nil, unix.WALL, nil)
  50. if err != nil {
  51. return err
  52. }
  53. if ret == pid {
  54. return nil
  55. }
  56. return errors.Errorf(waitPidErrorMessage, ret)
  57. }
  58. // NewTracedProgram ptrace all threads of a process.
  59. func NewTracedProgram(pid int) (*TracedProgram, error) {
  60. tidMap := make(map[int]bool)
  61. retryCount := make(map[int]int)
  62. // iterate over the thread group, until it doens't change
  63. //
  64. // we have tried several ways to ensure that we have stopped all the tasks:
  65. // 1. iterating over and over again to make sure all of them are tracee
  66. // 2. send `SIGSTOP` signal
  67. // ...
  68. // only the first way finally worked for every situations
  69. for {
  70. threads, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
  71. if err != nil {
  72. return nil, errors.WithStack(err)
  73. }
  74. // judge whether `threads` is a subset of `tidMap`
  75. subset := true
  76. tids := make(map[int]bool)
  77. for _, thread := range threads {
  78. tid64, err := strconv.ParseInt(thread.Name(), 10, 32)
  79. if err != nil {
  80. return nil, errors.WithStack(err)
  81. }
  82. tid := int(tid64)
  83. _, ok := tidMap[tid]
  84. if ok {
  85. tids[tid] = true
  86. continue
  87. }
  88. subset = false
  89. err = syscall.PtraceAttach(tid)
  90. if err != nil {
  91. _, ok := retryCount[tid]
  92. if !ok {
  93. retryCount[tid] = 1
  94. } else {
  95. retryCount[tid]++
  96. }
  97. if retryCount[tid] < threadRetryLimit {
  98. klog.Info("retry attaching thread", "tid", tid, "retryCount", retryCount[tid], "limit", threadRetryLimit)
  99. continue
  100. }
  101. if !strings.Contains(err.Error(), "no such process") {
  102. return nil, errors.WithStack(err)
  103. }
  104. continue
  105. }
  106. err = waitPid(tid)
  107. if err != nil {
  108. e := syscall.PtraceDetach(tid)
  109. if e != nil && !strings.Contains(e.Error(), "no such process") {
  110. klog.Error(e, "detach failed", "tid", tid)
  111. }
  112. return nil, errors.WithStack(err)
  113. }
  114. klog.Infof("attach successfully %d", tid)
  115. tids[tid] = true
  116. tidMap[tid] = true
  117. }
  118. if subset {
  119. tidMap = tids
  120. break
  121. }
  122. }
  123. var tids []int
  124. for key := range tidMap {
  125. tids = append(tids, key)
  126. }
  127. program := &TracedProgram{
  128. pid: pid,
  129. tids: tids,
  130. backupRegs: &syscall.PtraceRegs{},
  131. backupCode: make([]byte, syscallInstrSize),
  132. }
  133. return program, nil
  134. }
  135. // Detach detaches from all threads of the processes.
  136. func (p *TracedProgram) Detach() error {
  137. for _, tid := range p.tids {
  138. err := syscall.PtraceDetach(tid)
  139. if err != nil {
  140. if !strings.Contains(err.Error(), "no such process") {
  141. return errors.WithStack(err)
  142. }
  143. }
  144. }
  145. return nil
  146. }
  147. // Protect will backup regs and rip into fields.
  148. func (p *TracedProgram) Protect() error {
  149. err := getRegs(p.pid, p.backupRegs)
  150. if err != nil {
  151. return errors.WithStack(err)
  152. }
  153. _, err = syscall.PtracePeekData(p.pid, getIP(p.backupRegs), p.backupCode)
  154. if err != nil {
  155. return errors.WithStack(err)
  156. }
  157. return nil
  158. }
  159. // Restore will restore regs and rip from fields.
  160. func (p *TracedProgram) Restore() error {
  161. err := setRegs(p.pid, p.backupRegs)
  162. if err != nil {
  163. return errors.WithStack(err)
  164. }
  165. _, err = syscall.PtracePokeData(p.pid, getIP(p.backupRegs), p.backupCode)
  166. if err != nil {
  167. return errors.WithStack(err)
  168. }
  169. return nil
  170. }
  171. // Wait waits until the process stops.
  172. func (p *TracedProgram) Wait() error {
  173. _, err := syscall.Wait4(p.pid, nil, 0, nil)
  174. return err
  175. }
  176. // Step moves one step forward.
  177. func (p *TracedProgram) Step() error {
  178. err := syscall.PtraceSingleStep(p.pid)
  179. if err != nil {
  180. return errors.WithStack(err)
  181. }
  182. return p.Wait()
  183. }
  184. // SetMemLockInfinity sets the memlock rlimit to infinity.
  185. func (p *TracedProgram) SetMemLockInfinity() error {
  186. // Requires CAP_SYS_RESOURCE.
  187. newLimit := unix.Rlimit{Cur: unix.RLIM_INFINITY, Max: unix.RLIM_INFINITY}
  188. if err := unix.Prlimit(p.pid, unix.RLIMIT_MEMLOCK, &newLimit, nil); err != nil {
  189. return fmt.Errorf("failed to set memlock rlimit: %w", err)
  190. }
  191. return nil
  192. }
  193. // Mmap runs mmap syscall.
  194. func (p *TracedProgram) Mmap(length uint64, fd uint64) (uint64, error) {
  195. return p.Syscall(syscall.SYS_MMAP, 0, length, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE|syscall.MAP_POPULATE|syscall.MAP_LOCKED, fd, 0)
  196. }
  197. // Madvise runs madvise syscall.
  198. func (p *TracedProgram) Madvise(addr uint64, length uint64) error {
  199. advice := uint64(syscall.MADV_WILLNEED)
  200. ver, err := utils.GetLinuxKernelVersion()
  201. if err != nil {
  202. return errors.WithStack(err)
  203. }
  204. minVersion := version.Must(version.NewVersion("5.14"))
  205. p.logger.Info("Detected linux kernel version", "version", ver)
  206. if ver.GreaterThanOrEqual(minVersion) {
  207. advice = syscall.MADV_WILLNEED | MadvisePopulateRead | MadvisePopulateWrite
  208. }
  209. _, err = p.Syscall(syscall.SYS_MADVISE, addr, length, advice, 0, 0, 0)
  210. return err
  211. }
  212. // Mlock runs mlock syscall.
  213. func (p *TracedProgram) Mlock(addr uint64, length uint64) error {
  214. ret, err := p.Syscall(syscall.SYS_MLOCK, addr, length, 0, 0, 0, 0)
  215. p.logger.Info("mlock ret", "ret", ret)
  216. return err
  217. }