uprobe.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. package link
  2. import (
  3. "debug/elf"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. "github.com/cilium/ebpf"
  11. "github.com/cilium/ebpf/internal"
  12. )
  13. var (
  14. uprobeEventsPath = filepath.Join(tracefsPath, "uprobe_events")
  15. uprobeRetprobeBit = struct {
  16. once sync.Once
  17. value uint64
  18. err error
  19. }{}
  20. uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset"
  21. // elixir.bootlin.com/linux/v5.15-rc7/source/kernel/events/core.c#L9799
  22. uprobeRefCtrOffsetShift = 32
  23. haveRefCtrOffsetPMU = internal.FeatureTest("RefCtrOffsetPMU", "4.20", func() error {
  24. _, err := os.Stat(uprobeRefCtrOffsetPMUPath)
  25. if err != nil {
  26. return internal.ErrNotSupported
  27. }
  28. return nil
  29. })
  30. // ErrNoSymbol indicates that the given symbol was not found
  31. // in the ELF symbols table.
  32. ErrNoSymbol = errors.New("not found")
  33. )
  34. // Executable defines an executable program on the filesystem.
  35. type Executable struct {
  36. // Path of the executable on the filesystem.
  37. path string
  38. // Parsed ELF and dynamic symbols' addresses.
  39. addresses map[string]uint64
  40. }
  41. // UprobeOptions defines additional parameters that will be used
  42. // when loading Uprobes.
  43. type UprobeOptions struct {
  44. // Symbol address. Must be provided in case of external symbols (shared libs).
  45. // If set, overrides the address eventually parsed from the executable.
  46. Address uint64
  47. // The offset relative to given symbol. Useful when tracing an arbitrary point
  48. // inside the frame of given symbol.
  49. //
  50. // Note: this field changed from being an absolute offset to being relative
  51. // to Address.
  52. Offset uint64
  53. // Only set the uprobe on the given process ID. Useful when tracing
  54. // shared library calls or programs that have many running instances.
  55. PID int
  56. // Automatically manage SDT reference counts (semaphores).
  57. //
  58. // If this field is set, the Kernel will increment/decrement the
  59. // semaphore located in the process memory at the provided address on
  60. // probe attach/detach.
  61. //
  62. // See also:
  63. // sourceware.org/systemtap/wiki/UserSpaceProbeImplementation (Semaphore Handling)
  64. // github.com/torvalds/linux/commit/1cc33161a83d
  65. // github.com/torvalds/linux/commit/a6ca88b241d5
  66. RefCtrOffset uint64
  67. // Arbitrary value that can be fetched from an eBPF program
  68. // via `bpf_get_attach_cookie()`.
  69. //
  70. // Needs kernel 5.15+.
  71. Cookie uint64
  72. }
  73. // To open a new Executable, use:
  74. //
  75. // OpenExecutable("/bin/bash")
  76. //
  77. // The returned value can then be used to open Uprobe(s).
  78. func OpenExecutable(path string) (*Executable, error) {
  79. if path == "" {
  80. return nil, fmt.Errorf("path cannot be empty")
  81. }
  82. f, err := os.Open(path)
  83. if err != nil {
  84. return nil, fmt.Errorf("open file '%s': %w", path, err)
  85. }
  86. defer f.Close()
  87. se, err := internal.NewSafeELFFile(f)
  88. if err != nil {
  89. return nil, fmt.Errorf("parse ELF file: %w", err)
  90. }
  91. if se.Type != elf.ET_EXEC && se.Type != elf.ET_DYN {
  92. // ELF is not an executable or a shared object.
  93. return nil, errors.New("the given file is not an executable or a shared object")
  94. }
  95. ex := Executable{
  96. path: path,
  97. addresses: make(map[string]uint64),
  98. }
  99. if err := ex.load(se); err != nil {
  100. return nil, err
  101. }
  102. return &ex, nil
  103. }
  104. func (ex *Executable) load(f *internal.SafeELFFile) error {
  105. syms, err := f.Symbols()
  106. if err != nil && !errors.Is(err, elf.ErrNoSymbols) {
  107. return err
  108. }
  109. dynsyms, err := f.DynamicSymbols()
  110. if err != nil && !errors.Is(err, elf.ErrNoSymbols) {
  111. return err
  112. }
  113. syms = append(syms, dynsyms...)
  114. for _, s := range syms {
  115. if elf.ST_TYPE(s.Info) != elf.STT_FUNC {
  116. // Symbol not associated with a function or other executable code.
  117. continue
  118. }
  119. address := s.Value
  120. // Loop over ELF segments.
  121. for _, prog := range f.Progs {
  122. // Skip uninteresting segments.
  123. if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 {
  124. continue
  125. }
  126. if prog.Vaddr <= s.Value && s.Value < (prog.Vaddr+prog.Memsz) {
  127. // If the symbol value is contained in the segment, calculate
  128. // the symbol offset.
  129. //
  130. // fn symbol offset = fn symbol VA - .text VA + .text offset
  131. //
  132. // stackoverflow.com/a/40249502
  133. address = s.Value - prog.Vaddr + prog.Off
  134. break
  135. }
  136. }
  137. ex.addresses[s.Name] = address
  138. }
  139. return nil
  140. }
  141. // address calculates the address of a symbol in the executable.
  142. //
  143. // opts must not be nil.
  144. func (ex *Executable) address(symbol string, opts *UprobeOptions) (uint64, error) {
  145. if opts.Address > 0 {
  146. return opts.Address + opts.Offset, nil
  147. }
  148. address, ok := ex.addresses[symbol]
  149. if !ok {
  150. return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol)
  151. }
  152. // Symbols with location 0 from section undef are shared library calls and
  153. // are relocated before the binary is executed. Dynamic linking is not
  154. // implemented by the library, so mark this as unsupported for now.
  155. //
  156. // Since only offset values are stored and not elf.Symbol, if the value is 0,
  157. // assume it's an external symbol.
  158. if address == 0 {
  159. return 0, fmt.Errorf("cannot resolve %s library call '%s': %w "+
  160. "(consider providing UprobeOptions.Address)", ex.path, symbol, ErrNotSupported)
  161. }
  162. return address + opts.Offset, nil
  163. }
  164. // Uprobe attaches the given eBPF program to a perf event that fires when the
  165. // given symbol starts executing in the given Executable.
  166. // For example, /bin/bash::main():
  167. //
  168. // ex, _ = OpenExecutable("/bin/bash")
  169. // ex.Uprobe("main", prog, nil)
  170. //
  171. // When using symbols which belongs to shared libraries,
  172. // an offset must be provided via options:
  173. //
  174. // up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
  175. //
  176. // Note: Setting the Offset field in the options supersedes the symbol's offset.
  177. //
  178. // Losing the reference to the resulting Link (up) will close the Uprobe
  179. // and prevent further execution of prog. The Link must be Closed during
  180. // program shutdown to avoid leaking system resources.
  181. //
  182. // Functions provided by shared libraries can currently not be traced and
  183. // will result in an ErrNotSupported.
  184. func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
  185. u, err := ex.uprobe(symbol, prog, opts, false)
  186. if err != nil {
  187. return nil, err
  188. }
  189. lnk, err := attachPerfEvent(u, prog)
  190. if err != nil {
  191. u.Close()
  192. return nil, err
  193. }
  194. return lnk, nil
  195. }
  196. // Uretprobe attaches the given eBPF program to a perf event that fires right
  197. // before the given symbol exits. For example, /bin/bash::main():
  198. //
  199. // ex, _ = OpenExecutable("/bin/bash")
  200. // ex.Uretprobe("main", prog, nil)
  201. //
  202. // When using symbols which belongs to shared libraries,
  203. // an offset must be provided via options:
  204. //
  205. // up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
  206. //
  207. // Note: Setting the Offset field in the options supersedes the symbol's offset.
  208. //
  209. // Losing the reference to the resulting Link (up) will close the Uprobe
  210. // and prevent further execution of prog. The Link must be Closed during
  211. // program shutdown to avoid leaking system resources.
  212. //
  213. // Functions provided by shared libraries can currently not be traced and
  214. // will result in an ErrNotSupported.
  215. func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
  216. u, err := ex.uprobe(symbol, prog, opts, true)
  217. if err != nil {
  218. return nil, err
  219. }
  220. lnk, err := attachPerfEvent(u, prog)
  221. if err != nil {
  222. u.Close()
  223. return nil, err
  224. }
  225. return lnk, nil
  226. }
  227. // uprobe opens a perf event for the given binary/symbol and attaches prog to it.
  228. // If ret is true, create a uretprobe.
  229. func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions, ret bool) (*perfEvent, error) {
  230. if prog == nil {
  231. return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
  232. }
  233. if prog.Type() != ebpf.Kprobe {
  234. return nil, fmt.Errorf("eBPF program type %s is not Kprobe: %w", prog.Type(), errInvalidInput)
  235. }
  236. if opts == nil {
  237. opts = &UprobeOptions{}
  238. }
  239. offset, err := ex.address(symbol, opts)
  240. if err != nil {
  241. return nil, err
  242. }
  243. pid := opts.PID
  244. if pid == 0 {
  245. pid = perfAllThreads
  246. }
  247. if opts.RefCtrOffset != 0 {
  248. if err := haveRefCtrOffsetPMU(); err != nil {
  249. return nil, fmt.Errorf("uprobe ref_ctr_offset: %w", err)
  250. }
  251. }
  252. args := probeArgs{
  253. symbol: symbol,
  254. path: ex.path,
  255. offset: offset,
  256. pid: pid,
  257. refCtrOffset: opts.RefCtrOffset,
  258. ret: ret,
  259. cookie: opts.Cookie,
  260. }
  261. // Use uprobe PMU if the kernel has it available.
  262. tp, err := pmuUprobe(args)
  263. if err == nil {
  264. return tp, nil
  265. }
  266. if err != nil && !errors.Is(err, ErrNotSupported) {
  267. return nil, fmt.Errorf("creating perf_uprobe PMU: %w", err)
  268. }
  269. // Use tracefs if uprobe PMU is missing.
  270. args.symbol = sanitizeSymbol(symbol)
  271. tp, err = tracefsUprobe(args)
  272. if err != nil {
  273. return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
  274. }
  275. return tp, nil
  276. }
  277. // pmuUprobe opens a perf event based on the uprobe PMU.
  278. func pmuUprobe(args probeArgs) (*perfEvent, error) {
  279. return pmuProbe(uprobeType, args)
  280. }
  281. // tracefsUprobe creates a Uprobe tracefs entry.
  282. func tracefsUprobe(args probeArgs) (*perfEvent, error) {
  283. return tracefsProbe(uprobeType, args)
  284. }
  285. // sanitizeSymbol replaces every invalid character for the tracefs api with an underscore.
  286. // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_").
  287. func sanitizeSymbol(s string) string {
  288. var b strings.Builder
  289. b.Grow(len(s))
  290. var skip bool
  291. for _, c := range []byte(s) {
  292. switch {
  293. case c >= 'a' && c <= 'z',
  294. c >= 'A' && c <= 'Z',
  295. c >= '0' && c <= '9':
  296. skip = false
  297. b.WriteByte(c)
  298. default:
  299. if !skip {
  300. b.WriteByte('_')
  301. skip = true
  302. }
  303. }
  304. }
  305. return b.String()
  306. }
  307. // uprobeToken creates the PATH:OFFSET(REF_CTR_OFFSET) token for the tracefs api.
  308. func uprobeToken(args probeArgs) string {
  309. po := fmt.Sprintf("%s:%#x", args.path, args.offset)
  310. if args.refCtrOffset != 0 {
  311. // This is not documented in Documentation/trace/uprobetracer.txt.
  312. // elixir.bootlin.com/linux/v5.15-rc7/source/kernel/trace/trace.c#L5564
  313. po += fmt.Sprintf("(%#x)", args.refCtrOffset)
  314. }
  315. return po
  316. }
  317. func uretprobeBit() (uint64, error) {
  318. uprobeRetprobeBit.once.Do(func() {
  319. uprobeRetprobeBit.value, uprobeRetprobeBit.err = determineRetprobeBit(uprobeType)
  320. })
  321. return uprobeRetprobeBit.value, uprobeRetprobeBit.err
  322. }