ETrace is a syscall tracing utility powered by eBPF.
This is (yet another) strace
implementation with a twist:
- ETrace figures out dynamically the parameters of each syscall by parsing the tracepoint format files in the
/sys/kernel/debug/tracing/events/syscalls/*
directories - ETrace fetches dynamically the size of each structure of each syscall parameter by parsing the BTF information of the kernel. Once the data is retrieved from kernel space, it uses the same BTF information to parse and format the captured data.
In addition to normal syscall parameters, ETrace also collects process context data on each syscall entry and exit. This context includes:
- The process cgroups
- The process namespaces
- The process credentials
- The process comm
This project was developed on a Ubuntu Hirsute machine (Linux Kernel 5.11).
- golang 1.16+
- Kernel headers are expected to be installed in
lib/modules/$(uname -r)
, update theMakefile
with their location otherwise. - clang & llvm 11.0.1+
- Since ETrace was built using CORE, you shouldn't need to rebuild the eBPF programs. That said, if you want to rebuild the eBPF programs, you can use the following command:
# ~ make build-ebpf
- To build ETrace, run:
# ~ make build
- To install ETrace (copy to /usr/bin/etrace) run:
# ~ make install
ETrace needs to run as root. Run sudo etrace -h
to get help.
# ~ etrace -h
Usage:
etrace [flags]
Flags:
--bytes int amount of bytes shown to the screen when --stdout is provided (default 8)
-c, --comm stringArray list of process comms to filter, leave empty to capture everything
-h, --help help for etrace
--input string input file to parse data from
--json parse and dump the data retrieved from kernel space in the JSON format. This option might lead to more lost events than the --raw option and more CPU usage
-l, --log-level string log level, options: panic, fatal, error, warn, info, debug or trace (default "info")
--raw dump the data retrieved from kernel space without parsing it, use this option instead of the --json option to reduce the amount of lost events, and reduce the CPU usage of ETrace. You can ask ETrace to parse a --raw dump using the --input option
--stats show syscall statistics (default true)
--stdout parse and dump the data retrieved from kernel space to the console. This option might lead to more lost events than the --raw option and more CPU usage.
-s, --syscall stringArray list of syscalls to filter, leave empty to capture everything
# ~ sudo etrace --comm cat --stdout
INFO[2021-09-25T21:54:11Z] Tracing started ... (Ctrl + C to stop)
cat(10609) | SysBrk(unsigned long brk: 0) = 93940710199296
cat(10609) | SysArchPrctl(int option: 12289, unsigned long arg2: 140727494090368) = -22
cat(10609) | SysAccess(const char * filename: /etc/ld.so.preload, int mode: 4) = -2
cat(10609) | SysOpenat(int dfd: 4294967196, const char * filename: /etc/ld.so.cache, int flags: 524288, umode_t mode: 0) = 3
cat(10609) | SysNewfstatat(int dfd: 3, const char * filename: NULL, struct stat * statbuf: {uint st_dev: 2049, uint st_ino: 1988, uint st_nlink: 1, uint st_mode: 33188, uint st_uid: 0, uint st_gid: 0, uint __pad0: 0, uint st_rdev: 0, int st_size: 34082, int st_blksize: 4096, int st_blocks: 72, uint st_atime: 1632575756, uint st_atime_nsec: 340000086, uint st_mtime: 1632478351, uint st_mtime_nsec: 721952010, uint st_ctime: 1632478351, uint st_ctime_nsec: 721952010, array __unused: {int 0: 0, int 1: 0, int 2: 0}}, int flag: 4096) = 0
cat(10609) | SysMmap(unsigned long addr: 0, unsigned long len: 34082, unsigned long prot: 1, unsigned long flags: 2, unsigned long fd: 3, unsigned long off: 0) = 140402711691264
cat(10609) | SysClose(unsigned int fd: 3) = 0
cat(10609) | SysOpenat(int dfd: 4294967196, const char * filename: /lib/x86_64-linux-gnu/libc.so.6, int flags: 524288, umode_t mode: 0) = 3
cat(10609) | SysRead(unsigned int fd: 3, char * buf: 0x7f454c4602010103..., size_t count: 832) = 832
cat(10609) | SysPread64(unsigned int fd: 3, char * buf: 0x0600000004000000..., size_t count: 784, loff_t pos: 64) = 784
cat(10609) | SysPread64(unsigned int fd: 3, char * buf: 0x0400000020000000..., size_t count: 48, loff_t pos: 848) = 48
cat(10609) | SysPread64(unsigned int fd: 3, char * buf: 0x0400000014000000..., size_t count: 68, loff_t pos: 896) = 68
[...]
cat(10609) | SysOpenat(int dfd: 4294967196, const char * filename: /etc/hosts, int flags: 0, umode_t mode: 0) = 3
cat(10609) | SysFstat(unsigned int fd: 3, struct stat * statbuf: {uint st_dev: 2049, uint st_ino: 44, uint st_nlink: 1, uint st_mode: 33188, uint st_uid: 0, uint st_gid: 0, uint __pad0: 0, uint st_rdev: 0, int st_size: 262, int st_blksize: 4096, int st_blocks: 8, uint st_atime: 1632575760, uint st_atime_nsec: 904000225, uint st_mtime: 1627389290, uint st_mtime_nsec: 284000207, uint st_ctime: 1627389290, uint st_ctime_nsec: 284000207, array __unused: {int 0: 0, int 1: 0, int 2: 0}}) = 0
cat(10609) | SysFadvise64(int fd: 3, loff_t offset: 0, size_t len: 0, int advice: 2) = 0
cat(10609) | SysMmap(unsigned long addr: 0, unsigned long len: 139264, unsigned long prot: 3, unsigned long flags: 34, unsigned long fd: 4294967295, unsigned long off: 0) = 140402704576512
cat(10609) | SysRead(unsigned int fd: 3, char * buf: 127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
127.0.1.1 ubuntu-hirsute ubuntu-hirsute
, size_t count: 131072) = 262
cat(10609) | SysWrite(unsigned int fd: 1, const char * buf: 127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
127.0.1.1 ubuntu-hirsute ubuntu-hirsute
, size_t count: 262) = 262
cat(10609) | SysRead(unsigned int fd: 3, char * buf: NULL, size_t count: 131072) = 0
cat(10609) | SysMunmap(unsigned long addr: 140402704576512, size_t len: 139264) = 0
cat(10609) | SysClose(unsigned int fd: 3) = 0
cat(10609) | SysClose(unsigned int fd: 1) = 0
cat(10609) | SysClose(unsigned int fd: 2) = 0
- The golang code is under Apache 2.0 License.
- The eBPF programs are under the GPL v2 License.