├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── COPYING ├── Makefile.am ├── README.md ├── TODO ├── autogen.sh ├── configure.ac ├── include ├── Makefile.am ├── linux │ ├── bpf.h │ └── bpf_common.h └── ply │ ├── .gitignore │ ├── arch.h │ ├── buffer.h │ ├── func.h │ ├── internal.h │ ├── ir.h │ ├── kallsyms.h │ ├── node.h │ ├── perf_event.h │ ├── ply.h │ ├── printxf.h │ ├── provider.h │ ├── sym.h │ ├── syscall.h │ ├── type.h │ └── utils.h ├── lib └── qsort_r.c ├── man ├── .gitignore ├── Makefile.am ├── index.txt └── ply.1.ronn ├── scripts ├── execsnoop.ply ├── net-rx.ply └── opensnoop.ply ├── src ├── Makefile.am ├── libply │ ├── .gitignore │ ├── Makefile.am │ ├── arch │ │ ├── aarch64.c │ │ ├── arm.c │ │ ├── loongarch64.c │ │ ├── mips.c │ │ ├── powerpc.c │ │ ├── riscv32.c │ │ ├── riscv64.c │ │ └── x86_64.c │ ├── aux │ │ ├── kallsyms.c │ │ ├── perf_event.c │ │ ├── printxf.c │ │ ├── syscall.c │ │ └── utils.c │ ├── built-in │ │ ├── aggregation.c │ │ ├── buffer.c │ │ ├── built-in.c │ │ ├── built-in.h │ │ ├── flow.c │ │ ├── math.c │ │ ├── memory.c │ │ ├── print.c │ │ └── proc.c │ ├── compile.c │ ├── func.c │ ├── grammar.y │ ├── ir.c │ ├── lexer.l │ ├── libply.c │ ├── node.c │ ├── provider.c │ ├── provider │ │ ├── interval.c │ │ ├── kprobe.c │ │ ├── kprobe.h │ │ ├── kretprobe.c │ │ ├── profile.c │ │ ├── special.c │ │ ├── tracepoint.c │ │ ├── xprobe.c │ │ └── xprobe.h │ ├── sym.c │ └── type.c └── ply │ ├── .gitignore │ ├── Makefile.am │ ├── ply-gdb.py │ ├── ply.c │ └── self-test.sh └── test ├── .gitignore ├── Makefile ├── README.md └── rootfs ├── etc ├── fstab ├── hostname ├── init.d │ └── test.sh ├── inittab └── shadow └── lib └── ply ├── test-wrapper.sh └── test.sh /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | build: 13 | name: ${{ matrix.arch }} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | arch: [armv7, aarch64, x86_64] 18 | fail-fast: false 19 | env: 20 | MAKEFLAGS: -j 21 | steps: 22 | - name: Install dependencies 23 | run: | 24 | sudo apt-get -y update 25 | sudo apt-get -y install autoconf automake bison flex qemu-system-arm qemu-system-x86 26 | 27 | - uses: actions/checkout@v4 28 | with: 29 | clean: true 30 | 31 | - name: Restore cache of test/cache 32 | uses: actions/cache@v4 33 | with: 34 | path: test/cache 35 | key: cache-${{ matrix.arch }}-${{ hashFiles('test/Makefile') }} 36 | restore-keys: | 37 | cache-${{ matrix.arch }}- 38 | 39 | - name: Build 40 | run: | 41 | make -C test ${{ matrix.arch }}-build 42 | 43 | - name: Check 44 | run: | 45 | make -C test ${{ matrix.arch }}-check 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .#* 3 | *.o 4 | *.lo 5 | *.la 6 | .libs 7 | .gdb_history 8 | 9 | # autoconf/automake generated files 10 | .deps 11 | .dirstamp 12 | Makefile 13 | Makefile.in 14 | /aclocal.m4 15 | /autom4te.cache 16 | /compile 17 | /config.* 18 | /configure 19 | /depcomp 20 | /install-sh 21 | /*libtool 22 | /ltmain.sh 23 | /m4 24 | /missing 25 | /stamp-h1 26 | /ylwrap 27 | 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.4.0] - 2025-02-16 4 | 5 | ### Added 6 | 7 | - New architecture: mips (yejq) 8 | - New architecture: loongarch (wuruilong) 9 | - Output buffering can now be unconditionally disabled 10 | - New provider: profile (Ism Hong) 11 | 12 | ### Fixed 13 | 14 | - Incorrect stack management when accessing tracepoint data (Bjorn 15 | Andersson) 16 | 17 | 18 | ## [2.3.0] - 2022-11-29 19 | 20 | Add support for riscv64. Minimum supported kernel version is now 5.5. 21 | 22 | ### Changed 23 | 24 | - New implementation of `BEGIN`/`END` which is more reliable across 25 | architectures. (Mingzheng Xing) 26 | 27 | ### Added 28 | 29 | - New architecture: riscv64 (Mingzheng Xing) 30 | 31 | 32 | ## [2.2.0] - 2021-11-26 33 | 34 | ### Changed 35 | 36 | - kprobe wildcards are now filtered through 37 | available_filter_functions, if available, making them much more 38 | reliable. 39 | 40 | ### Added 41 | 42 | - Self-test (ply -T) to automatically diagnose the most common 43 | configuration issues. 44 | - `sum()` aggregation (Namhyung Kim). 45 | - `BEGIN` and `END` probes that run at the beginning/end of a script 46 | (Namhyung Kim). 47 | - `interval` provider to run a probe at a specified interval (Namhyung 48 | Kim). 49 | - Access to dynamic tracepoint data, i.e. members marked with the 50 | `__data_loc` attribute. 51 | 52 | ### Fixed 53 | 54 | - A bunch of parsing errors from weird scripts. Found via fuzzing done 55 | by Juraj Vijtiuk. 56 | - Static linking is now supported (Namhyung Kim) 57 | - Data layout issues with some tracepoints. 58 | 59 | ## [2.1.1] - 2020-04-22 60 | 61 | ### Changed 62 | 63 | - Disable the kernel verifier output by default. Newer kernels 64 | generates __massive__ amounts of verifier output for certain BPF 65 | programs. It expects to be able to store up to 16MB (!) of text. On 66 | some systems using `ply`, that is half of the total system 67 | RAM. Instead, the verifier output is now enabled by specifying the 68 | `-d`/`--debug` option. 69 | 70 | ### Added 71 | 72 | - Allow lossy tracing. By default ply will exit when it detects loss 73 | of any trace events. The new `-k`/`--keep-going` option allows the 74 | user to disable this safety check. 75 | - `ply` can now be built against alternative libcs. In particular 76 | Glibc and musl are known to work. 77 | - VPATH builds are now supported. 78 | - Basic automatic test system. This ensures that basic ply can be 79 | built against all supported architectures and that basic probes work 80 | as expected. 81 | 82 | ### Fixed 83 | 84 | - When expanding wildcards in probe specifiers, avoid symbols that we 85 | know are untraceable. 86 | - Symbol lookups (typically in stack traces) now always return the 87 | correct symbol. 88 | - Multiple references to `stack` no longer results in a `SIGSEGV`. 89 | - The type information from `caller`/`retval` is now retained in all 90 | cases. 91 | 92 | ## [2.1.0] - 2018-11-01 93 | 94 | ### Added 95 | 96 | - `tracepoint` provider to use the kernel's stable tracepoints. 97 | - `delete` keyword to remove associations from a map. 98 | - Numeric constants can now be in binary using the `0b` prefix. 99 | - Numeric constants can be grouped by insering `_`'s, i.e. one million 100 | could be written as `1_000_000` a 32-bit address could be written as 101 | `0xc1ab_dead` 102 | 103 | ### Fixed 104 | 105 | - Architecture specific files are now a part of the distribution 106 | tarball (#10). 107 | - Multiple off-by-one issues with string literals have been fixed 108 | (#14). 109 | - Unary operators now work with arguments that require AST rewrites 110 | (#11). 111 | 112 | ## [2.0.0] - 2018-10-18 113 | 114 | The entire compiler has been re-written using the lessons learned from 115 | the limitations of v1. There is now a proper type system that be 116 | extended to describe all C types, there is an intermediate 117 | representation (IR) that makes instruction selection much easier. Some 118 | NIH accidents regarding the grammar have been corrected to align with 119 | existing work (DTrace). Error messages on invalid scripts should be 120 | more helpful, though there is still much to be done in this area. 121 | 122 | ### Changed 123 | - Everything 124 | 125 | ### Removed 126 | - Tracepoint provider. Not ported to v2 yet. 127 | - Profile provider. Not ported to v2 yet. 128 | - uprobe provider. Not ported to v2 yet. 129 | 130 | ### Added 131 | - PowerPC support. 132 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | if HAVE_RONN 2 | MAN_DIR = man 3 | endif 4 | 5 | SUBDIRS = include $(MAN_DIR) src 6 | doc_DATA = README.md COPYING 7 | EXTRA_DIST = CHANGELOG.md README.md 8 | DISTCLEANFILES = *~ *.d 9 | 10 | ACLOCAL_AMFLAGS = -I m4 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ply 2 | === 3 | 4 | Documentation and language reference is available at 5 | [wkz.github.io/ply][3]. 6 | 7 | A light-weight dynamic tracer for Linux that leverages the kernel's 8 | BPF VM in concert with kprobes and tracepoints to attach probes to 9 | arbitrary points in the kernel. Most tracers that generate BPF 10 | bytecode are based on the LLVM based BCC toolchain. ply on the other 11 | hand has no required external dependencies except for `libc`. In 12 | addition to `x86_64`, ply also runs on `aarch64`, `arm`, `loongarch`, 13 | `mips`, `riscv64`, `riscv32`, and `powerpc`. Adding support for more 14 | ISAs is easy. 15 | 16 | `ply` follows the [Little Language][1] approach of yore, compiling ply 17 | scripts into Linux [BPF][2] programs that are attached to kprobes and 18 | tracepoints in the kernel. The scripts have a C-like syntax, heavily 19 | inspired by `dtrace(1)` and, by extension, `awk(1)`. 20 | 21 | The primary goals of `ply` are: 22 | 23 | * Expose most of the BPF tracing feature-set in such a way that new 24 | scripts can be whipped up very quickly to test different 25 | hypotheses. 26 | 27 | * Keep dependencies to a minimum. Right now Flex and Bison are 28 | required at build-time, leaving `libc` as the only runtime 29 | dependency. Thus, `ply` is well suited for embedded targets. 30 | 31 | If you need more fine-grained control over the kernel/userspace 32 | interaction in your tracing, checkout the [bcc][4] project which 33 | compiles C programs to BPF using LLVM in combination with a python 34 | userspace recipient to give you the full six degrees of freedom. 35 | 36 | 37 | Examples 38 | -------- 39 | 40 | Here are some one-liner examples to show the kinds of questions that 41 | `ply` can help answer. 42 | 43 | **What is the distribution of the returned sizes from `read(2)`s to the VFS?** 44 | ``` 45 | ply 'kretprobe:vfs_read { @["size"] = quantize(retval); }' 46 | ``` 47 | 48 | **Which processes are receiving errors when reading from the VFS?** 49 | ``` 50 | ply 'kretprobe:vfs_read if (retval < 0) { @[pid, comm, retval] = count(); }' 51 | ``` 52 | 53 | **Which files are being opened, by who?** 54 | ``` 55 | ply 'kprobe:do_sys_open { printf("%v(%v): %s\n", comm, uid, str(arg1)); }' 56 | ``` 57 | 58 | **When sending packets, where are we coming from?** 59 | ``` 60 | ply 'kprobe:dev_queue_xmit { @[stack] = count(); }' 61 | ``` 62 | 63 | **From which hosts and ports are we receiving TCP resets?** 64 | ``` 65 | ply 'tracepoint:tcp/tcp_receive_reset { 66 | printf("saddr:%v port:%v->%v\n", 67 | data->saddr, data->sport, data->dport); 68 | }' 69 | ``` 70 | 71 | 72 | Build and Installation 73 | ---------------------- 74 | 75 | `ply` uses GNU's autotools as its build system. When building from 76 | a Git clone, use the following steps: 77 | 78 | ``` 79 | ./autogen.sh # to generate the configure script 80 | ./configure 81 | make 82 | make install # you probably need to be root for this 83 | ``` 84 | 85 | Contributing 86 | ------------ 87 | 88 | Contributions are welcome! To help you on your way, the [test/](test/) 89 | directory contains ready-made infrastructure to: 90 | 91 | - Test cross-compilation on all supported architectures. 92 | - Run a simple test suite on a range of machines using QEMU system 93 | emulation. 94 | - Run interactive sessions on QEMU machines. 95 | 96 | A GitHub Action is setup to run these jobs. Please make sure to test 97 | your changes locally before opening a PR to avoid unnecessary review 98 | cycles. 99 | 100 | Maintainers 101 | ----------- 102 | 103 | `ply` is developed and maintained by [Tobias Waldekranz][5]. Please 104 | direct all bug reports and pull requests towards the official 105 | [Github][6] repo. 106 | 107 | [1]: http://c2.com/cgi/wiki?LittleLanguage 108 | [2]: https://www.kernel.org/doc/Documentation/networking/filter.txt 109 | [3]: https://wkz.github.io/ply 110 | [4]: https://github.com/iovisor/bcc 111 | [5]: mailto://tobias@waldekranz.com 112 | [6]: https://github.com/iovisor/ply 113 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * general 2 | ** X ply script license, option to ply (can go in hashbang) 3 | ** X sigint handling 4 | ** X pspec wildcards 5 | ** kallsyms resolve 6 | 7 | * printf 8 | ** X width 9 | ** X overrun 10 | 11 | * map 12 | ** X dump 13 | *** X records 14 | ** hints 15 | *** type/length 16 | ** X methods (aggregations) 17 | *** X count (dump:hbar) 18 | *** X quantize (log/lin dump:hbar) 19 | *** ! checkpoint (dump:hbar per delta) 20 | 21 | * doc 22 | ** license 23 | ** man page 24 | ** readme 25 | ** web page 26 | 27 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | autoreconf -W portability -visfm 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.61) 2 | AC_INIT(ply, [m4_esyscmd_s(git describe --always --dirty)], 3 | https://github.com/wkz/ply/issues) 4 | 5 | AC_GNU_SOURCE 6 | AM_INIT_AUTOMAKE(1.11 foreign subdir-objects) 7 | AM_SILENT_RULES(yes) 8 | 9 | LT_INIT 10 | 11 | AC_CONFIG_HEADER(config.h) 12 | AC_CONFIG_FILES([ 13 | Makefile 14 | include/Makefile 15 | man/Makefile 16 | src/Makefile 17 | src/libply/Makefile 18 | src/ply/Makefile 19 | ]) 20 | AC_CONFIG_MACRO_DIRS(m4) 21 | 22 | AC_PROG_CC 23 | AC_PROG_INSTALL 24 | AC_PROG_LEX 25 | AC_PROG_YACC 26 | AM_PROG_LIBTOOL 27 | 28 | AC_CHECK_PROG(HAVE_RONN,ronn,yes) 29 | AM_CONDITIONAL(HAVE_RONN, test "$HAVE_RONN" = "yes") 30 | 31 | AC_HEADER_STDC 32 | AC_CHECK_HEADERS(linux/bpf.h linux/perf_event.h linux/version.h) 33 | 34 | AC_CONFIG_LIBOBJ_DIR(lib) 35 | AC_REPLACE_FUNCS(qsort_r) 36 | 37 | AC_CANONICAL_HOST 38 | AC_SUBST(arch) 39 | 40 | AS_CASE($host_cpu, 41 | arm*, arch=arm, 42 | mips*, arch=mips, 43 | arch=$host_cpu) 44 | 45 | AC_OUTPUT 46 | -------------------------------------------------------------------------------- /include/Makefile.am: -------------------------------------------------------------------------------- 1 | nobase_include_HEADERS = \ 2 | ply/arch.h \ 3 | ply/buffer.h \ 4 | ply/func.h \ 5 | ply/internal.h \ 6 | ply/ir.h \ 7 | ply/kallsyms.h \ 8 | ply/node.h \ 9 | ply/perf_event.h \ 10 | ply/ply.h \ 11 | ply/printxf.h \ 12 | ply/provider.h \ 13 | ply/sym.h \ 14 | ply/syscall.h \ 15 | ply/type.h \ 16 | ply/utils.h 17 | -------------------------------------------------------------------------------- /include/linux/bpf_common.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | #ifndef _UAPI__LINUX_BPF_COMMON_H__ 3 | #define _UAPI__LINUX_BPF_COMMON_H__ 4 | 5 | /* Instruction classes */ 6 | #define BPF_CLASS(code) ((code) & 0x07) 7 | #define BPF_LD 0x00 8 | #define BPF_LDX 0x01 9 | #define BPF_ST 0x02 10 | #define BPF_STX 0x03 11 | #define BPF_ALU 0x04 12 | #define BPF_JMP 0x05 13 | #define BPF_RET 0x06 14 | #define BPF_MISC 0x07 15 | 16 | /* ld/ldx fields */ 17 | #define BPF_SIZE(code) ((code) & 0x18) 18 | #define BPF_W 0x00 /* 32-bit */ 19 | #define BPF_H 0x08 /* 16-bit */ 20 | #define BPF_B 0x10 /* 8-bit */ 21 | /* eBPF BPF_DW 0x18 64-bit */ 22 | #define BPF_MODE(code) ((code) & 0xe0) 23 | #define BPF_IMM 0x00 24 | #define BPF_ABS 0x20 25 | #define BPF_IND 0x40 26 | #define BPF_MEM 0x60 27 | #define BPF_LEN 0x80 28 | #define BPF_MSH 0xa0 29 | 30 | /* alu/jmp fields */ 31 | #define BPF_OP(code) ((code) & 0xf0) 32 | #define BPF_ADD 0x00 33 | #define BPF_SUB 0x10 34 | #define BPF_MUL 0x20 35 | #define BPF_DIV 0x30 36 | #define BPF_OR 0x40 37 | #define BPF_AND 0x50 38 | #define BPF_LSH 0x60 39 | #define BPF_RSH 0x70 40 | #define BPF_NEG 0x80 41 | #define BPF_MOD 0x90 42 | #define BPF_XOR 0xa0 43 | 44 | #define BPF_JA 0x00 45 | #define BPF_JEQ 0x10 46 | #define BPF_JGT 0x20 47 | #define BPF_JGE 0x30 48 | #define BPF_JSET 0x40 49 | #define BPF_SRC(code) ((code) & 0x08) 50 | #define BPF_K 0x00 51 | #define BPF_X 0x08 52 | 53 | #ifndef BPF_MAXINSNS 54 | #define BPF_MAXINSNS 4096 55 | #endif 56 | 57 | #endif /* _UAPI__LINUX_BPF_COMMON_H__ */ 58 | -------------------------------------------------------------------------------- /include/ply/.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | stamp-h1 -------------------------------------------------------------------------------- /include/ply/arch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_ARCH_H 8 | #define _PLY_ARCH_H 9 | 10 | struct type; 11 | 12 | /* fixed length integers */ 13 | extern struct type t_s8; 14 | extern struct type t_u8; 15 | extern struct type t_s16; 16 | extern struct type t_u16; 17 | extern struct type t_s32; 18 | extern struct type t_u32; 19 | extern struct type t_s64; 20 | extern struct type t_u64; 21 | 22 | /* a hardware register */ 23 | extern struct type t_reg_t; 24 | 25 | /* layout of captured registers */ 26 | extern struct type t_pt_regs; 27 | 28 | /* ABI mapping of registers to arguments/return value */ 29 | const char *arch_register_argument(int num); 30 | const char *arch_register_pc (void); 31 | const char *arch_register_return (void); 32 | 33 | #endif /* _PLY_ARCH_H */ 34 | -------------------------------------------------------------------------------- /include/ply/buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_BUFFER_H 8 | #define _PLY_BUFFER_H 9 | 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | struct buffer_ev { 17 | struct perf_event_header hdr; 18 | uint32_t size; 19 | 20 | uint64_t id; 21 | uint8_t data[0]; 22 | } __attribute__((packed)); 23 | 24 | struct buffer_evh { 25 | TAILQ_ENTRY(buffer_evh) node; 26 | 27 | uint64_t id; 28 | void *priv; 29 | 30 | struct ply_return (*handle)(struct buffer_ev *ev, void *priv); 31 | }; 32 | 33 | void buffer_evh_register(struct buffer_evh *evh); 34 | 35 | struct buffer; 36 | 37 | struct buffer *buffer_new(int mapfd); 38 | 39 | struct ply_return buffer_loop(struct buffer *buf, int timeout); 40 | 41 | #endif /* _PLY_BUFFER_H */ 42 | -------------------------------------------------------------------------------- /include/ply/func.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _FUNC_H 8 | #define _FUNC_H 9 | 10 | #include 11 | 12 | struct node; 13 | struct ply_probe; 14 | struct type; 15 | 16 | struct func { 17 | const char *name; 18 | struct type *type; 19 | 20 | int static_ret:1; /* return type is statically known */ 21 | 22 | SLIST_ENTRY(func) entry; 23 | 24 | int (*static_validate)(const struct func *, struct node *); 25 | int (*type_infer) (const struct func *, struct node *); 26 | int (*rewrite) (const struct func *, struct node *, struct ply_probe *); 27 | 28 | int (*ir_pre) (const struct func *, struct node *, struct ply_probe *); 29 | int (*ir_post)(const struct func *, struct node *, struct ply_probe *); 30 | }; 31 | 32 | int func_pass_ir_post(const struct func *func, struct node *n, 33 | struct ply_probe *pb); 34 | 35 | int func_static_validate(const struct func *func, struct node *n); 36 | struct type *func_return_type(const struct func *func); 37 | 38 | #endif /* _FUNC_H */ 39 | -------------------------------------------------------------------------------- /include/ply/internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_INTERNAL_H 8 | #define _PLY_INTERNAL_H 9 | 10 | #include "arch.h" 11 | #include "buffer.h" 12 | #include "func.h" 13 | #include "ir.h" 14 | #include "node.h" 15 | #include "provider.h" 16 | #include "sym.h" 17 | #include "type.h" 18 | 19 | 20 | #include "kallsyms.h" 21 | #include "perf_event.h" 22 | #include "printxf.h" 23 | #include "syscall.h" 24 | #include "utils.h" 25 | 26 | void built_in_init(void); 27 | 28 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 29 | 30 | #endif /* _PLY_INTERNAL_H */ 31 | -------------------------------------------------------------------------------- /include/ply/ir.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_IR_H 8 | #define _PLY_IR_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | /* TODO: This is not exported in userspace headers for some reason */ 17 | #ifndef MAX_BPF_STACK 18 | #define MAX_BPF_STACK 512 19 | #endif 20 | 21 | /* TODO: TEMP workaround for old headers */ 22 | #ifndef BPF_JLE 23 | #define BPF_JLE 0xb0 /* LE is unsigned, '<=' */ 24 | #endif 25 | 26 | #define INSN(_code, _dst, _src, _off, _imm) \ 27 | ((struct bpf_insn) { \ 28 | .code = _code, \ 29 | .dst_reg = _dst, \ 30 | .src_reg = _src, \ 31 | .off = _off, \ 32 | .imm = _imm \ 33 | }) 34 | 35 | #define MOV32 INSN(BPF_ALU | BPF_MOV | BPF_X, 0, 0, 0, 0) 36 | #define MOV32_IMM(_imm) INSN(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, _imm) 37 | 38 | #define MOV64 INSN(BPF_ALU64 | BPF_MOV | BPF_X, 0, 0, 0, 0) 39 | #define MOV64_IMM(_imm) INSN(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, _imm) 40 | 41 | #define EXIT INSN(BPF_JMP | BPF_EXIT, 0, 0, 0, 0) 42 | #define CALL(_imm) INSN(BPF_JMP | BPF_CALL, 0, 0, 0, _imm) 43 | 44 | #define JMP(_op, _off) INSN(BPF_JMP | BPF_OP((_op)) | BPF_X, 0, 0, _off, 0) 45 | #define JMP_IMM(_op, _imm, _off) INSN(BPF_JMP | BPF_OP((_op)) | BPF_K, 0, 0, _off, _imm) 46 | 47 | #define ALU32(_op) INSN(BPF_ALU | BPF_OP((_op)) | BPF_X, 0, 0, 0, 0) 48 | #define ALU32_IMM(_op, _imm) INSN(BPF_ALU | BPF_OP((_op)) | BPF_K, 0, 0, 0, _imm) 49 | 50 | #define ALU64(_op) INSN(BPF_ALU64 | BPF_OP((_op)) | BPF_X, 0, 0, 0, 0) 51 | #define ALU64_IMM(_op, _imm) INSN(BPF_ALU64 | BPF_OP((_op)) | BPF_K, 0, 0, 0, _imm) 52 | 53 | #define STX(_width, _off) INSN(BPF_STX | BPF_SIZE(_width) | BPF_MEM, 0, 0, _off, 0) 54 | #define ST_IMM(_width, _off, _imm) INSN(BPF_ST | BPF_SIZE(_width) | BPF_MEM, 0, 0, _off, _imm) 55 | #define ST_XADD(_width, _off) INSN(BPF_STX | BPF_SIZE(_width) | BPF_XADD, 0, 0, _off, 0) 56 | 57 | #define LDX(_width, _off) INSN(BPF_LDX | BPF_SIZE(_width) | BPF_MEM, 0, 0, _off, 0) 58 | #define LDDW_IMM(_imm) INSN(BPF_LD | BPF_DW | BPF_IMM, 0, 0, 0, _imm) 59 | 60 | #if UINTPTR_MAX == 0xffffffffffffffff 61 | # define MOV MOV64 62 | # define MOV_IMM MOV64_IMM 63 | # define ALU ALU64 64 | # define ALU_IMM ALU64_IMM 65 | #elif UINTPTR_MAX == 0xffffffff 66 | # define MOV MOV32 67 | # define MOV_IMM MOV32_IMM 68 | # define ALU ALU32 69 | # define ALU_IMM ALU32_IMM 70 | #else 71 | # error "Only 32- and 64-bit machines are supported." 72 | #endif 73 | 74 | #define BPF_REG_BP BPF_REG_10 75 | 76 | /* r0 is return value and r1-r5 are used for arguments */ 77 | #define BPF_REG_CALLER_SAVE 0x3f 78 | 79 | static inline int bpf_width(size_t size) 80 | { 81 | switch (size) { 82 | case 1: return BPF_B; 83 | case 2: return BPF_H; 84 | case 4: return BPF_W; 85 | case 8: return BPF_DW; 86 | } 87 | 88 | return -1; 89 | } 90 | 91 | struct sym; 92 | struct type; 93 | 94 | enum vitype { 95 | VI_INSN, 96 | VI_LDMAP, 97 | VI_LABEL, 98 | VI_COMMENT, 99 | }; 100 | 101 | struct vinsn { 102 | enum vitype vitype; 103 | 104 | union { 105 | struct { 106 | struct bpf_insn bpf; 107 | uint16_t dst; 108 | uint16_t src; 109 | } insn; 110 | 111 | struct { 112 | uint16_t reg; 113 | struct sym *sym; 114 | } map; 115 | 116 | int16_t label; 117 | 118 | const char *comment; 119 | }; 120 | }; 121 | 122 | struct ir { 123 | struct vinsn *vi; 124 | size_t len; 125 | 126 | int16_t next_label; 127 | uint16_t next_reg; 128 | 129 | ssize_t sp; 130 | }; 131 | 132 | enum irloc { 133 | LOC_IMM = (1 << 0), 134 | LOC_REG = (1 << 1), 135 | LOC_STACK = (1 << 2), 136 | }; 137 | 138 | 139 | struct irstate { 140 | int loc; 141 | 142 | size_t size; 143 | int32_t stack; 144 | int32_t imm; 145 | uint16_t reg; 146 | 147 | struct { 148 | int dot:1; 149 | int lval:1; 150 | int stack:1; 151 | } hint; 152 | }; 153 | 154 | void insn_dump(struct bpf_insn insn, FILE *fp); 155 | void vinsn_dump(struct vinsn *vi, FILE *fp); 156 | void ir_dump(struct ir *ir, FILE *fp); 157 | 158 | int16_t ir_alloc_label(struct ir *ir); 159 | 160 | void ir_init_irs(struct ir *ir, struct irstate *irs, struct type *t); 161 | void ir_init_sym(struct ir *ir, struct sym *sym); 162 | 163 | void ir_emit_insn (struct ir *ir, struct bpf_insn bpf, uint16_t dst, uint16_t src); 164 | void ir_emit_ldmap (struct ir *ir, uint16_t dst, struct sym *map); 165 | void ir_emit_label (struct ir *ir, int16_t label); 166 | void ir_emit_comment(struct ir *ir, const char *comment); 167 | 168 | static inline void ir_emit_ldbp(struct ir *ir, uint16_t dst, ssize_t offset) 169 | { 170 | /* Always use 64-bit operations. Otherwise kernel will mark 171 | * `dst` as invalid even if the underlying ISA is 32-bit. */ 172 | ir_emit_insn(ir, MOV64, dst, BPF_REG_BP); 173 | ir_emit_insn(ir, ALU64_IMM(BPF_ADD, offset), dst, 0); 174 | } 175 | 176 | void ir_emit_sym_to_reg (struct ir *ir, uint16_t dst, struct sym *src); 177 | void ir_emit_reg_to_sym (struct ir *ir, struct sym *dst, uint16_t src); 178 | void ir_emit_sym_to_stack(struct ir *ir, ssize_t offset, struct sym *src); 179 | void ir_emit_sym_to_sym (struct ir *ir, struct sym *dst, struct sym *src); 180 | void ir_emit_read_to_sym (struct ir *ir, struct sym *dst, uint16_t src); 181 | 182 | void ir_emit_data (struct ir *ir, ssize_t dst, const char *src, size_t size); 183 | void ir_emit_memcpy(struct ir *ir, ssize_t dst, ssize_t src, size_t size); 184 | void ir_emit_bzero (struct ir *ir, ssize_t offset, size_t size); 185 | 186 | void ir_emit_perf_event_output(struct ir *ir, 187 | struct sym *map, struct sym *regs, struct sym *ev); 188 | 189 | struct ir *ir_new(void); 190 | 191 | int ir_bpf_generate(struct ir *ir); 192 | int ir_bpf_extract (struct ir *ir, struct bpf_insn **insnsp, int *n_insnsp); 193 | 194 | #endif /* _PLY_IR_H */ 195 | -------------------------------------------------------------------------------- /include/ply/kallsyms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef __KALLSYMS_H 8 | #define __KALLSYMS_H 9 | 10 | #include 11 | 12 | struct ksym { 13 | uintptr_t addr; 14 | char sym[0x40 - sizeof(uintptr_t)]; 15 | }; 16 | 17 | struct ksym_cache_hdr { 18 | uint32_t n_syms; 19 | char _reserved[0x40 - sizeof(uint32_t)]; 20 | }; 21 | 22 | struct ksym_cache { 23 | struct ksym_cache_hdr hdr; 24 | 25 | struct ksym sym[0]; 26 | }; 27 | 28 | struct ksyms { 29 | int cache_fd; 30 | struct ksym_cache *cache; 31 | }; 32 | 33 | int ksym_fprint(struct ksyms *ks, FILE *fp, uintptr_t addr); 34 | const struct ksym *ksym_get(struct ksyms *ks, uintptr_t addr); 35 | 36 | void ksyms_free(struct ksyms *ks); 37 | struct ksyms *ksyms_new(void); 38 | 39 | #define ksyms_foreach(_sym, _ks) \ 40 | for ((_sym) = &(_ks)->cache->sym[1]; \ 41 | (_sym) < &(_ks)->cache->sym[(_ks)->cache->hdr.n_syms - 2]; \ 42 | (_sym)++) 43 | 44 | #endif /* __KALLSYMS_H */ 45 | -------------------------------------------------------------------------------- /include/ply/node.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_NODE_H 8 | #define _PLY_NODE_H 9 | 10 | #include 11 | #include 12 | 13 | /* symbol information is defined externally */ 14 | struct sym; 15 | 16 | enum ntype { 17 | N_EXPR, 18 | N_NUM, 19 | N_STRING, 20 | }; 21 | 22 | /* source location info, this is identical to bison's default YYLTYPE. 23 | * defining it here means we can keep parser dependencies out of the 24 | * rest of the code base. */ 25 | struct nloc { 26 | int first_line; 27 | int first_column; 28 | int last_line; 29 | int last_column; 30 | }; 31 | 32 | struct node { 33 | struct node *next, *prev, *up; 34 | 35 | struct sym *sym; 36 | 37 | enum ntype ntype; 38 | 39 | union { 40 | struct { 41 | char *func; 42 | struct node *args; 43 | unsigned ident:1; 44 | } expr; 45 | struct { 46 | union { 47 | int64_t s64; 48 | uint64_t u64; 49 | }; 50 | unsigned unsignd:1; 51 | unsigned size:4; 52 | } num; 53 | struct { 54 | char *data; 55 | unsigned virtual:1; 56 | } string; 57 | }; 58 | 59 | struct nloc loc; 60 | }; 61 | 62 | /* debug */ 63 | void node_print(struct node *n, FILE *fp); 64 | 65 | typedef int (*nwalk_fn)(struct node *, void *); 66 | int node_walk(struct node *n, nwalk_fn pre, nwalk_fn post, void *ctx); 67 | 68 | int node_replace(struct node *n, struct node *new); 69 | 70 | 71 | /* constructors */ 72 | struct node *node_string (const struct nloc *loc, char *data); 73 | struct node *__node_num (const struct nloc *loc, size_t size, 74 | int64_t *s64, uint64_t *u64); 75 | struct node *node_num (const struct nloc *loc, const char *numstr); 76 | void node_insert (struct node *prev, struct node *n); 77 | struct node *node_append (struct node *head, struct node *tail); 78 | struct node *node_expr_append(const struct nloc *loc, struct node *n, struct node *arg); 79 | struct node *node_expr (const struct nloc *loc, char *func, ...); 80 | struct node *node_expr_ident (const struct nloc *loc, char *func); 81 | 82 | 83 | /* helpers */ 84 | 85 | static inline int node_nargs(struct node *n) 86 | { 87 | struct node *arg; 88 | int nargs = 0; 89 | 90 | for (arg = n->expr.args; arg; arg = arg->next, nargs++); 91 | 92 | return nargs; 93 | } 94 | 95 | int node_is(struct node *n, const char *func); 96 | 97 | #define node_expr_foreach(_expr, _arg) \ 98 | for ((_arg) = (_expr)->expr.args; (_arg); (_arg) = (_arg)->next) 99 | 100 | #endif /* _PLY_NODE_H */ 101 | -------------------------------------------------------------------------------- /include/ply/perf_event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_PERF_EVENT_H 8 | #define _PLY_PERF_EVENT_H 9 | 10 | #define TRACEPATH "/sys/kernel/debug/tracing/" 11 | 12 | struct ply_probe; 13 | 14 | int perf_event_attach(struct ply_probe *pb, const char *name, 15 | int task_mode); 16 | int perf_event_attach_raw(struct ply_probe *pb, int type, unsigned long config, 17 | unsigned long long period, int task_mode); 18 | int perf_event_attach_profile(struct ply_probe *pb, int cpu, 19 | unsigned long long freq); 20 | 21 | int perf_event_enable (int group_fd); 22 | int perf_event_disable(int group_fd); 23 | 24 | #endif /* _PLY_PERF_EVENT_H */ 25 | -------------------------------------------------------------------------------- /include/ply/ply.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_H 8 | #define _PLY_H 9 | 10 | #include 11 | 12 | #include "sym.h" 13 | #include "utils.h" 14 | 15 | struct ksyms; 16 | struct ply; 17 | struct node; 18 | struct ir; 19 | 20 | struct ply_return { 21 | int val; 22 | unsigned err:1; 23 | unsigned exit:1; 24 | }; 25 | 26 | /* api */ 27 | struct ply_probe { 28 | struct ply_probe *next, *prev; 29 | struct ply *ply; 30 | 31 | char *probe; 32 | struct node *ast; 33 | 34 | struct symtab locals; 35 | 36 | struct provider *provider; 37 | void *provider_data; 38 | 39 | struct ir *ir; 40 | int bpf_fd; 41 | int special; 42 | }; 43 | 44 | struct ply_config { 45 | size_t map_elems; 46 | size_t string_size; 47 | size_t buf_pages; /* number of memory pages, per-cpu, per buffer */ 48 | size_t stack_depth; 49 | 50 | unsigned unicode:1; /* allow unicode in output. */ 51 | unsigned hex:1; /* prefer hexadecimal output for scalars. */ 52 | unsigned sort:1; /* sort maps before output, requires more memory. */ 53 | unsigned ksyms:1; /* create ksyms cache. */ 54 | unsigned strict:1; /* abort on error. */ 55 | unsigned verify:1; /* capture verifier output, uses 16M of memory. */ 56 | }; 57 | 58 | extern struct ply_config ply_config; 59 | 60 | struct ply { 61 | struct sym *stdbuf; 62 | 63 | struct ply_probe *probes; 64 | struct symtab globals; 65 | struct ksyms *ksyms; 66 | 67 | char *group; 68 | int group_fd; 69 | }; 70 | 71 | #define ply_probe_foreach(_ply, _probe) \ 72 | for ((_probe) = (_ply)->probes; (_probe); (_probe) = (_probe)->next) 73 | 74 | static inline struct ply_probe *sym_to_probe(struct sym *sym) 75 | { 76 | if (sym->st->global) 77 | return NULL; 78 | 79 | return container_of(sym->st, struct ply_probe, locals); 80 | } 81 | 82 | void ply_maps_print(struct ply *ply); 83 | void ply_map_print(struct ply *ply, struct sym *sym, FILE *fp); 84 | void ply_map_clear(struct ply *ply, struct sym *sym); 85 | 86 | struct ply_return ply_loop(struct ply *ply); 87 | 88 | int ply_start(struct ply *ply); 89 | int ply_stop(struct ply *ply); 90 | 91 | struct ply_return ply_load(struct ply *ply); 92 | int ply_unload(struct ply *ply); 93 | 94 | int ply_add_probe(struct ply *ply, struct ply_probe *probe); 95 | int ply_compile(struct ply *ply); 96 | 97 | 98 | int ply_fparse(struct ply *ply, FILE *fp); 99 | int ply_parsef(struct ply *ply, const char *fmt, ...); 100 | void ply_free (struct ply *ply); 101 | int ply_alloc (struct ply **plyp); 102 | 103 | void ply_init(void); 104 | 105 | #endif /* _PLY_H */ 106 | -------------------------------------------------------------------------------- /include/ply/printxf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PRINTXF_H 8 | #define _PRINTXF_H 9 | 10 | #include 11 | #include 12 | 13 | struct printxf; 14 | 15 | typedef int (*vfprintxf_fn)(struct printxf *pxf, 16 | FILE *fp, const char *spec, va_list ap); 17 | 18 | typedef int (*xfprintxf_fn)(struct printxf *pxf, 19 | FILE *fp, const char *spec, void *priv); 20 | 21 | extern int printxf_vfprintf(struct printxf *pxf, 22 | FILE *fp, const char *spec, va_list ap); 23 | 24 | struct printxf { 25 | vfprintxf_fn vfprintxf[0x80]; 26 | xfprintxf_fn xfprintxf[0x80]; 27 | }; 28 | 29 | extern struct printxf printxf_default; 30 | 31 | int xfprintxf(struct printxf *pxf, FILE *stream, const char *fmt, void *priv); 32 | int vfprintxf(struct printxf *pxf, FILE *stream, const char *fmt, va_list ap); 33 | int fprintxf(struct printxf *pxf, FILE *stream, const char *fmt, ...); 34 | int vprintxf(struct printxf *pxf, const char *fmt, va_list ap); 35 | int printxf(struct printxf *pxf, const char *fmt, ...); 36 | 37 | #endif /* _PRINTXF_H */ 38 | -------------------------------------------------------------------------------- /include/ply/provider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PROVIDER_H 8 | #define _PROVIDER_H 9 | 10 | #include 11 | 12 | #include 13 | 14 | struct ply; 15 | struct ply_probe; 16 | struct node; 17 | 18 | struct provider { 19 | const char *name; 20 | enum bpf_prog_type prog_type; 21 | 22 | SLIST_ENTRY(provider) entry; 23 | 24 | int (*probe) (struct ply_probe *); 25 | int (*sym_alloc)(struct ply_probe *, struct node *); 26 | int (*ir_pre) (struct ply_probe *); 27 | int (*ir_post) (struct ply_probe *); 28 | int (*attach) (struct ply_probe *); 29 | int (*detach) (struct ply_probe *); 30 | }; 31 | 32 | struct provider *provider_get(const char *name); 33 | void provider_init(void); 34 | 35 | #endif /* _PROVIDER_H */ 36 | -------------------------------------------------------------------------------- /include/ply/sym.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_SYM_H 8 | #define _PLY_SYM_H 9 | 10 | #include 11 | #include 12 | 13 | #include "ir.h" 14 | 15 | struct func; 16 | struct node; 17 | struct type; 18 | 19 | struct symtab; 20 | 21 | struct sym { 22 | struct symtab *st; 23 | 24 | const char *name; 25 | const struct func *func; 26 | 27 | struct type *type; 28 | struct irstate irs; 29 | 30 | /* TODO: move to priv */ 31 | int mapfd; 32 | 33 | void *priv; 34 | }; 35 | 36 | struct symtab { 37 | struct sym **syms; 38 | size_t len; 39 | 40 | unsigned global:1; 41 | }; 42 | 43 | #define symtab_foreach(_st, _sym) \ 44 | for((_sym) = (_st)->syms; (_sym) < &(_st)->syms[(_st)->len]; (_sym)++) 45 | 46 | struct sym *__sym_alloc(struct symtab *st, const char *name, 47 | const struct func *func); 48 | struct sym *sym_alloc(struct symtab *st, struct node *n, 49 | const struct func *func); 50 | 51 | void sym_dump(struct sym *sym, FILE *fp); 52 | void symtab_dump(struct symtab *st, FILE *fp); 53 | 54 | 55 | static inline int sym_in_reg(struct sym *sym) 56 | { 57 | return sym->irs.loc == LOC_REG; 58 | } 59 | 60 | static inline int sym_on_stack(struct sym *sym) 61 | { 62 | return sym->irs.loc == LOC_STACK; 63 | } 64 | 65 | #endif /* _PLY_SYM_H */ 66 | -------------------------------------------------------------------------------- /include/ply/syscall.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_BPF_SYSCALL_H 8 | #define _PLY_BPF_SYSCALL_H 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | int bpf_prog_load(enum bpf_prog_type type, 17 | const struct bpf_insn *insns, int insn_cnt, 18 | char *vlog, size_t vlog_sz); 19 | 20 | int bpf_map_create(enum bpf_map_type type, int key_sz, int val_sz, int entries); 21 | 22 | int bpf_map_lookup(int fd, void *key, void *val); 23 | int bpf_map_update(int fd, void *key, void *val, int flags); 24 | int bpf_map_delete(int fd, void *key); 25 | int bpf_map_next (int fd, void *key, void *next_key); 26 | 27 | #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)) 28 | #define LINUX_HAS_STACKMAP 29 | #endif 30 | #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) 31 | #define LINUX_HAS_TRACEPOINT 32 | #endif 33 | 34 | int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, 35 | int cpu, int group_fd, unsigned long flags); 36 | 37 | int bpf_prog_test_run(int prog_fd); 38 | 39 | #endif /* _PLY_BPF_SYSCALL_H */ 40 | -------------------------------------------------------------------------------- /include/ply/type.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_TYPE_H 8 | #define _PLY_TYPE_H 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | struct sym; 16 | 17 | struct ttdef { 18 | char *name; 19 | struct type *type; 20 | }; 21 | 22 | struct tscalar { 23 | size_t size; 24 | unsigned unsignd:1; 25 | char *name; 26 | }; 27 | 28 | struct tptr { 29 | struct type *type; 30 | unsigned bpf:1; 31 | }; 32 | 33 | struct tarray { 34 | struct type *type; 35 | size_t len; 36 | }; 37 | 38 | struct tmap { 39 | struct type *vtype; 40 | struct type *ktype; 41 | 42 | enum bpf_map_type mtype; 43 | size_t len; 44 | }; 45 | 46 | struct tfield { 47 | char *name; 48 | struct type *type; 49 | 50 | size_t offset; 51 | 52 | /* TODO: bitfields */ 53 | /* uint8_t bit_offset; */ 54 | /* uint8_t bit_size; */ 55 | }; 56 | 57 | #define tfields_foreach(_f, _fields) \ 58 | for ((_f) = (_fields); (_f)->type; (_f)++) 59 | 60 | struct tfield *tfields_get(struct tfield *fields, const char *name); 61 | 62 | struct tstruct { 63 | char *name; 64 | struct tfield *fields; 65 | 66 | size_t size; 67 | unsigned packed:1; 68 | }; 69 | 70 | struct tfunc { 71 | struct type *type; 72 | struct tfield *args; 73 | 74 | unsigned vargs:1; 75 | }; 76 | 77 | enum ttype { 78 | T_VOID, 79 | T_TYPEDEF, 80 | T_SCALAR, 81 | T_POINTER, 82 | T_ARRAY, 83 | T_MAP, 84 | T_STRUCT, 85 | /* T_UNION, TODO */ 86 | T_FUNC, 87 | }; 88 | 89 | struct type { 90 | enum ttype ttype; 91 | union { 92 | struct ttdef tdef; 93 | struct tscalar scalar; 94 | struct tptr ptr; 95 | struct tarray array; 96 | struct tmap map; 97 | struct tstruct sou; 98 | struct tfunc func; 99 | }; 100 | 101 | int (*fprint)(struct type *t, FILE *fp, const void *data); 102 | void *priv; 103 | unsigned fprint_log2:1; 104 | }; 105 | 106 | struct type *type_scalar_promote(struct type *t); 107 | struct type *type_scalar_convert(struct type *a, struct type *b); 108 | 109 | int type_equal (struct type *a, struct type *b); 110 | int type_compatible(struct type *a, struct type *b); 111 | 112 | void type_dump (struct type *t, const char *name, FILE *fp); 113 | void type_dump_decl(struct type *t, FILE *fp); 114 | void type_dump_decls(FILE *fp); 115 | 116 | int type_fprint(struct type *t, FILE *fp, const void *data); 117 | int type_cmp (const void *a, const void *b, void *_type); 118 | 119 | ssize_t type_alignof(struct type *t); 120 | ssize_t type_offsetof(struct type *t, const char *field); 121 | ssize_t type_sizeof(struct type *t); 122 | 123 | void type_struct_layout(struct type *t); 124 | 125 | int type_add(struct type *t); 126 | int type_add_list(struct type **ts); 127 | 128 | struct type *type_typedef (struct type *type, const char *name); 129 | struct type *type_array_of(struct type *type, size_t len); 130 | struct type *type_map_of (struct type *ktype, struct type *vtype, 131 | enum bpf_map_type mtype, size_t len); 132 | struct type *type_ptr_of (struct type *type, unsigned bpf); 133 | 134 | 135 | /* built-in types */ 136 | 137 | extern struct type t_void; 138 | 139 | extern struct type t_char; 140 | extern struct type t_schar; 141 | extern struct type t_uchar; 142 | 143 | extern struct type t_short; 144 | extern struct type t_sshort; 145 | extern struct type t_ushort; 146 | 147 | extern struct type t_int; 148 | extern struct type t_sint; 149 | extern struct type t_uint; 150 | 151 | extern struct type t_long; 152 | extern struct type t_slong; 153 | extern struct type t_ulong; 154 | 155 | extern struct type t_llong; 156 | extern struct type t_sllong; 157 | extern struct type t_ullong; 158 | 159 | extern struct type t_binop_func; 160 | extern struct type t_unary_func; 161 | extern struct type t_vargs_func; 162 | 163 | extern struct type t_buffer; 164 | 165 | /* helpers */ 166 | 167 | static inline int type_nargs(struct type *t) 168 | { 169 | struct tfield *f; 170 | int nargs = 0; 171 | 172 | if (!t->func.args) 173 | return 0; 174 | 175 | for (f = t->func.args; f->type; f++, nargs++); 176 | 177 | return nargs; 178 | 179 | } 180 | 181 | static inline struct type *type_base(struct type *t) 182 | { 183 | while (t->ttype == T_TYPEDEF) 184 | t = t->tdef.type; 185 | 186 | return t; 187 | } 188 | 189 | 190 | static inline struct type *type_return(struct type *t) 191 | { 192 | struct type *base = type_base(t); 193 | 194 | if (base->ttype == T_FUNC) 195 | return base->func.type; 196 | 197 | return t; 198 | } 199 | 200 | static inline int type_is_string(struct type *t) 201 | { 202 | t = type_base(t); 203 | 204 | if (t->ttype != T_ARRAY) 205 | return 0; 206 | 207 | t = type_base(t->array.type); 208 | return t == &t_char; 209 | } 210 | 211 | #endif /* _PLY_TYPE_H */ 212 | -------------------------------------------------------------------------------- /include/ply/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_UTILS_H 8 | #define _PLY_UTILS_H 9 | 10 | #include 11 | 12 | int strtonum(const char *_str, int64_t *s64, uint64_t *u64); 13 | int isstring(const char *data, size_t len); 14 | 15 | FILE *fopenf(const char *mode, const char *fmt, ...) 16 | __attribute__ ((format (printf, 2, 3))); 17 | 18 | void ast_fprint(FILE *fp, struct node *root); 19 | 20 | /* This variable controls debug output for non-DEBUG build. */ 21 | extern int ply_debug; 22 | 23 | #include "printxf.h" 24 | 25 | #ifdef DEBUG 26 | #define _l(_prefix, _fmt, ...) \ 27 | fprintxf(NULL, stderr, "\e[2m%s:%d\e[0m " _prefix _fmt, \ 28 | __FILE__, __LINE__, ##__VA_ARGS__) 29 | #else 30 | #define _l(_prefix, _fmt, ...) \ 31 | fprintxf(NULL, stderr, _prefix _fmt, ##__VA_ARGS__) 32 | #endif 33 | 34 | #ifdef DEBUG 35 | #define _d(fmt, ...) _l("debug: ", fmt, ##__VA_ARGS__) 36 | #else 37 | #define _d(fmt, ...) if (ply_debug) _l("debug: ", fmt, ##__VA_ARGS__) 38 | #endif 39 | 40 | #define _i(fmt, ...) _l("info: ", fmt, ##__VA_ARGS__) 41 | #define _w(fmt, ...) _l("warning: ", fmt, ##__VA_ARGS__) 42 | #define _e(fmt, ...) _l("error: ", fmt, ##__VA_ARGS__) 43 | 44 | #define _ne(_n, fmt, ...) _l("%#N: \e[31merror:\e[0m ", fmt, _n, ##__VA_ARGS__) 45 | #define _nw(_n, fmt, ...) _l("%#N: \e[33mwarning:\e[0m ", fmt, _n, ##__VA_ARGS__) 46 | 47 | 48 | #define container_of(ptr, type, member) ({ \ 49 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 50 | (type *)( (char *)__mptr - offsetof(type,member) );}) 51 | 52 | #define max(a, b) \ 53 | ({ \ 54 | __typeof__ (a) _a = (a); \ 55 | __typeof__ (b) _b = (b); \ 56 | _a > _b ? _a : _b; \ 57 | }) 58 | 59 | #define min(a, b) \ 60 | ({ \ 61 | __typeof__ (a) _a = (a); \ 62 | __typeof__ (b) _b = (b); \ 63 | _a < _b ? _a : _b; \ 64 | }) 65 | 66 | static inline void *xcalloc(size_t nmemb, size_t size) 67 | { 68 | void *mem = calloc(nmemb, size); 69 | 70 | assert(mem); 71 | return mem; 72 | } 73 | 74 | #ifndef HAVE_QSORT_R 75 | void qsort_r(void *base, size_t nmemb, size_t size, 76 | int (*compar)(const void *, const void *, void *), void *arg); 77 | #endif 78 | 79 | #endif /* _PLY_UTILS_H */ 80 | -------------------------------------------------------------------------------- /lib/qsort_r.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Adapted from https://github.com/noporpoise/sort_r, original 3 | * copyright follows: 4 | * 5 | * Isaac Turner 29 April 2014 Public Domain 6 | */ 7 | #include 8 | #include 9 | 10 | #define SORT_R_SWAP(a,b,tmp) ((tmp) = (a), (a) = (b), (b) = (tmp)) 11 | 12 | /* swap a and b */ 13 | /* a and b must not be equal! */ 14 | static void sort_r_swap(char *__restrict a, char *__restrict b, 15 | size_t w) 16 | { 17 | char tmp, *end = a+w; 18 | for(; a < end; a++, b++) { SORT_R_SWAP(*a, *b, tmp); } 19 | } 20 | 21 | /* swap a, b iff a>b */ 22 | /* a and b must not be equal! */ 23 | /* __restrict is same as restrict but better support on old machines */ 24 | static int sort_r_cmpswap(char *__restrict a, 25 | char *__restrict b, size_t w, 26 | int (*compar)(const void *_a, 27 | const void *_b, 28 | void *_arg), 29 | void *arg) 30 | { 31 | if(compar(a, b, arg) > 0) { 32 | sort_r_swap(a, b, w); 33 | return 1; 34 | } 35 | return 0; 36 | } 37 | 38 | /* 39 | Swap consecutive blocks of bytes of size na and nb starting at memory addr ptr, 40 | with the smallest swap so that the blocks are in the opposite order. Blocks may 41 | be internally re-ordered e.g. 42 | 43 | 12345ab -> ab34512 44 | 123abc -> abc123 45 | 12abcde -> deabc12 46 | */ 47 | static void sort_r_swap_blocks(char *ptr, size_t na, size_t nb) 48 | { 49 | if(na > 0 && nb > 0) { 50 | if(na > nb) { sort_r_swap(ptr, ptr+na, nb); } 51 | else { sort_r_swap(ptr, ptr+nb, na); } 52 | } 53 | } 54 | 55 | /* Implement recursive quicksort ourselves */ 56 | /* Note: quicksort is not stable, equivalent values may be swapped */ 57 | void qsort_r(void *base, size_t nel, size_t w, 58 | int (*compar)(const void *_a, 59 | const void *_b, 60 | void *_arg), 61 | void *arg) 62 | { 63 | char *b = (char *)base, *end = b + nel*w; 64 | 65 | /* for(size_t i=0; i b && sort_r_cmpswap(pj-w,pj,w,compar,arg); pj -= w) {} 73 | } 74 | } 75 | else 76 | { 77 | /* nel > 6; Quicksort */ 78 | 79 | int cmp; 80 | char *pl, *ple, *pr, *pre, *pivot; 81 | char *last = b+w*(nel-1), *tmp; 82 | 83 | /* 84 | Use median of second, middle and second-last items as pivot. 85 | First and last may have been swapped with pivot and therefore be extreme 86 | */ 87 | char *l[3]; 88 | l[0] = b + w; 89 | l[1] = b+w*(nel/2); 90 | l[2] = last - w; 91 | 92 | /* printf("pivots: %i, %i, %i\n", *(int*)l[0], *(int*)l[1], *(int*)l[2]); */ 93 | 94 | if(compar(l[0],l[1],arg) > 0) { SORT_R_SWAP(l[0], l[1], tmp); } 95 | if(compar(l[1],l[2],arg) > 0) { 96 | SORT_R_SWAP(l[1], l[2], tmp); 97 | if(compar(l[0],l[1],arg) > 0) { SORT_R_SWAP(l[0], l[1], tmp); } 98 | } 99 | 100 | /* swap mid value (l[1]), and last element to put pivot as last element */ 101 | if(l[1] != last) { sort_r_swap(l[1], last, w); } 102 | 103 | /* 104 | pl is the next item on the left to be compared to the pivot 105 | pr is the last item on the right that was compared to the pivot 106 | ple is the left position to put the next item that equals the pivot 107 | ple is the last right position where we put an item that equals the pivot 108 | 109 | v- end (beyond the array) 110 | EEEEEELLLLLLLLuuuuuuuuGGGGGGGEEEEEEEE. 111 | ^- b ^- ple ^- pl ^- pr ^- pre ^- last (where the pivot is) 112 | 113 | Pivot comparison key: 114 | E = equal, L = less than, u = unknown, G = greater than, E = equal 115 | */ 116 | pivot = last; 117 | ple = pl = b; 118 | pre = pr = last; 119 | 120 | /* 121 | Strategy: 122 | Loop into the list from the left and right at the same time to find: 123 | - an item on the left that is greater than the pivot 124 | - an item on the right that is less than the pivot 125 | Once found, they are swapped and the loop continues. 126 | Meanwhile items that are equal to the pivot are moved to the edges of the 127 | array. 128 | */ 129 | while(pl < pr) { 130 | /* Move left hand items which are equal to the pivot to the far left. 131 | break when we find an item that is greater than the pivot */ 132 | for(; pl < pr; pl += w) { 133 | cmp = compar(pl, pivot, arg); 134 | if(cmp > 0) { break; } 135 | else if(cmp == 0) { 136 | if(ple < pl) { sort_r_swap(ple, pl, w); } 137 | ple += w; 138 | } 139 | } 140 | /* break if last batch of left hand items were equal to pivot */ 141 | if(pl >= pr) { break; } 142 | /* Move right hand items which are equal to the pivot to the far right. 143 | break when we find an item that is less than the pivot */ 144 | for(; pl < pr; ) { 145 | pr -= w; /* Move right pointer onto an unprocessed item */ 146 | cmp = compar(pr, pivot, arg); 147 | if(cmp == 0) { 148 | pre -= w; 149 | if(pr < pre) { sort_r_swap(pr, pre, w); } 150 | } 151 | else if(cmp < 0) { 152 | if(pl < pr) { sort_r_swap(pl, pr, w); } 153 | pl += w; 154 | break; 155 | } 156 | } 157 | } 158 | 159 | pl = pr; /* pr may have gone below pl */ 160 | 161 | /* 162 | Now we need to go from: EEELLLGGGGEEEE 163 | to: LLLEEEEEEEGGGG 164 | 165 | Pivot comparison key: 166 | E = equal, L = less than, u = unknown, G = greater than, E = equal 167 | */ 168 | sort_r_swap_blocks(b, ple-b, pl-ple); 169 | sort_r_swap_blocks(pr, pre-pr, end-pre); 170 | 171 | /*for(size_t i=0; i$@ 6 | -------------------------------------------------------------------------------- /man/index.txt: -------------------------------------------------------------------------------- 1 | ply(1) ply.1.ronn 2 | 3 | awk(1) http://man7.org/linux/man-pages/man1/gawk.1.html 4 | dtrace(1) https://docs.oracle.com/cd/E23823_01/html/816-5166/dtrace-1m.html 5 | bpf(2) http://man7.org/linux/man-pages/man2/bpf.2.html 6 | -------------------------------------------------------------------------------- /scripts/execsnoop.ply: -------------------------------------------------------------------------------- 1 | #!/sbin/ply 2 | 3 | kprobe:SyS_execve { 4 | execs[kpid] = str(arg0, 48); 5 | } 6 | 7 | kretprobe:SyS_execve { 8 | printf("(%4u) %v %3ld\n", uid, execs[kpid], retval); 9 | } 10 | -------------------------------------------------------------------------------- /scripts/net-rx.ply: -------------------------------------------------------------------------------- 1 | #!/sbin/ply 2 | 3 | kprobe:__netif_receive_skb_core 4 | { 5 | rx[arg0] = time; 6 | } 7 | 8 | kprobe:ip_rcv_finish / rx[arg2] / 9 | { 10 | @["rx"] = quantize(time - rx[arg2]); 11 | } 12 | -------------------------------------------------------------------------------- /scripts/opensnoop.ply: -------------------------------------------------------------------------------- 1 | #!/sbin/ply 2 | 3 | kprobe:do_sys_open 4 | { 5 | path[kpid] = str(arg1); 6 | } 7 | 8 | kretprobe:do_sys_open 9 | { 10 | printf("%v %v %v :%d\n", pid, comm, path[kpid], retval); 11 | } 12 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = subdir-objects 2 | 3 | SUBDIRS = libply ply 4 | -------------------------------------------------------------------------------- /src/libply/.gitignore: -------------------------------------------------------------------------------- 1 | grammar.[ch] 2 | lexer.[ch] 3 | -------------------------------------------------------------------------------- /src/libply/Makefile.am: -------------------------------------------------------------------------------- 1 | lib_LTLIBRARIES = libply.la 2 | 3 | libply_la_CPPFLAGS = -include $(top_builddir)/config.h -I $(top_srcdir)/include 4 | libply_la_CFLAGS = -Wall -Wextra -Wno-unused -Wno-unused-result 5 | libply_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:0:0 6 | 7 | AM_YFLAGS = -d -Wall 8 | AM_LFLAGS = --header-file=lexer.h 9 | 10 | EXTRA_DIST = grammar.c grammar.h lexer.c lexer.h \ 11 | arch/aarch64.c \ 12 | arch/arm.c \ 13 | arch/loongarch64.c \ 14 | arch/mips.c \ 15 | arch/powerpc.c \ 16 | arch/riscv32.c \ 17 | arch/riscv64.c \ 18 | arch/x86_64.c 19 | 20 | BUILT_SOURCES = grammar.h lexer.h 21 | 22 | libply_la_SOURCES = \ 23 | arch/@arch@.c \ 24 | \ 25 | aux/kallsyms.c \ 26 | aux/perf_event.c \ 27 | aux/printxf.c \ 28 | aux/syscall.c \ 29 | aux/utils.c \ 30 | \ 31 | built-in/aggregation.c \ 32 | built-in/built-in.c \ 33 | built-in/built-in.h \ 34 | built-in/buffer.c \ 35 | built-in/flow.c \ 36 | built-in/math.c \ 37 | built-in/memory.c \ 38 | built-in/print.c \ 39 | built-in/proc.c \ 40 | \ 41 | provider/interval.c \ 42 | provider/kprobe.c \ 43 | provider/kprobe.h \ 44 | provider/kretprobe.c \ 45 | provider/profile.c \ 46 | provider/special.c \ 47 | provider/tracepoint.c \ 48 | provider/xprobe.c \ 49 | provider/xprobe.h \ 50 | \ 51 | compile.c \ 52 | func.c \ 53 | grammar.y \ 54 | ir.c \ 55 | lexer.l \ 56 | node.c \ 57 | provider.c \ 58 | sym.c \ 59 | type.c \ 60 | \ 61 | libply.c 62 | 63 | libply_la_LIBADD = $(LTLIBOBJS) 64 | 65 | lexer.h: lexer.c 66 | -------------------------------------------------------------------------------- /src/libply/arch/aarch64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_slong); 23 | struct type t_u64 = arch_typedef(u64, &t_ulong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "x0", .type = &t_reg_t }, 42 | { .name = "x1", .type = &t_reg_t }, 43 | { .name = "x2", .type = &t_reg_t }, 44 | { .name = "x3", .type = &t_reg_t }, 45 | { .name = "x4", .type = &t_reg_t }, 46 | { .name = "x5", .type = &t_reg_t }, 47 | { .name = "x6", .type = &t_reg_t }, 48 | { .name = "x7", .type = &t_reg_t }, 49 | { .name = "x8", .type = &t_reg_t }, 50 | { .name = "x9", .type = &t_reg_t }, 51 | { .name = "x10", .type = &t_reg_t }, 52 | { .name = "x11", .type = &t_reg_t }, 53 | { .name = "x12", .type = &t_reg_t }, 54 | { .name = "x13", .type = &t_reg_t }, 55 | { .name = "x14", .type = &t_reg_t }, 56 | { .name = "x15", .type = &t_reg_t }, 57 | { .name = "x16", .type = &t_reg_t }, 58 | { .name = "x17", .type = &t_reg_t }, 59 | { .name = "x18", .type = &t_reg_t }, 60 | { .name = "x19", .type = &t_reg_t }, 61 | { .name = "x20", .type = &t_reg_t }, 62 | { .name = "x12", .type = &t_reg_t }, 63 | { .name = "x22", .type = &t_reg_t }, 64 | { .name = "x23", .type = &t_reg_t }, 65 | { .name = "x24", .type = &t_reg_t }, 66 | { .name = "x25", .type = &t_reg_t }, 67 | { .name = "x26", .type = &t_reg_t }, 68 | { .name = "x27", .type = &t_reg_t }, 69 | { .name = "x28", .type = &t_reg_t }, 70 | { .name = "x29", .type = &t_reg_t }, 71 | { .name = "x30", .type = &t_reg_t }, 72 | { .name = "sp", .type = &t_reg_t }, 73 | { .name = "pc", .type = &t_reg_t }, 74 | { .name = "pstate", .type = &t_reg_t }, 75 | 76 | { .type = NULL } 77 | }; 78 | 79 | struct type t_pt_regs = { 80 | .ttype = T_STRUCT, 81 | 82 | .sou = { 83 | .name = "pt_regs", 84 | .fields = f_pt_regs_fields, 85 | }, 86 | }; 87 | 88 | struct type *arch_types[] = { 89 | &t_s8, &t_u8, 90 | &t_s16, &t_u16, 91 | &t_s32, &t_u32, 92 | &t_s64, &t_u64, 93 | &t_reg_t, &t_pt_regs, 94 | 95 | NULL 96 | }; 97 | 98 | const char *arch_register_argument(int num) 99 | { 100 | switch (num) { 101 | case 0: return "x0"; 102 | case 1: return "x1"; 103 | case 2: return "x2"; 104 | case 3: return "x3"; 105 | case 4: return "x4"; 106 | case 5: return "x5"; 107 | case 6: return "x6"; 108 | case 7: return "x7"; 109 | } 110 | 111 | return NULL; 112 | } 113 | 114 | const char *arch_register_pc(void) 115 | { 116 | return "pc"; 117 | } 118 | 119 | const char *arch_register_return(void) 120 | { 121 | return "x0"; 122 | } 123 | 124 | __attribute__((constructor)) 125 | static void arch_init(void) 126 | { 127 | type_struct_layout(&t_pt_regs); 128 | type_add_list(arch_types); 129 | } 130 | -------------------------------------------------------------------------------- /src/libply/arch/arm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_sllong); 23 | struct type t_u64 = arch_typedef(u64, &t_ullong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "r0", .type = &t_reg_t }, 42 | { .name = "r1", .type = &t_reg_t }, 43 | { .name = "r2", .type = &t_reg_t }, 44 | { .name = "r3", .type = &t_reg_t }, 45 | { .name = "r4", .type = &t_reg_t }, 46 | { .name = "r5", .type = &t_reg_t }, 47 | { .name = "r6", .type = &t_reg_t }, 48 | { .name = "r7", .type = &t_reg_t }, 49 | { .name = "r8", .type = &t_reg_t }, 50 | { .name = "r9", .type = &t_reg_t }, 51 | { .name = "r10", .type = &t_reg_t }, 52 | { .name = "fp", .type = &t_reg_t }, 53 | { .name = "ip", .type = &t_reg_t }, 54 | { .name = "sp", .type = &t_reg_t }, 55 | { .name = "lr", .type = &t_reg_t }, 56 | { .name = "pc", .type = &t_reg_t }, 57 | { .name = "cpsr", .type = &t_reg_t }, 58 | { .name = "orig_r0", .type = &t_reg_t }, 59 | 60 | { .type = NULL } 61 | }; 62 | 63 | struct type t_pt_regs = { 64 | .ttype = T_STRUCT, 65 | 66 | .sou = { 67 | .name = "pt_regs", 68 | .fields = f_pt_regs_fields, 69 | }, 70 | }; 71 | 72 | struct type *arch_types[] = { 73 | &t_s8, &t_u8, 74 | &t_s16, &t_u16, 75 | &t_s32, &t_u32, 76 | &t_s64, &t_u64, 77 | &t_reg_t, &t_pt_regs, 78 | 79 | NULL 80 | }; 81 | 82 | const char *arch_register_argument(int num) 83 | { 84 | switch (num) { 85 | case 0: return "r0"; 86 | case 1: return "r1"; 87 | case 2: return "r2"; 88 | case 3: return "r3"; 89 | case 4: return "r4"; 90 | case 5: return "r5"; 91 | case 6: return "r6"; 92 | } 93 | 94 | return NULL; 95 | } 96 | 97 | const char *arch_register_pc(void) 98 | { 99 | return "pc"; 100 | } 101 | 102 | const char *arch_register_return(void) 103 | { 104 | return "r0"; 105 | } 106 | 107 | __attribute__((constructor)) 108 | static void arch_init(void) 109 | { 110 | type_struct_layout(&t_pt_regs); 111 | type_add_list(arch_types); 112 | } 113 | -------------------------------------------------------------------------------- /src/libply/arch/loongarch64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_slong); 23 | struct type t_u64 = arch_typedef(u64, &t_ulong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "pc", .type = &t_reg_t }, 42 | { .name = "ra", .type = &t_reg_t }, 43 | { .name = "tp", .type = &t_reg_t }, 44 | { .name = "sp", .type = &t_reg_t }, 45 | { .name = "a0", .type = &t_reg_t }, 46 | { .name = "a1", .type = &t_reg_t }, 47 | { .name = "a2", .type = &t_reg_t }, 48 | { .name = "a3", .type = &t_reg_t }, 49 | { .name = "a4", .type = &t_reg_t }, 50 | { .name = "a5", .type = &t_reg_t }, 51 | { .name = "a6", .type = &t_reg_t }, 52 | { .name = "a7", .type = &t_reg_t }, 53 | { .name = "t0", .type = &t_reg_t }, 54 | { .name = "t1", .type = &t_reg_t }, 55 | { .name = "t2", .type = &t_reg_t }, 56 | { .name = "t3", .type = &t_reg_t }, 57 | { .name = "t4", .type = &t_reg_t }, 58 | { .name = "t5", .type = &t_reg_t }, 59 | { .name = "t6", .type = &t_reg_t }, 60 | { .name = "t7", .type = &t_reg_t }, 61 | { .name = "t8", .type = &t_reg_t }, 62 | { .name = "r21", .type = &t_reg_t }, 63 | { .name = "fp", .type = &t_reg_t }, 64 | { .name = "s0", .type = &t_reg_t }, 65 | { .name = "s1", .type = &t_reg_t }, 66 | { .name = "s2", .type = &t_reg_t }, 67 | { .name = "s3", .type = &t_reg_t }, 68 | { .name = "s4", .type = &t_reg_t }, 69 | { .name = "s5", .type = &t_reg_t }, 70 | { .name = "s6", .type = &t_reg_t }, 71 | { .name = "s7", .type = &t_reg_t }, 72 | { .name = "s8", .type = &t_reg_t }, 73 | 74 | { .type = NULL } 75 | }; 76 | 77 | struct type t_pt_regs = { 78 | .ttype = T_STRUCT, 79 | 80 | .sou = { 81 | .name = "pt_regs", 82 | .fields = f_pt_regs_fields, 83 | }, 84 | }; 85 | 86 | struct type *arch_types[] = { 87 | &t_s8, &t_u8, 88 | &t_s16, &t_u16, 89 | &t_s32, &t_u32, 90 | &t_s64, &t_u64, 91 | &t_reg_t, &t_pt_regs, 92 | 93 | NULL 94 | }; 95 | 96 | const char *arch_register_argument(int num) 97 | { 98 | switch (num) { 99 | case 0: return "a0"; 100 | case 1: return "a1"; 101 | case 2: return "a2"; 102 | case 3: return "a3"; 103 | case 4: return "a4"; 104 | case 5: return "a5"; 105 | case 6: return "a6"; 106 | } 107 | 108 | return NULL; 109 | } 110 | 111 | const char *arch_register_pc(void) 112 | { 113 | return "pc"; 114 | } 115 | 116 | const char *arch_register_return(void) 117 | { 118 | return "a0"; 119 | } 120 | 121 | __attribute__((constructor)) 122 | static void arch_init(void) 123 | { 124 | type_struct_layout(&t_pt_regs); 125 | type_add_list(arch_types); 126 | } 127 | -------------------------------------------------------------------------------- /src/libply/arch/mips.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * Copyright Ye Jiaqiang 4 | * 5 | * SPDX-License-Identifier: GPL-2.0 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | 12 | #define arch_typedef(_a, _t) { \ 13 | .ttype = T_TYPEDEF, \ 14 | .tdef = { .name = #_a, .type = _t }, \ 15 | } 16 | 17 | struct type t_s8 = arch_typedef(s8, &t_schar); 18 | struct type t_u8 = arch_typedef(u8, &t_uchar); 19 | struct type t_s16 = arch_typedef(s16, &t_sshort); 20 | struct type t_u16 = arch_typedef(u16, &t_ushort); 21 | struct type t_s32 = arch_typedef(s32, &t_sint); 22 | struct type t_u32 = arch_typedef(u32, &t_uint); 23 | #ifdef __mips64 24 | struct type t_s64 = arch_typedef(s64, &t_slong); 25 | struct type t_u64 = arch_typedef(u64, &t_ulong); 26 | #else 27 | struct type t_s64 = arch_typedef(s64, &t_sllong); 28 | struct type t_u64 = arch_typedef(u64, &t_ullong); 29 | #endif 30 | 31 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 32 | { 33 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 34 | } 35 | 36 | struct type t_reg_t = { 37 | .ttype = T_TYPEDEF, 38 | .tdef = { 39 | .name = "reg_t", 40 | .type = &t_ulong, 41 | }, 42 | 43 | .fprint = reg_fprint, 44 | }; 45 | 46 | struct tfield f_pt_regs_fields[] = { 47 | #ifndef __mips64 48 | { .name = "uarg0", .type = &t_reg_t }, 49 | { .name = "uarg1", .type = &t_reg_t }, 50 | { .name = "uarg2", .type = &t_reg_t }, 51 | { .name = "uarg3", .type = &t_reg_t }, 52 | { .name = "uarg4", .type = &t_reg_t }, 53 | { .name = "uarg5", .type = &t_reg_t }, 54 | { .name = "uarg6", .type = &t_reg_t }, 55 | { .name = "uarg7", .type = &t_reg_t }, 56 | #endif 57 | { .name = "zero", .type = &t_reg_t }, /* $0 */ 58 | { .name = "at", .type = &t_reg_t }, /* $1 */ 59 | { .name = "v0", .type = &t_reg_t }, /* $2 */ 60 | { .name = "v1", .type = &t_reg_t }, /* $3 */ 61 | { .name = "a0", .type = &t_reg_t }, /* $4 */ 62 | { .name = "a1", .type = &t_reg_t }, /* $5 */ 63 | { .name = "a2", .type = &t_reg_t }, /* $6 */ 64 | { .name = "a3", .type = &t_reg_t }, /* $7 */ 65 | #ifdef _ABIO32 66 | { .name = "t0", .type = &t_reg_t }, /* $8 */ 67 | { .name = "t1", .type = &t_reg_t }, /* $9 */ 68 | { .name = "t2", .type = &t_reg_t }, /* $10 */ 69 | { .name = "t3", .type = &t_reg_t }, /* $11 */ 70 | #else /* n32 or native 64-bit ABI */ 71 | { .name = "a4", .type = &t_reg_t }, /* $8 */ 72 | { .name = "a5", .type = &t_reg_t }, /* $9 */ 73 | { .name = "a6", .type = &t_reg_t }, /* $10 */ 74 | { .name = "a7", .type = &t_reg_t }, /* $11 */ 75 | #endif 76 | { .name = "t4", .type = &t_reg_t }, /* $12 */ 77 | { .name = "t5", .type = &t_reg_t }, /* $13 */ 78 | { .name = "t6", .type = &t_reg_t }, /* $14 */ 79 | { .name = "t7", .type = &t_reg_t }, /* $15 */ 80 | { .name = "s0", .type = &t_reg_t }, /* $16 */ 81 | { .name = "s1", .type = &t_reg_t }, /* $17 */ 82 | { .name = "s2", .type = &t_reg_t }, /* $18 */ 83 | { .name = "s3", .type = &t_reg_t }, /* $19 */ 84 | { .name = "s4", .type = &t_reg_t }, /* $20 */ 85 | { .name = "s5", .type = &t_reg_t }, /* $21 */ 86 | { .name = "s6", .type = &t_reg_t }, /* $22 */ 87 | { .name = "s7", .type = &t_reg_t }, /* $23 */ 88 | { .name = "t8", .type = &t_reg_t }, /* $24 */ 89 | { .name = "t9", .type = &t_reg_t }, /* $25 */ 90 | { .name = "k0", .type = &t_reg_t }, /* $26 */ 91 | { .name = "k1", .type = &t_reg_t }, /* $27 */ 92 | { .name = "gp", .type = &t_reg_t }, /* $28 */ 93 | { .name = "sp", .type = &t_reg_t }, /* $29 */ 94 | { .name = "fp", .type = &t_reg_t }, /* $30, or s8 */ 95 | { .name = "ra", .type = &t_reg_t }, /* $31 */ 96 | 97 | { .name = "cp0_status", .type = &t_reg_t }, 98 | { .name = "hi", .type = &t_reg_t }, 99 | { .name = "lo", .type = &t_reg_t }, 100 | 101 | #ifdef __mips_smartmips 102 | { .name = "acx", .type = &t_reg_t }, 103 | #endif 104 | { .name = "cp0_badvaddr", .type = &t_reg_t }, 105 | { .name = "cp0_cause", .type = &t_reg_t }, 106 | { .name = "cp0_epc", .type = &t_reg_t }, 107 | 108 | #ifdef __OCTEON__ 109 | { .name = "mpl0", .type = &t_reg_t }, 110 | { .name = "mpl1", .type = &t_reg_t }, 111 | { .name = "mpl2", .type = &t_reg_t }, 112 | { .name = "mpl3", .type = &t_reg_t }, 113 | { .name = "mpl4", .type = &t_reg_t }, 114 | { .name = "mpl5", .type = &t_reg_t }, 115 | { .name = "mtp0", .type = &t_reg_t }, 116 | { .name = "mtp1", .type = &t_reg_t }, 117 | { .name = "mtp2", .type = &t_reg_t }, 118 | { .name = "mtp3", .type = &t_reg_t }, 119 | { .name = "mtp4", .type = &t_reg_t }, 120 | { .name = "mtp5", .type = &t_reg_t }, 121 | #endif 122 | { .name = NULL, .type = NULL } 123 | }; 124 | 125 | struct type t_pt_regs = { 126 | .ttype = T_STRUCT, 127 | 128 | .sou = { 129 | .name = "pt_regs", 130 | .fields = f_pt_regs_fields, 131 | }, 132 | }; 133 | 134 | struct type *arch_types[] = { 135 | &t_s8, &t_u8, 136 | &t_s16, &t_u16, 137 | &t_s32, &t_u32, 138 | &t_s64, &t_u64, 139 | &t_reg_t, &t_pt_regs, 140 | NULL 141 | }; 142 | 143 | const char *arch_register_argument(int num) 144 | { 145 | switch (num) { 146 | case 0: return "a0"; 147 | case 1: return "a1"; 148 | case 2: return "a2"; 149 | case 3: return "a3"; 150 | #ifndef _ABIO32 /* n32 or native 64-bit ABI */ 151 | case 4: return "a4"; 152 | case 5: return "a5"; 153 | case 6: return "a6"; 154 | case 7: return "a7"; 155 | #endif 156 | default: 157 | break; 158 | } 159 | 160 | return NULL; 161 | } 162 | 163 | const char *arch_register_pc(void) 164 | { 165 | return "cp0_epc"; 166 | } 167 | 168 | const char *arch_register_return(void) 169 | { 170 | return "v0"; 171 | } 172 | 173 | __attribute__((constructor)) 174 | static void arch_init(void) 175 | { 176 | type_struct_layout(&t_pt_regs); 177 | type_add_list(arch_types); 178 | } 179 | -------------------------------------------------------------------------------- /src/libply/arch/powerpc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_sllong); 23 | struct type t_u64 = arch_typedef(u64, &t_ullong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "gpr0", .type = &t_reg_t }, 42 | { .name = "gpr1", .type = &t_reg_t }, 43 | { .name = "gpr2", .type = &t_reg_t }, 44 | { .name = "gpr3", .type = &t_reg_t }, 45 | { .name = "gpr4", .type = &t_reg_t }, 46 | { .name = "gpr5", .type = &t_reg_t }, 47 | { .name = "gpr6", .type = &t_reg_t }, 48 | { .name = "gpr7", .type = &t_reg_t }, 49 | { .name = "gpr8", .type = &t_reg_t }, 50 | { .name = "gpr9", .type = &t_reg_t }, 51 | { .name = "gpr10", .type = &t_reg_t }, 52 | { .name = "gpr11", .type = &t_reg_t }, 53 | { .name = "gpr12", .type = &t_reg_t }, 54 | { .name = "gpr13", .type = &t_reg_t }, 55 | { .name = "gpr14", .type = &t_reg_t }, 56 | { .name = "gpr15", .type = &t_reg_t }, 57 | { .name = "gpr16", .type = &t_reg_t }, 58 | { .name = "gpr17", .type = &t_reg_t }, 59 | { .name = "gpr18", .type = &t_reg_t }, 60 | { .name = "gpr19", .type = &t_reg_t }, 61 | { .name = "gpr20", .type = &t_reg_t }, 62 | { .name = "gpr12", .type = &t_reg_t }, 63 | { .name = "gpr22", .type = &t_reg_t }, 64 | { .name = "gpr23", .type = &t_reg_t }, 65 | { .name = "gpr24", .type = &t_reg_t }, 66 | { .name = "gpr25", .type = &t_reg_t }, 67 | { .name = "gpr26", .type = &t_reg_t }, 68 | { .name = "gpr27", .type = &t_reg_t }, 69 | { .name = "gpr28", .type = &t_reg_t }, 70 | { .name = "gpr29", .type = &t_reg_t }, 71 | { .name = "gpr30", .type = &t_reg_t }, 72 | { .name = "gpr31", .type = &t_reg_t }, 73 | { .name = "nip", .type = &t_reg_t }, 74 | { .name = "msr", .type = &t_reg_t }, 75 | { .name = "orig_gpr3", .type = &t_reg_t }, 76 | { .name = "ctr", .type = &t_reg_t }, 77 | { .name = "link", .type = &t_reg_t }, 78 | { .name = "xer", .type = &t_reg_t }, 79 | { .name = "ccr", .type = &t_reg_t }, 80 | { .name = "mq", .type = &t_reg_t }, 81 | { .name = "trap", .type = &t_reg_t }, 82 | { .name = "dar", .type = &t_reg_t }, 83 | { .name = "dsisr", .type = &t_reg_t }, 84 | { .name = "result", .type = &t_reg_t }, 85 | 86 | { .type = NULL } 87 | }; 88 | 89 | struct type t_pt_regs = { 90 | .ttype = T_STRUCT, 91 | 92 | .sou = { 93 | .name = "pt_regs", 94 | .fields = f_pt_regs_fields, 95 | }, 96 | }; 97 | 98 | struct type *arch_types[] = { 99 | &t_s8, &t_u8, 100 | &t_s16, &t_u16, 101 | &t_s32, &t_u32, 102 | &t_s64, &t_u64, 103 | &t_reg_t, &t_pt_regs, 104 | 105 | NULL 106 | }; 107 | 108 | const char *arch_register_argument(int num) 109 | { 110 | switch (num) { 111 | case 0: return "gpr3"; 112 | case 1: return "gpr4"; 113 | case 2: return "gpr5"; 114 | case 3: return "gpr6"; 115 | case 4: return "gpr7"; 116 | case 5: return "gpr8"; 117 | case 6: return "gpr9"; 118 | } 119 | 120 | return NULL; 121 | } 122 | 123 | const char *arch_register_pc(void) 124 | { 125 | return "nip"; 126 | } 127 | 128 | const char *arch_register_return(void) 129 | { 130 | return "gpr3"; 131 | } 132 | 133 | __attribute__((constructor)) 134 | static void arch_init(void) 135 | { 136 | type_struct_layout(&t_pt_regs); 137 | type_add_list(arch_types); 138 | } 139 | -------------------------------------------------------------------------------- /src/libply/arch/riscv32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_sllong); 23 | struct type t_u64 = arch_typedef(u64, &t_ullong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "pc", .type = &t_reg_t }, 42 | { .name = "ra", .type = &t_reg_t }, 43 | { .name = "sp", .type = &t_reg_t }, 44 | { .name = "gp", .type = &t_reg_t }, 45 | { .name = "tp", .type = &t_reg_t }, 46 | { .name = "t0", .type = &t_reg_t }, 47 | { .name = "t1", .type = &t_reg_t }, 48 | { .name = "t2", .type = &t_reg_t }, 49 | { .name = "s0", .type = &t_reg_t }, 50 | { .name = "s1", .type = &t_reg_t }, 51 | { .name = "a0", .type = &t_reg_t }, 52 | { .name = "a1", .type = &t_reg_t }, 53 | { .name = "a2", .type = &t_reg_t }, 54 | { .name = "a3", .type = &t_reg_t }, 55 | { .name = "a4", .type = &t_reg_t }, 56 | { .name = "a5", .type = &t_reg_t }, 57 | { .name = "a6", .type = &t_reg_t }, 58 | { .name = "a7", .type = &t_reg_t }, 59 | { .name = "s2", .type = &t_reg_t }, 60 | { .name = "s3", .type = &t_reg_t }, 61 | { .name = "s4", .type = &t_reg_t }, 62 | { .name = "s5", .type = &t_reg_t }, 63 | { .name = "s6", .type = &t_reg_t }, 64 | { .name = "s7", .type = &t_reg_t }, 65 | { .name = "s8", .type = &t_reg_t }, 66 | { .name = "s9", .type = &t_reg_t }, 67 | { .name = "s10", .type = &t_reg_t }, 68 | { .name = "s11", .type = &t_reg_t }, 69 | { .name = "t3", .type = &t_reg_t }, 70 | { .name = "t4", .type = &t_reg_t }, 71 | { .name = "t5", .type = &t_reg_t }, 72 | { .name = "t6", .type = &t_reg_t }, 73 | 74 | { .type = NULL } 75 | }; 76 | 77 | struct type t_pt_regs = { 78 | .ttype = T_STRUCT, 79 | 80 | .sou = { 81 | .name = "pt_regs", 82 | .fields = f_pt_regs_fields, 83 | }, 84 | }; 85 | 86 | struct type *arch_types[] = { 87 | &t_s8, &t_u8, 88 | &t_s16, &t_u16, 89 | &t_s32, &t_u32, 90 | &t_s64, &t_u64, 91 | &t_reg_t, &t_pt_regs, 92 | 93 | NULL 94 | }; 95 | 96 | const char *arch_register_argument(int num) 97 | { 98 | switch (num) { 99 | case 0: return "a0"; 100 | case 1: return "a1"; 101 | case 2: return "a2"; 102 | case 3: return "a3"; 103 | case 4: return "a4"; 104 | case 5: return "a5"; 105 | case 6: return "a6"; 106 | } 107 | 108 | return NULL; 109 | } 110 | 111 | const char *arch_register_pc(void) 112 | { 113 | return "pc"; 114 | } 115 | 116 | const char *arch_register_return(void) 117 | { 118 | return "a0"; 119 | } 120 | 121 | __attribute__((constructor)) 122 | static void arch_init(void) 123 | { 124 | type_struct_layout(&t_pt_regs); 125 | type_add_list(arch_types); 126 | } 127 | -------------------------------------------------------------------------------- /src/libply/arch/riscv64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_slong); 23 | struct type t_u64 = arch_typedef(u64, &t_ulong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "pc", .type = &t_reg_t }, 42 | { .name = "ra", .type = &t_reg_t }, 43 | { .name = "sp", .type = &t_reg_t }, 44 | { .name = "gp", .type = &t_reg_t }, 45 | { .name = "tp", .type = &t_reg_t }, 46 | { .name = "t0", .type = &t_reg_t }, 47 | { .name = "t1", .type = &t_reg_t }, 48 | { .name = "t2", .type = &t_reg_t }, 49 | { .name = "s0", .type = &t_reg_t }, 50 | { .name = "s1", .type = &t_reg_t }, 51 | { .name = "a0", .type = &t_reg_t }, 52 | { .name = "a1", .type = &t_reg_t }, 53 | { .name = "a2", .type = &t_reg_t }, 54 | { .name = "a3", .type = &t_reg_t }, 55 | { .name = "a4", .type = &t_reg_t }, 56 | { .name = "a5", .type = &t_reg_t }, 57 | { .name = "a6", .type = &t_reg_t }, 58 | { .name = "a7", .type = &t_reg_t }, 59 | { .name = "s2", .type = &t_reg_t }, 60 | { .name = "s3", .type = &t_reg_t }, 61 | { .name = "s4", .type = &t_reg_t }, 62 | { .name = "s5", .type = &t_reg_t }, 63 | { .name = "s6", .type = &t_reg_t }, 64 | { .name = "s7", .type = &t_reg_t }, 65 | { .name = "s8", .type = &t_reg_t }, 66 | { .name = "s9", .type = &t_reg_t }, 67 | { .name = "s10", .type = &t_reg_t }, 68 | { .name = "s11", .type = &t_reg_t }, 69 | { .name = "t3", .type = &t_reg_t }, 70 | { .name = "t4", .type = &t_reg_t }, 71 | { .name = "t5", .type = &t_reg_t }, 72 | { .name = "t6", .type = &t_reg_t }, 73 | 74 | { .type = NULL } 75 | }; 76 | 77 | struct type t_pt_regs = { 78 | .ttype = T_STRUCT, 79 | 80 | .sou = { 81 | .name = "pt_regs", 82 | .fields = f_pt_regs_fields, 83 | }, 84 | }; 85 | 86 | struct type *arch_types[] = { 87 | &t_s8, &t_u8, 88 | &t_s16, &t_u16, 89 | &t_s32, &t_u32, 90 | &t_s64, &t_u64, 91 | &t_reg_t, &t_pt_regs, 92 | 93 | NULL 94 | }; 95 | 96 | const char *arch_register_argument(int num) 97 | { 98 | switch (num) { 99 | case 0: return "a0"; 100 | case 1: return "a1"; 101 | case 2: return "a2"; 102 | case 3: return "a3"; 103 | case 4: return "a4"; 104 | case 5: return "a5"; 105 | case 6: return "a6"; 106 | } 107 | 108 | return NULL; 109 | } 110 | 111 | const char *arch_register_pc(void) 112 | { 113 | return "pc"; 114 | } 115 | 116 | const char *arch_register_return(void) 117 | { 118 | return "a0"; 119 | } 120 | 121 | __attribute__((constructor)) 122 | static void arch_init(void) 123 | { 124 | type_struct_layout(&t_pt_regs); 125 | type_add_list(arch_types); 126 | } 127 | -------------------------------------------------------------------------------- /src/libply/arch/x86_64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #define arch_typedef(_a, _t) { \ 12 | .ttype = T_TYPEDEF, \ 13 | .tdef = { .name = #_a, .type = _t }, \ 14 | } 15 | 16 | struct type t_s8 = arch_typedef(s8, &t_schar); 17 | struct type t_u8 = arch_typedef(u8, &t_uchar); 18 | struct type t_s16 = arch_typedef(s16, &t_sshort); 19 | struct type t_u16 = arch_typedef(u16, &t_ushort); 20 | struct type t_s32 = arch_typedef(s32, &t_sint); 21 | struct type t_u32 = arch_typedef(u32, &t_uint); 22 | struct type t_s64 = arch_typedef(s64, &t_slong); 23 | struct type t_u64 = arch_typedef(u64, &t_ulong); 24 | 25 | static int reg_fprint(struct type *t, FILE *fp, const void *data) 26 | { 27 | return fprintf(fp, "%#lx", *((unsigned long *)data)); 28 | } 29 | 30 | struct type t_reg_t = { 31 | .ttype = T_TYPEDEF, 32 | .tdef = { 33 | .name = "reg_t", 34 | .type = &t_ulong, 35 | }, 36 | 37 | .fprint = reg_fprint, 38 | }; 39 | 40 | struct tfield f_pt_regs_fields[] = { 41 | { .name = "r15", .type = &t_reg_t }, 42 | { .name = "r14", .type = &t_reg_t }, 43 | { .name = "r13", .type = &t_reg_t }, 44 | { .name = "r12", .type = &t_reg_t }, 45 | { .name = "rbp", .type = &t_reg_t }, 46 | { .name = "rbx", .type = &t_reg_t }, 47 | { .name = "r11", .type = &t_reg_t }, 48 | { .name = "r10", .type = &t_reg_t }, 49 | { .name = "r9", .type = &t_reg_t }, 50 | { .name = "r8", .type = &t_reg_t }, 51 | { .name = "rax", .type = &t_reg_t }, 52 | { .name = "rcx", .type = &t_reg_t }, 53 | { .name = "rdx", .type = &t_reg_t }, 54 | { .name = "rsi", .type = &t_reg_t }, 55 | { .name = "rdi", .type = &t_reg_t }, 56 | { .name = "orig_rax", .type = &t_reg_t }, 57 | { .name = "rip", .type = &t_reg_t }, 58 | { .name = "cs", .type = &t_reg_t }, 59 | { .name = "eflags", .type = &t_reg_t }, 60 | { .name = "rsp", .type = &t_reg_t }, 61 | { .name = "ss", .type = &t_reg_t }, 62 | 63 | { .type = NULL } 64 | }; 65 | 66 | struct type t_pt_regs = { 67 | .ttype = T_STRUCT, 68 | 69 | .sou = { 70 | .name = "pt_regs", 71 | .fields = f_pt_regs_fields, 72 | }, 73 | }; 74 | 75 | struct type *arch_types[] = { 76 | &t_s8, &t_u8, 77 | &t_s16, &t_u16, 78 | &t_s32, &t_u32, 79 | &t_s64, &t_u64, 80 | &t_reg_t, &t_pt_regs, 81 | 82 | NULL 83 | }; 84 | 85 | const char *arch_register_argument(int num) 86 | { 87 | switch (num) { 88 | case 0: return "rdi"; 89 | case 1: return "rsi"; 90 | case 2: return "rdx"; 91 | case 3: return "r10"; 92 | case 4: return "r8"; 93 | case 5: return "r9"; 94 | } 95 | 96 | return NULL; 97 | } 98 | 99 | const char *arch_register_pc(void) 100 | { 101 | return "rip"; 102 | } 103 | 104 | const char *arch_register_return(void) 105 | { 106 | return "rax"; 107 | } 108 | 109 | __attribute__((constructor)) 110 | static void arch_init(void) 111 | { 112 | type_struct_layout(&t_pt_regs); 113 | type_add_list(arch_types); 114 | } 115 | -------------------------------------------------------------------------------- /src/libply/aux/kallsyms.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | /* Manages a cache of the contents in /proc/kallsyms, but in a binary 8 | * format with each symbol using a fixed amount of space. This let's 9 | * us lookup addresses using bsearch(3), making stacktrace resolution 10 | * much faster. 11 | * 12 | * The first and last symbols are the special `nullsym` and `endsym` 13 | * symbols defined below. These are always present but never included 14 | * in the range given to bsearch(3) thus making it safe to always look 15 | * at the previous and next record. 16 | */ 17 | 18 | #define _XOPEN_SOURCE /* strptime */ 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #define KALLSYMS "/proc/kallsyms" 42 | #define KSYMS_CACHE "/var/tmp/ply-ksyms" 43 | #define KFUNC_LIST TRACEPATH "available_filter_functions" 44 | 45 | struct kfunc_list { 46 | int size; 47 | char **kfunc; 48 | }; 49 | 50 | static struct ksym nullsym = { 51 | .addr = 0, 52 | .sym = "NULL", 53 | }; 54 | 55 | static struct ksym endsym = { 56 | .addr = UINTPTR_MAX, 57 | .sym = "END", 58 | }; 59 | 60 | static int ksym_cmp(const void *_key, const void *_member) 61 | { 62 | const struct ksym *key = _key, *member = _member; 63 | 64 | if (key->addr < member->addr) 65 | return -1; 66 | else if (key->addr >= (member + 1)->addr) 67 | return 1; 68 | 69 | return 0; 70 | } 71 | 72 | int ksym_fprint(struct ksyms *ks, FILE *fp, uintptr_t addr) 73 | { 74 | const struct ksym *sym; 75 | 76 | if (ks && (sym = ksym_get(ks, addr))) { 77 | if (sym->addr == addr) 78 | return fputs(sym->sym, fp); 79 | else 80 | return fprintf(fp, "%s+%"PRIuPTR, sym->sym, addr - sym->addr); 81 | } else { 82 | int w = (int)(sizeof(addr) * 2); 83 | 84 | return fprintf(fp, "<%*.*"PRIxPTR">", w, w, addr); 85 | } 86 | } 87 | 88 | const struct ksym *ksym_get(struct ksyms *ks, uintptr_t addr) 89 | { 90 | struct ksym key = { .addr = addr }; 91 | 92 | if (!ks) 93 | return NULL; 94 | 95 | return bsearch(&key, ks->cache->sym, 96 | ks->cache->hdr.n_syms - 1, sizeof(key), ksym_cmp); 97 | } 98 | 99 | static int kfunc_list_cmp(const void *_key, const void *_member) 100 | { 101 | const char * const *key = _key, * const *member = _member; 102 | 103 | return strcmp(*key, *member); 104 | } 105 | 106 | static void kfunc_list_sort(struct kfunc_list *kfuncs) 107 | { 108 | if (!kfuncs->kfunc) 109 | return; 110 | 111 | qsort(kfuncs->kfunc, kfuncs->size, sizeof(char *), kfunc_list_cmp); 112 | } 113 | 114 | static int kfunc_list_find(struct kfunc_list *kfuncs, const char *name) 115 | { 116 | if (!kfuncs->kfunc) 117 | return 1; 118 | 119 | return !!bsearch(&name, kfuncs->kfunc, kfuncs->size, sizeof(char *), kfunc_list_cmp); 120 | } 121 | 122 | static int kfunc_list_build(struct kfunc_list *kfuncs) 123 | { 124 | FILE *fp; 125 | char line[0x80]; 126 | char **list = NULL; 127 | int size = 0; 128 | int count = 0; 129 | 130 | fp = fopen(KFUNC_LIST, "r"); 131 | if (!fp) { 132 | _w("cannot open %s: %s\n", KFUNC_LIST, strerror(errno)); 133 | return -1; 134 | } 135 | 136 | while (fgets(line, sizeof(line), fp)) { 137 | char *p; 138 | struct ksym *ksym; 139 | 140 | if (count >= size) { 141 | size = size ? (size * 2) : 1024; 142 | list = realloc(list, size * sizeof(*list)); 143 | assert(list); 144 | } 145 | 146 | p = strtok(line, " \t\n"); 147 | list[count] = strndup(p, sizeof(ksym->sym) - 1); 148 | assert(list[count]); 149 | 150 | count++; 151 | } 152 | kfuncs->size = count; 153 | kfuncs->kfunc = list; 154 | 155 | kfunc_list_sort(kfuncs); 156 | 157 | fclose(fp); 158 | return 0; 159 | } 160 | 161 | static void kfunc_list_free(struct kfunc_list *kfuncs) 162 | { 163 | int i; 164 | 165 | for (i = 0; i < kfuncs->size; i++) 166 | free(kfuncs->kfunc[i]); 167 | free(kfuncs->kfunc); 168 | 169 | kfuncs->size = 0; 170 | kfuncs->kfunc = NULL; 171 | } 172 | 173 | static int ksym_write(FILE *fp, struct ksym *ksym) 174 | { 175 | return fwrite(ksym, sizeof(*ksym), 1, fp) ? 0 : -EIO; 176 | } 177 | 178 | static int ksym_parse(FILE *fp, struct ksym *ksym) 179 | { 180 | char line[0x80]; 181 | char *p; 182 | 183 | while (fgets(line, sizeof(line), fp)) { 184 | ksym->addr = strtoul(line, &p, 16); 185 | if (ksym->addr == ULONG_MAX) 186 | continue; 187 | 188 | p++; 189 | if (*p != 't' && *p != 'T') 190 | continue; 191 | 192 | p += 2; 193 | p = strtok(p, " \t\n"); 194 | if (!p) 195 | continue; 196 | 197 | strncpy(ksym->sym, p, sizeof(ksym->sym) - 1); 198 | return 0; 199 | } 200 | 201 | return EOF; 202 | } 203 | 204 | static int __ksyms_cache_open(struct ksyms *ks) 205 | { 206 | struct stat st; 207 | int err, i; 208 | 209 | if (stat(KSYMS_CACHE, &st)) 210 | return -errno; 211 | 212 | ks->cache_fd = open(KSYMS_CACHE, O_RDWR); 213 | if (ks->cache_fd < 0) 214 | return -errno; 215 | 216 | ks->cache = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, 217 | ks->cache_fd, 0); 218 | 219 | return (ks->cache == MAP_FAILED) ? -EIO : 0; 220 | } 221 | 222 | static int ksym_sort_cmp(const void *_a, const void *_b) 223 | { 224 | const struct ksym *a = _a, *b = _b; 225 | 226 | return a->addr - b->addr; 227 | } 228 | 229 | static int ksyms_cache_sort(struct ksyms *ks) 230 | { 231 | int err; 232 | 233 | err = __ksyms_cache_open(ks); 234 | if (err) 235 | return err; 236 | 237 | /* Sort everything between NULL and END */ 238 | qsort(&ks->cache->sym[1], ks->cache->hdr.n_syms - 2, 239 | sizeof(struct ksym), ksym_sort_cmp); 240 | 241 | err = msync(ks->cache, sizeof(ks->cache->hdr) + 242 | ks->cache->hdr.n_syms * sizeof(struct ksym), MS_SYNC); 243 | if (err) 244 | return -errno; 245 | 246 | return 0; 247 | } 248 | 249 | /* Anyone may read /proc/kallsyms, but if the reader does not posses 250 | * the CAP_SYSLOG capability, all symbol addresses are set to zero. So 251 | * we make sure that we have it to avoid creating a useless cache. If 252 | * there is some less hacky way of getting this information without 253 | * depending on libcap, please refactor. */ 254 | static int ksyms_cache_cap(void) 255 | { 256 | FILE *fp; 257 | char line[0x100]; 258 | int err = -EPERM; 259 | uint64_t caps = 0; 260 | 261 | fp = fopen("/proc/self/status", "r"); 262 | if (!fp) 263 | return err; 264 | 265 | while (fgets(line, sizeof(line), fp)) { 266 | if (strstr(line, "CapEff:") != line) 267 | continue; 268 | 269 | caps = strtoull(&line[7], NULL, 16); 270 | err = 0; 271 | break; 272 | } 273 | 274 | fclose(fp); 275 | if (err) 276 | return err; 277 | 278 | return (caps & (1ULL << CAP_SYSLOG)) ? 0 : -EPERM; 279 | } 280 | 281 | static int ksyms_cache_build(struct ksyms *ks) 282 | { 283 | struct ksym_cache_hdr hdr = { 0 }; 284 | struct kfunc_list kfuncs = { 0 }; 285 | struct ksym ksym; 286 | FILE *cfp, *kfp; 287 | int err, i; 288 | 289 | _i("creating kallsyms cache\n"); 290 | 291 | err = ksyms_cache_cap(); 292 | if (err) 293 | goto out; 294 | 295 | kfp = fopen(KALLSYMS, "r"); 296 | if (!kfp) { 297 | err = -errno; 298 | goto out; 299 | } 300 | 301 | /* Ignore failures due to missing filter functions. */ 302 | kfunc_list_build(&kfuncs); 303 | 304 | cfp = fopen(KSYMS_CACHE, "w"); 305 | if (!cfp) { 306 | err = -errno; 307 | goto close_kfp; 308 | } 309 | 310 | if (fseek(cfp, sizeof(hdr), SEEK_CUR)) { 311 | err = -errno; 312 | goto close_cfp; 313 | } 314 | 315 | err = ksym_write(cfp, &nullsym); 316 | if (err) 317 | goto close_cfp; 318 | hdr.n_syms++; 319 | 320 | while (!(err = ksym_parse(kfp, &ksym))) { 321 | if (!kfunc_list_find(&kfuncs, ksym.sym)) 322 | continue; 323 | 324 | err = ksym_write(cfp, &ksym); 325 | if (err) 326 | goto close_cfp; 327 | hdr.n_syms++; 328 | } 329 | 330 | if (err && (err != EOF)) 331 | goto close_cfp; 332 | 333 | err = ksym_write(cfp, &endsym); 334 | if (err) 335 | goto close_cfp; 336 | hdr.n_syms++; 337 | 338 | rewind(cfp); 339 | if (!fwrite(&hdr, sizeof(hdr), 1, cfp)) 340 | err = -EIO; 341 | 342 | close_cfp: 343 | fclose(cfp); 344 | if (err) 345 | unlink(KSYMS_CACHE); 346 | 347 | close_kfp: 348 | kfunc_list_free(&kfuncs); 349 | fclose(kfp); 350 | out: 351 | if (!err) 352 | err = ksyms_cache_sort(ks); 353 | 354 | if (err) 355 | _w("unable to create kallsyms cache: %s\n", strerror(-err)); 356 | 357 | return err; 358 | } 359 | 360 | static int ksyms_cache_open(struct ksyms *ks) 361 | { 362 | struct stat procst, ksymsst; 363 | int err; 364 | 365 | err = __ksyms_cache_open(ks); 366 | if (err) 367 | return ksyms_cache_build(ks); 368 | 369 | if (stat("/proc", &procst) || stat(KSYMS_CACHE, &ksymsst)) 370 | return ksyms_cache_build(ks); 371 | 372 | /* Use ctime of `/proc` as an approximation of system boot 373 | * time and require that our cache is younger than that. */ 374 | if (ksymsst.st_ctime < procst.st_ctime) 375 | return ksyms_cache_build(ks); 376 | 377 | return 0; 378 | } 379 | 380 | void ksyms_free(struct ksyms *ks) 381 | { 382 | size_t size; 383 | 384 | size = sizeof(ks->cache->hdr) + 385 | sizeof(ks->cache->sym[0]) * ks->cache->hdr.n_syms; 386 | 387 | munmap(ks->cache, size); 388 | close(ks->cache_fd); 389 | } 390 | 391 | struct ksyms *ksyms_new(void) 392 | { 393 | struct ksyms *ks; 394 | int err; 395 | 396 | ks = xcalloc(1, sizeof(*ks)); 397 | 398 | err = ksyms_cache_open(ks); 399 | if (err) 400 | goto err; 401 | 402 | return ks; 403 | err: 404 | free(ks); 405 | return NULL; 406 | } 407 | -------------------------------------------------------------------------------- /src/libply/aux/perf_event.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | static int perf_event_id(struct ply_probe *pb, const char *path) 20 | { 21 | FILE *fp; 22 | int id; 23 | 24 | fp = fopenf("r", "%s/id", path); 25 | if (!fp) 26 | goto err; 27 | 28 | if (fscanf(fp, "%d", &id) != 1) 29 | goto err; 30 | 31 | return id; 32 | err: 33 | return -errno; 34 | } 35 | 36 | int perf_event_attach(struct ply_probe *pb, const char *path, 37 | int task_mode) 38 | { 39 | struct perf_event_attr attr = {}; 40 | int fd, id; 41 | int pid = task_mode ? 0 : -1; 42 | int cpu = task_mode ? -1 : 0; 43 | int group_fd = task_mode ? -1 : pb->ply->group_fd; 44 | 45 | id = perf_event_id(pb, path); 46 | if (id < 0) 47 | return id; 48 | 49 | attr.type = PERF_TYPE_TRACEPOINT; 50 | attr.sample_type = PERF_SAMPLE_RAW; 51 | attr.sample_period = 1; 52 | attr.wakeup_events = 1; 53 | attr.config = id; 54 | attr.disabled = 1; 55 | 56 | fd = perf_event_open(&attr, pid, cpu, group_fd, 0); 57 | if (fd < 0) 58 | return -errno; 59 | 60 | if (ioctl(fd, PERF_EVENT_IOC_SET_BPF, pb->bpf_fd)) { 61 | close(fd); 62 | return -errno; 63 | } 64 | 65 | if (pb->ply->group_fd == -1 && !task_mode) 66 | pb->ply->group_fd = fd; 67 | 68 | return fd; 69 | } 70 | 71 | int perf_event_attach_raw(struct ply_probe *pb, int type, unsigned long config, 72 | unsigned long long period, int task_mode) 73 | { 74 | struct perf_event_attr attr = {}; 75 | int fd; 76 | int pid = task_mode ? 0 : -1; 77 | int cpu = task_mode ? -1 : 0; 78 | int group_fd = task_mode ? -1 : pb->ply->group_fd; 79 | 80 | attr.type = type; 81 | attr.config = config; 82 | attr.sample_type = PERF_SAMPLE_RAW; 83 | attr.sample_period = period; 84 | attr.wakeup_events = 1; 85 | attr.disabled = 1; 86 | 87 | fd = perf_event_open(&attr, pid, cpu, group_fd, 0); 88 | if (fd < 0) 89 | return -errno; 90 | 91 | if (ioctl(fd, PERF_EVENT_IOC_SET_BPF, pb->bpf_fd)) { 92 | close(fd); 93 | return -errno; 94 | } 95 | 96 | if (pb->ply->group_fd == -1 && !task_mode) 97 | pb->ply->group_fd = fd; 98 | 99 | return fd; 100 | } 101 | 102 | int perf_event_attach_profile(struct ply_probe *pb, int cpu, 103 | unsigned long long freq) 104 | { 105 | struct perf_event_attr attr = {}; 106 | int fd; 107 | 108 | attr.type = PERF_TYPE_SOFTWARE; 109 | attr.config = PERF_COUNT_SW_CPU_CLOCK; 110 | attr.sample_freq = freq; 111 | attr.freq = 1; 112 | 113 | fd = perf_event_open(&attr, -1, cpu, -1, 0); 114 | if (fd < 0) 115 | return -errno; 116 | 117 | if (ioctl(fd, PERF_EVENT_IOC_SET_BPF, pb->bpf_fd)) { 118 | close(fd); 119 | return -errno; 120 | } 121 | 122 | return fd; 123 | } 124 | 125 | int perf_event_enable(int group_fd) 126 | { 127 | if (ioctl(group_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP)) 128 | return -errno; 129 | 130 | return 0; 131 | } 132 | 133 | int perf_event_disable(int group_fd) 134 | { 135 | if (ioctl(group_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP)) 136 | return -errno; 137 | 138 | return 0; 139 | } 140 | -------------------------------------------------------------------------------- /src/libply/aux/printxf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | /* allow domains an easy way to defer standard specifiers to the 16 | * system's implementation. */ 17 | int printxf_vfprintf(struct printxf *pxf, 18 | FILE *fp, const char *spec, va_list ap) 19 | { 20 | return vfprintf(fp, spec, ap); 21 | } 22 | 23 | int __printxf_wsegment(FILE *fp, const char **fmt, size_t ssize, size_t *tsize) 24 | { 25 | size_t wsize; 26 | 27 | wsize = fwrite(*fmt, 1, ssize, fp); 28 | *tsize += wsize; 29 | *fmt += wsize; 30 | 31 | return (wsize < ssize) ? EOF : 0; 32 | } 33 | 34 | int __printxf(struct printxf *pxf, FILE *fp, const char *fmt, 35 | void *priv, va_list ap) 36 | { 37 | size_t tsize = 0, wsize, ssize; 38 | vfprintxf_fn handler; 39 | char spec[16]; 40 | int type; 41 | 42 | if (!pxf) 43 | pxf = &printxf_default; 44 | 45 | if (!fmt) 46 | return 0; 47 | 48 | while (*fmt) { 49 | ssize = strcspn(fmt, "%"); 50 | 51 | /* leading segment containing no format specifiers. */ 52 | if (ssize && __printxf_wsegment(fp, &fmt, ssize, &tsize)) 53 | break; 54 | 55 | if (fmt[0] == '\0') { 56 | /* this was the last segment */ 57 | break; 58 | } else if ((fmt[0] == '%') 59 | && ((fmt[1] == '\0') || (fmt[1] == '%'))) { 60 | /* "%" or "%%", write "%" */ 61 | if (!fwrite("%", 1, 1, fp)) 62 | break; 63 | 64 | tsize++; 65 | fmt += fmt[1] ? 2 : 1; 66 | continue; 67 | } 68 | 69 | ssize = strspn(fmt + 1, " #'*+,-.0123456789Lhjlqtz") + 1; 70 | 71 | if (!fmt[ssize]) { 72 | /* corner case. fmt ends with an unterminated 73 | * format. e.g. "evilness: 100%" */ 74 | __printxf_wsegment(fp, &fmt, ssize, &tsize); 75 | break; 76 | } 77 | 78 | type = fmt[ssize] & 0x7f; 79 | if (( priv && !pxf->xfprintxf[type]) || 80 | (!priv && !pxf->vfprintxf[type])) { 81 | /* unsupported specifier, write the entire 82 | * specifier unformatted to the output */ 83 | if (__printxf_wsegment(fp, &fmt, ssize + 1, &tsize)) 84 | break; 85 | 86 | continue; 87 | } 88 | 89 | ssize++; 90 | memset(spec, '\0', sizeof(spec)); 91 | strncpy(spec, fmt, (ssize >= sizeof(spec)) ? sizeof(spec) - 1 : ssize); 92 | fmt += ssize; 93 | 94 | if (priv) 95 | tsize += pxf->xfprintxf[type](pxf, fp, spec, priv); 96 | else { 97 | va_list aq; 98 | char *tmp; 99 | 100 | va_copy(aq, ap); 101 | tsize += pxf->vfprintxf[type](pxf, fp, spec, aq); 102 | va_end(aq); 103 | 104 | /* After printing the specifier using a copy 105 | * of `ap`, fast forward the original `ap` to 106 | * the same state. */ 107 | 108 | for (tmp = spec; *tmp; tmp++) { 109 | if (*tmp == '*') { 110 | int dummy_int = va_arg(ap, int); 111 | } 112 | } 113 | 114 | /* This is ugly, but this is the only portable 115 | * way I can think of that let's us use our 116 | * libc's vfprintf implementation. NOTE: We 117 | * can assume nothing about the underlying 118 | * type of `va_list` as it varies between 119 | * architectures, we can only use the va_* 120 | * family of functions/macros. */ 121 | switch (type) { 122 | case 'a': case 'A': 123 | case 'e': case 'E': 124 | case 'f': case 'F': 125 | case 'g': case 'G': 126 | if (strstr(spec, "L")) { 127 | long double dummy_ld = va_arg(ap, long double); 128 | } else { 129 | double dummy_d = va_arg(ap, double); 130 | } 131 | break; 132 | 133 | case 'c': 134 | case 'd': case 'i': 135 | case 'o': case 'u': 136 | case 'x': case 'X': 137 | if (strstr(spec, "ll") || strstr(spec, "q")) { 138 | long long int dummy_ll = va_arg(ap, long long int); 139 | } else if (strstr(spec, "l")) { 140 | long int dummy_l = va_arg(ap, long int); 141 | } else { 142 | int dummy_int = va_arg(ap, int); 143 | } 144 | break; 145 | 146 | case 'p': case 's': 147 | default: 148 | /* Extensions must consume exactly one 149 | * pointer. */ 150 | do { void *dummy_ptr = va_arg(ap, void *); } while (0); 151 | break; 152 | } 153 | } 154 | } 155 | 156 | return tsize; 157 | } 158 | 159 | int xfprintxf(struct printxf *pxf, FILE *fp, const char *fmt, void *priv) 160 | { 161 | va_list ap; 162 | 163 | return __printxf(pxf, fp, fmt, priv, ap); 164 | } 165 | 166 | int vfprintxf(struct printxf *pxf, FILE *fp, const char *fmt, va_list ap) 167 | { 168 | return __printxf(pxf, fp, fmt, NULL, ap); 169 | } 170 | 171 | int fprintxf(struct printxf *pxf, FILE *fp, const char *fmt, ...) 172 | { 173 | va_list ap; 174 | int ret; 175 | 176 | va_start(ap, fmt); 177 | ret = vfprintxf(pxf, fp, fmt, ap); 178 | va_end(ap); 179 | return ret; 180 | } 181 | 182 | int vprintxf(struct printxf *pxf, const char *fmt, va_list ap) 183 | { 184 | return vfprintxf(pxf, stdout, fmt, ap); 185 | } 186 | 187 | int printxf(struct printxf *pxf, const char *fmt, ...) 188 | { 189 | va_list ap; 190 | int ret; 191 | 192 | va_start(ap, fmt); 193 | ret = vprintxf(pxf, fmt, ap); 194 | va_end(ap); 195 | return ret; 196 | } 197 | 198 | struct printxf printxf_default = { 199 | .vfprintxf = { 200 | ['a'] = printxf_vfprintf, ['A'] = printxf_vfprintf, 201 | ['c'] = printxf_vfprintf, ['d'] = printxf_vfprintf, 202 | ['e'] = printxf_vfprintf, ['E'] = printxf_vfprintf, 203 | ['f'] = printxf_vfprintf, ['F'] = printxf_vfprintf, 204 | ['g'] = printxf_vfprintf, ['G'] = printxf_vfprintf, 205 | ['i'] = printxf_vfprintf, ['o'] = printxf_vfprintf, 206 | ['p'] = printxf_vfprintf, ['s'] = printxf_vfprintf, 207 | ['u'] = printxf_vfprintf, 208 | ['x'] = printxf_vfprintf, ['X'] = printxf_vfprintf, 209 | }, 210 | }; 211 | -------------------------------------------------------------------------------- /src/libply/aux/syscall.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | static __u64 ptr_to_u64(const void *ptr) 18 | { 19 | return (__u64) (unsigned long) ptr; 20 | } 21 | 22 | int bpf_prog_load(enum bpf_prog_type type, 23 | const struct bpf_insn *insns, int insn_cnt, 24 | char *vlog, size_t vlog_sz) 25 | { 26 | union bpf_attr attr; 27 | 28 | /* required since the kernel checks that unused fields and pad 29 | * bytes are zeroed */ 30 | memset(&attr, 0, sizeof(attr)); 31 | 32 | attr.kern_version = LINUX_VERSION_CODE; 33 | attr.prog_type = type; 34 | attr.insns = ptr_to_u64(insns); 35 | attr.insn_cnt = insn_cnt; 36 | attr.license = ptr_to_u64("GPL"); 37 | attr.log_buf = ptr_to_u64(vlog); 38 | attr.log_size = vlog_sz; 39 | attr.log_level = vlog ? 1 : 0; 40 | 41 | return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); 42 | } 43 | 44 | int bpf_map_create(enum bpf_map_type type, int key_sz, int val_sz, int entries) 45 | { 46 | union bpf_attr attr; 47 | 48 | /* required since the kernel checks that unused fields and pad 49 | * bytes are zeroed */ 50 | memset(&attr, 0, sizeof(attr)); 51 | 52 | attr.map_type = type; 53 | attr.key_size = key_sz; 54 | attr.value_size = val_sz; 55 | attr.max_entries = entries; 56 | 57 | return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); 58 | } 59 | 60 | int bpf_prog_test_run(int prog_fd) 61 | { 62 | union bpf_attr attr; 63 | 64 | memset(&attr, 0, sizeof(attr)); 65 | attr.test.prog_fd = prog_fd; 66 | 67 | return syscall(__NR_bpf, BPF_PROG_TEST_RUN, &attr, sizeof(attr)); 68 | } 69 | 70 | static int bpf_map_op(enum bpf_cmd cmd, int fd, 71 | void *key, void *val_or_next, int flags) 72 | { 73 | union bpf_attr attr = { 74 | .map_fd = fd, 75 | .key = ptr_to_u64(key), 76 | .value = ptr_to_u64(val_or_next), 77 | .flags = flags, 78 | }; 79 | 80 | return syscall(__NR_bpf, cmd, &attr, sizeof(attr)); 81 | } 82 | 83 | int bpf_map_lookup(int fd, void *key, void *val) 84 | { 85 | return bpf_map_op(BPF_MAP_LOOKUP_ELEM, fd, key, val, 0); 86 | } 87 | 88 | int bpf_map_update(int fd, void *key, void *val, int flags) 89 | { 90 | return bpf_map_op(BPF_MAP_UPDATE_ELEM, fd, key, val, flags); 91 | } 92 | 93 | int bpf_map_delete(int fd, void *key) 94 | { 95 | return bpf_map_op(BPF_MAP_DELETE_ELEM, fd, key, NULL, 0); 96 | } 97 | 98 | int bpf_map_next(int fd, void *key, void *next_key) 99 | { 100 | return bpf_map_op(BPF_MAP_GET_NEXT_KEY, fd, key, next_key, 0); 101 | } 102 | 103 | int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, 104 | int cpu, int group_fd, unsigned long flags) 105 | { 106 | int ret; 107 | 108 | ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, 109 | group_fd, flags); 110 | return ret; 111 | } 112 | -------------------------------------------------------------------------------- /src/libply/aux/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | int ply_debug; 17 | 18 | static void strkill(char *str, char kill) 19 | { 20 | char *r, *w; 21 | 22 | for (r = w = str; *r; r++) { 23 | if (*r == kill) 24 | continue; 25 | 26 | *w++ = *r; 27 | } 28 | 29 | *w = '\0'; 30 | } 31 | 32 | int strtonum(const char *_str, int64_t *s64, uint64_t *u64) 33 | { 34 | char *str = strdup(_str); 35 | 36 | strkill(str, '_'); 37 | 38 | errno = 0; 39 | if (*str == '-') { 40 | *s64 = strtoll(str, NULL, 0); 41 | if (!errno) 42 | return -1; 43 | } else if (strstr(str, "0b") == str) { 44 | *u64 = strtoull(&str[2], NULL, 2); 45 | if (!errno) 46 | return 1; 47 | } else { 48 | *u64 = strtoull(str, NULL, 0); 49 | if (!errno) 50 | return 1; 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | int isstring(const char *data, size_t len) 57 | { 58 | size_t i; 59 | 60 | /* All characters up to a '\0' must be printable. */ 61 | for (i = 0; (i < (len - 1)) && data[i]; i++) 62 | if (!isprint(data[i])) 63 | return 0; 64 | 65 | /* There must be at least one '\0', after which only more 66 | * '\0's may follow. */ 67 | for (; i < len; i++) 68 | if (data[i]) 69 | return 0; 70 | 71 | return 1; 72 | } 73 | 74 | FILE *fopenf(const char *mode, const char *fmt, ...) 75 | { 76 | va_list ap; 77 | FILE *fp; 78 | char *path; 79 | 80 | va_start(ap, fmt); 81 | vasprintf(&path, fmt, ap); 82 | va_end(ap); 83 | 84 | fp = fopen(path, mode); 85 | free(path); 86 | return fp; 87 | } 88 | 89 | struct ast_fprint_info { 90 | FILE *fp; 91 | int indent; 92 | }; 93 | 94 | static int __ast_fprint_pre(struct node *n, void *_info) 95 | { 96 | struct ast_fprint_info *info = _info; 97 | 98 | fprintxf(NULL, info->fp, "%*s%N", info->indent, "", n); 99 | 100 | if (n->sym && n->sym->type) 101 | fprintxf(NULL, info->fp, "%T", n->sym->type); 102 | 103 | fputc('\n', info->fp); 104 | 105 | if (n->ntype == N_EXPR) 106 | info->indent += 4; 107 | 108 | return 0; 109 | } 110 | 111 | static int __ast_fprint_post(struct node *n, void *_info) 112 | { 113 | struct ast_fprint_info *info = _info; 114 | 115 | if (n->ntype == N_EXPR) 116 | info->indent -= 4; 117 | 118 | return 0; 119 | } 120 | 121 | void ast_fprint(FILE *fp, struct node *root) 122 | { 123 | struct ast_fprint_info info = { 124 | .fp = fp, 125 | }; 126 | 127 | node_walk(root, __ast_fprint_pre, __ast_fprint_post, &info); 128 | fputc('\n', fp); 129 | } 130 | 131 | 132 | int order_vfprintxf(struct printxf *pxf, FILE *fp, const char *fmt, va_list ap) 133 | { 134 | int arg = va_arg(ap, int); 135 | 136 | switch (arg) { 137 | case 1: 138 | fputs("1st", fp); 139 | return 3; 140 | case 2: 141 | fputs("2nd", fp); 142 | return 3; 143 | case 3: 144 | fputs("3rd", fp); 145 | return 3; 146 | } 147 | 148 | return fprintf(fp, "%dth", arg); 149 | } 150 | 151 | __attribute__((constructor)) 152 | static void utils_init(void) 153 | { 154 | printxf_default.vfprintxf['O'] = order_vfprintxf; 155 | } 156 | -------------------------------------------------------------------------------- /src/libply/built-in/aggregation.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "built-in.h" 14 | 15 | 16 | static int count_ir_post(const struct func *func, struct node *n, 17 | struct ply_probe *pb) 18 | { 19 | struct node *mapop = n->up->expr.args; 20 | 21 | ir_emit_sym_to_reg(pb->ir, BPF_REG_0, mapop->sym); 22 | ir_emit_insn(pb->ir, ALU_IMM(BPF_ADD, 1), BPF_REG_0, 0); 23 | ir_emit_reg_to_sym(pb->ir, mapop->sym, BPF_REG_0); 24 | return 0; 25 | /* return map_ir_update(mapop, pb); */ 26 | } 27 | 28 | struct type t_count_func = { 29 | .ttype = T_FUNC, 30 | 31 | .func = { .type = &t_ulong }, 32 | }; 33 | 34 | static struct func count_func = { 35 | .name = "count", 36 | .type = &t_count_func, 37 | .static_ret = 1, 38 | 39 | .ir_post = count_ir_post, 40 | }; 41 | 42 | 43 | static int sum_ir_post(const struct func *func, struct node *n, 44 | struct ply_probe *pb) 45 | { 46 | struct node *mapop = n->up->expr.args; 47 | struct node *arg = n->expr.args; 48 | 49 | ir_emit_sym_to_reg(pb->ir, BPF_REG_0, mapop->sym); 50 | ir_emit_sym_to_reg(pb->ir, BPF_REG_1, arg->sym); 51 | ir_emit_insn(pb->ir, ALU64(BPF_ADD), BPF_REG_0, BPF_REG_1); 52 | ir_emit_reg_to_sym(pb->ir, mapop->sym, BPF_REG_0); 53 | return 0; 54 | /* return map_ir_update(mapop, pb); */ 55 | } 56 | 57 | static int sum_type_infer(const struct func *func, struct node *n) 58 | { 59 | struct node *arg; 60 | struct type *t; 61 | 62 | arg = n->expr.args; 63 | 64 | if (n->sym->type || !arg->sym->type) 65 | return 0; 66 | 67 | t = type_base(arg->sym->type); 68 | if (t->ttype != T_SCALAR) { 69 | _ne(n, "can't sum non-scalar value %N (type '%T').\n", 70 | arg, arg->sym->type); 71 | return -EINVAL; 72 | } 73 | 74 | n->sym->type = &t_ulong; 75 | return 0; 76 | } 77 | 78 | static struct func sum_func = { 79 | .name = "sum", 80 | .type = &t_unary_func, 81 | .type_infer = sum_type_infer, 82 | 83 | .ir_post = sum_ir_post, 84 | }; 85 | 86 | 87 | static uint64_t __quantize_total(struct type *t, const unsigned int *bucket) 88 | { 89 | uint64_t total; 90 | int i, len = type_base(t)->array.len; 91 | 92 | for (i = 0, total = 0; i < len; i++) 93 | total += bucket[i]; 94 | 95 | return total; 96 | } 97 | 98 | static void __quantize_fprint_hist_unicode(FILE *fp, unsigned int count, 99 | uint64_t total) 100 | { 101 | static const char bar_open[] = { 0xe2, 0x94, 0xa4 }; 102 | static const char bar_close[] = { 0xe2, 0x94, 0x82 }; 103 | 104 | int w = (((float)count / (float)total) * 256.0) + 0.5; 105 | int space = 32 - ((w + 7) >> 3); 106 | char block[] = { 0xe2, 0x96, 0x88 }; 107 | 108 | fwrite(bar_open, sizeof(bar_open), 1, fp); 109 | 110 | for (; w > 8; w -= 8) 111 | fwrite(block, sizeof(block), 1, fp); 112 | 113 | if (w) { 114 | block[2] += 8 - w; 115 | fwrite(block, sizeof(block), 1, fp); 116 | } 117 | 118 | fprintf(fp, "%*s", space, ""); 119 | fwrite(bar_close, sizeof(bar_close), 1, fp); 120 | } 121 | 122 | static void __quantize_fprint_hist_ascii(FILE *fp, unsigned int count, 123 | uint64_t total) 124 | { 125 | int w = (((float)count / (float)total) * 32.0) + 0.5; 126 | int i; 127 | 128 | fputc('|', fp); 129 | 130 | for (i = 0; i < 32; i++, w--) 131 | fputc((w > 0) ? '#' : ' ', fp); 132 | 133 | fputc('|', fp); 134 | } 135 | 136 | static int __quantize_fprint_value(FILE *fp, unsigned int count, uint64_t total) 137 | { 138 | fprintf(fp, "\t%8u ", count); 139 | 140 | if (ply_config.unicode) 141 | __quantize_fprint_hist_unicode(fp, count, total); 142 | else 143 | __quantize_fprint_hist_ascii(fp, count, total); 144 | fputc('\n', fp); 145 | return 0; 146 | } 147 | 148 | static int __quantize_normalize(int log2, char const **suffix) 149 | { 150 | static const char *s[] = { NULL, "k", "M", "G", "T", "P", "Z" }; 151 | int i; 152 | 153 | if (!log2) { 154 | *suffix = s[0]; 155 | return 0; 156 | } 157 | 158 | for (i = 0; log2 >= 10; i++, log2 -= 10); 159 | 160 | *suffix = s[i]; 161 | return (1 << log2); 162 | } 163 | 164 | static int __quantize_fprint_bucket_ext(struct type *t, FILE *fp, int i) 165 | { 166 | struct type *arg_type = t->priv; 167 | int64_t slo, shi; 168 | uint64_t ulo, uhi; 169 | 170 | slo = i ? (1LL << i) : 0; 171 | ulo = i ? (1ULL << i) : 0; 172 | 173 | shi = (1LL << (i + 1)) - 1; 174 | uhi = (1ULL << (i + 1)) - 1; 175 | 176 | fputs("\t[", fp); 177 | if (type_base(arg_type)->scalar.unsignd) 178 | type_fprint(arg_type, fp, &ulo); 179 | else 180 | type_fprint(arg_type, fp, &slo); 181 | 182 | fputs(", ", fp); 183 | if (type_base(arg_type)->scalar.unsignd) 184 | type_fprint(arg_type, fp, &uhi); 185 | else 186 | type_fprint(arg_type, fp, &shi); 187 | 188 | fputs("]", fp); 189 | return 0; 190 | } 191 | 192 | static int __quantize_fprint_bucket(struct type *t, FILE *fp, int i) 193 | { 194 | struct type *arg_type = t->priv; 195 | const char *ls, *hs; 196 | int lo, hi; 197 | 198 | if (arg_type->fprint_log2) 199 | return __quantize_fprint_bucket_ext(t, fp, i); 200 | 201 | lo = __quantize_normalize(i , &ls); 202 | hi = __quantize_normalize(i + 1, &hs); 203 | 204 | /* closed interval for values < 1k, else open ended */ 205 | if (!hs) 206 | fprintf(fp, "\t[%4d, %4d]", lo, hi - 1); 207 | else 208 | fprintf(fp, "\t[%*d%s, %*d%s)", 209 | ls ? 3 : 4, lo, ls ? : "", 210 | hs ? 3 : 4, hi, hs ? : ""); 211 | 212 | return 0; 213 | } 214 | 215 | static int quantize_fprint(struct type *t, FILE *fp, const void *data) 216 | { 217 | const unsigned int *bucket = data; 218 | struct type *arg_type = t->priv; 219 | uint64_t total = __quantize_total(t, bucket); 220 | int gap, i, len; 221 | 222 | fputc('\n', fp); 223 | 224 | len = type_base(t)->array.len; 225 | 226 | /* signed argument => last bucket holds count of negative 227 | * values and should thus be listed first. */ 228 | if (!type_base(arg_type)->scalar.unsignd) { 229 | len--; 230 | 231 | if (bucket[len]) { 232 | fputs("\t < 0", fp); 233 | __quantize_fprint_value(fp, bucket[len], total); 234 | if (!bucket[0]) 235 | fputs("\t...\n", fp); 236 | } 237 | } 238 | 239 | for (i = 0, gap = 0; i < len; i++) { 240 | if (bucket[i]) { 241 | if (gap) { 242 | if (gap != i) 243 | fputs("\t...\n", fp); 244 | gap = 0; 245 | } 246 | 247 | __quantize_fprint_bucket(t, fp, i); 248 | __quantize_fprint_value(fp, bucket[i], total); 249 | } else { 250 | gap++; 251 | } 252 | } 253 | 254 | return 0; 255 | } 256 | 257 | static int quantize_ir_post(const struct func *func, struct node *n, 258 | struct ply_probe *pb) 259 | { 260 | struct node *mapop = n->up->expr.args; 261 | struct node *arg = n->expr.args; 262 | struct type *atype = type_base(n->sym->type)->array.type; 263 | size_t bucketsz = type_sizeof(atype); 264 | int i; 265 | 266 | /* r0: bucket number 267 | r1: arg 268 | r2: arg copy, for 64-bit log2 operation 269 | */ 270 | ir_emit_insn(pb->ir, MOV_IMM(0), BPF_REG_0, 0); 271 | 272 | ir_emit_sym_to_reg(pb->ir, BPF_REG_1, arg->sym); 273 | if (type_sizeof(type_return(arg->sym->type)) > 4) { 274 | ir_emit_insn(pb->ir, MOV64, BPF_REG_2, BPF_REG_1); 275 | ir_emit_insn(pb->ir, ALU64_IMM(BPF_RSH, 32), BPF_REG_2, 0); 276 | ir_emit_insn(pb->ir, JMP_IMM(BPF_JEQ, 0, 2), BPF_REG_2, 0); 277 | ir_emit_insn(pb->ir, ALU_IMM(BPF_ADD, 32), BPF_REG_0, 0); 278 | ir_emit_insn(pb->ir, MOV64, BPF_REG_1, BPF_REG_2); 279 | } 280 | 281 | for (i = 16; i; i >>= 1) { 282 | ir_emit_insn(pb->ir, JMP_IMM(BPF_JLE, ((1 << i) - 1), 2), BPF_REG_1, 0); 283 | ir_emit_insn(pb->ir, ALU_IMM(BPF_ADD, i), BPF_REG_0, 0); 284 | ir_emit_insn(pb->ir, ALU64_IMM(BPF_RSH, i), BPF_REG_1, 0); 285 | } 286 | 287 | /* bucket in r0, convert it to an offset in the array */ 288 | switch (bucketsz) { 289 | case 8: 290 | ir_emit_insn(pb->ir, ALU_IMM(BPF_LSH, 3), BPF_REG_0, 0); 291 | break; 292 | case 4: 293 | ir_emit_insn(pb->ir, ALU_IMM(BPF_LSH, 2), BPF_REG_0, 0); 294 | break; 295 | default: 296 | assert(0); 297 | } 298 | 299 | ir_emit_ldbp(pb->ir, BPF_REG_1, mapop->sym->irs.stack); 300 | ir_emit_insn(pb->ir, ALU64(BPF_ADD), BPF_REG_1, BPF_REG_0); 301 | 302 | ir_emit_insn(pb->ir, MOV_IMM(1), BPF_REG_0, 0); 303 | ir_emit_insn(pb->ir, ST_XADD(bpf_width(bucketsz), 0), BPF_REG_1, BPF_REG_0); 304 | return 0; 305 | /* return map_ir_update(mapop, pb); */ 306 | } 307 | 308 | static int quantize_type_infer(const struct func *func, struct node *n) 309 | { 310 | struct node *arg; 311 | struct type *t, *array; 312 | char *type_name; 313 | 314 | arg = n->expr.args; 315 | 316 | if (n->sym->type || !arg->sym->type) 317 | return 0; 318 | 319 | t = type_base(arg->sym->type); 320 | if (t->ttype != T_SCALAR) { 321 | _ne(n, "can't quantize non-scalar value %N (type '%T').\n", 322 | arg, arg->sym->type); 323 | return -EINVAL; 324 | } 325 | 326 | array = type_array_of(&t_uint, type_sizeof(t) * 8); 327 | 328 | asprintf(&type_name, "quantize_%s_t", n->sym->name); 329 | n->sym->type = type_typedef(array, type_name); 330 | free(type_name); 331 | 332 | /* having access to the argument type lets us do (at least) 333 | * two things: (1) know whether the argument was signed or not 334 | * and thus, by extension, know how to interpret the top-most 335 | * bucket. (2) allow range output to be customized, 336 | * e.g. [256ms - 512ms] instead of [256G - 512G] and then 337 | * having to figure out what a giga-nanosecond is. */ 338 | n->sym->type->priv = arg->sym->type; 339 | n->sym->type->fprint = quantize_fprint; 340 | return 0; 341 | } 342 | 343 | static struct func quantize_func = { 344 | .name = "quantize", 345 | .type = &t_unary_func, 346 | .type_infer = quantize_type_infer, 347 | 348 | .ir_post = quantize_ir_post, 349 | }; 350 | 351 | void aggregation_init(void) 352 | { 353 | built_in_register(&count_func); 354 | built_in_register(&sum_func); 355 | built_in_register(&quantize_func); 356 | } 357 | -------------------------------------------------------------------------------- /src/libply/built-in/buffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include "built-in.h" 21 | 22 | TAILQ_HEAD(buffer_evhs, buffer_evh); 23 | static struct buffer_evhs evhs = TAILQ_HEAD_INITIALIZER(evhs); 24 | 25 | static struct buffer_evh *buffer_evh_find(uint64_t id) 26 | { 27 | struct buffer_evh *evh; 28 | 29 | TAILQ_FOREACH(evh, &evhs, node) { 30 | if (evh->id == id) 31 | return evh; 32 | } 33 | 34 | return NULL; 35 | } 36 | 37 | static struct ply_return buffer_evh_call(struct buffer_ev *ev, size_t size) 38 | { 39 | struct buffer_evh *evh; 40 | 41 | evh = buffer_evh_find(ev->id); 42 | if (!evh) { 43 | _e("unknown event: id:%#"PRIx64" size:%#zx\n", 44 | ev->id, size); 45 | return (struct ply_return) { .err = 1, .val = ENOSYS }; 46 | } 47 | 48 | return evh->handle(ev, evh->priv); 49 | } 50 | 51 | void buffer_evh_register(struct buffer_evh *evh) 52 | { 53 | static uint64_t next_id = 0; 54 | 55 | evh->id = next_id++; 56 | TAILQ_INSERT_TAIL(&evhs, evh, node); 57 | } 58 | 59 | 60 | struct lost_event { 61 | struct perf_event_header hdr; 62 | uint64_t id; 63 | uint64_t lost; 64 | } __attribute__((packed)); 65 | 66 | struct buffer_q { 67 | int fd; 68 | volatile struct perf_event_mmap_page *mem; 69 | 70 | void *buf; 71 | }; 72 | 73 | struct buffer { 74 | int mapfd; 75 | uint32_t ncpus; 76 | 77 | struct pollfd *poll; 78 | struct buffer_q q[0]; 79 | }; 80 | 81 | static inline uint64_t __get_head(volatile struct perf_event_mmap_page *mem) 82 | { 83 | volatile uint64_t head = mem->data_head; 84 | 85 | asm volatile("" ::: "memory"); 86 | return head; 87 | } 88 | 89 | static inline void __set_tail(volatile struct perf_event_mmap_page *mem, uint64_t tail) 90 | { 91 | asm volatile("" ::: "memory"); 92 | 93 | mem->data_tail = tail; 94 | } 95 | 96 | struct ply_return buffer_q_drain(struct buffer_q *q) 97 | { 98 | struct lost_event *lost; 99 | struct ply_return ret = {}; 100 | struct buffer_ev *ev; 101 | uint64_t size, offs, head, tail; 102 | uint8_t *base, *this, *next; 103 | 104 | size = q->mem->data_size; 105 | offs = q->mem->data_offset; 106 | base = (uint8_t *)q->mem + offs; 107 | 108 | for (head = __get_head(q->mem); q->mem->data_tail != head; 109 | __set_tail(q->mem, q->mem->data_tail + ev->hdr.size)) { 110 | tail = q->mem->data_tail; 111 | 112 | this = base + (tail % size); 113 | ev = (void *)this; 114 | next = base + ((tail + ev->hdr.size) % size); 115 | 116 | if (next < this) { 117 | size_t left = (base + size) - this; 118 | 119 | q->buf = realloc(q->buf, ev->hdr.size); 120 | memcpy(q->buf, this, left); 121 | memcpy(q->buf + left, base, ev->hdr.size - left); 122 | ev = q->buf; 123 | } 124 | 125 | switch (ev->hdr.type) { 126 | case PERF_RECORD_SAMPLE: 127 | ret = buffer_evh_call(ev, ev->hdr.size); 128 | break; 129 | 130 | case PERF_RECORD_LOST: 131 | lost = (void *)ev; 132 | 133 | if (ply_config.strict) { 134 | _e("lost %"PRId64" events\n", lost->lost); 135 | ret.err = 1; 136 | ret.val = EOVERFLOW; 137 | } else { 138 | _w("lost %"PRId64" events\n", lost->lost); 139 | } 140 | break; 141 | 142 | default: 143 | _e("unknown perf event %#"PRIx32"\n", ev->hdr.type); 144 | ret.err = 1; 145 | ret.val = EINVAL; 146 | break; 147 | } 148 | 149 | if (ret.err || ret.exit) 150 | break; 151 | } 152 | 153 | return ret; 154 | } 155 | 156 | struct ply_return buffer_loop(struct buffer *buf, int timeout) 157 | { 158 | struct ply_return ret; 159 | uint32_t cpu; 160 | int ready; 161 | 162 | for (;;) { 163 | ready = poll(buf->poll, buf->ncpus, timeout); 164 | if (ready < 0) { 165 | ret.err = 1; 166 | ret.val = errno; 167 | return ret; 168 | } 169 | 170 | if (timeout == -1) { 171 | assert(ready); 172 | } else if (ready == 0) { 173 | ret.err = 0; 174 | return ret; 175 | } 176 | 177 | for (cpu = 0; ready && (cpu < buf->ncpus); cpu++) { 178 | if (!(buf->poll[cpu].revents & POLLIN)) 179 | continue; 180 | 181 | ret = buffer_q_drain(&buf->q[cpu]); 182 | if (ret.err | ret.exit) 183 | return ret; 184 | 185 | ready--; 186 | } 187 | } 188 | 189 | return ret; 190 | } 191 | 192 | int buffer_q_init(struct buffer *buf, uint32_t cpu) 193 | { 194 | struct perf_event_attr attr = { 0 }; 195 | struct buffer_q *q = &buf->q[cpu]; 196 | size_t size; 197 | int err; 198 | 199 | attr.type = PERF_TYPE_SOFTWARE; 200 | attr.config = PERF_COUNT_SW_BPF_OUTPUT; 201 | attr.sample_type = PERF_SAMPLE_RAW; 202 | attr.wakeup_events = 1; 203 | 204 | q->fd = perf_event_open(&attr, -1, cpu, -1, 0); 205 | if (q->fd < 0) { 206 | _e("could not create queue\n"); 207 | return q->fd; 208 | } 209 | 210 | err = bpf_map_update(buf->mapfd, &cpu, &q->fd, BPF_ANY); 211 | if (err) { 212 | _e("could not link map to queue\n"); 213 | return err; 214 | } 215 | 216 | size = sysconf(_SC_PAGESIZE) * (ply_config.buf_pages + 1); 217 | q->mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, q->fd, 0); 218 | if (q->mem == MAP_FAILED) { 219 | _e("could not mmap queue\n"); 220 | return -1; 221 | } 222 | 223 | buf->poll[cpu].fd = q->fd; 224 | buf->poll[cpu].events = POLLIN; 225 | return 0; 226 | } 227 | 228 | struct buffer *buffer_new(int mapfd) 229 | { 230 | struct buffer *buf; 231 | int err, cpu, ncpus = t_buffer.map.len; 232 | 233 | buf = xcalloc(1, sizeof(*buf) + ncpus * sizeof(buf->q[0])); 234 | 235 | buf->mapfd = mapfd; 236 | buf->ncpus = ncpus; 237 | 238 | buf->poll = xcalloc(ncpus, sizeof(*buf->poll)); 239 | 240 | for (cpu = 0; cpu < ncpus; cpu++) { 241 | err = buffer_q_init(buf, cpu); 242 | if (err) 243 | return NULL; 244 | } 245 | 246 | return buf; 247 | } 248 | 249 | 250 | 251 | static int stdbuf_static_validate(const struct func *func, struct node *n) 252 | { 253 | n->expr.ident = 1; 254 | return 0; 255 | } 256 | 257 | static struct func stdbuf_func = { 258 | .name = "stdbuf", 259 | .type = &t_buffer, 260 | .static_ret = 1, 261 | 262 | .static_validate = stdbuf_static_validate, 263 | }; 264 | 265 | static int bwrite_ir_post(const struct func *func, struct node *n, 266 | struct ply_probe *pb) 267 | { 268 | struct node *buf, *data, *ctx; 269 | 270 | ctx = n->expr.args; 271 | buf = ctx->next; 272 | data = buf->next; 273 | 274 | ir_emit_perf_event_output(pb->ir, buf->sym, ctx->sym, data->sym); 275 | return 0; 276 | } 277 | 278 | static struct tfield f_bwrite[] = { 279 | { .type = &t_void }, 280 | { .type = &t_buffer }, 281 | { .type = &t_void }, 282 | { .type = NULL } 283 | }; 284 | 285 | struct type t_bwrite = { 286 | .ttype = T_FUNC, 287 | .func = { .type = &t_void, .args = f_bwrite }, 288 | }; 289 | 290 | static struct func bwrite_func = { 291 | .name = "bwrite", 292 | .type = &t_bwrite, 293 | .static_ret = 1, 294 | 295 | .ir_post = bwrite_ir_post, 296 | }; 297 | 298 | void buffer_init(void) 299 | { 300 | built_in_register(&stdbuf_func); 301 | built_in_register(&bwrite_func); 302 | } 303 | -------------------------------------------------------------------------------- /src/libply/built-in/built-in.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "built-in.h" 19 | 20 | SLIST_HEAD(built_in_list, func); 21 | static struct built_in_list heads = SLIST_HEAD_INITIALIZER(heads); 22 | 23 | static struct type t_ctx = { 24 | .ttype = T_POINTER, 25 | 26 | .ptr = { 27 | .type = &t_void, 28 | 29 | /* 'ctx' is a pointer, but the kernel verifier will 30 | * mark 32-bit accesses as invalid even on 32-bit 31 | * ISAs, so we always treat it as a 64-bit pointer */ 32 | .bpf = 1, 33 | }, 34 | }; 35 | 36 | static struct func ctx_func = { 37 | .name = "ctx", 38 | .type = &t_ctx, 39 | .static_ret = 1, 40 | }; 41 | 42 | 43 | static struct type *num_type(struct node *n) 44 | { 45 | switch (n->num.size) { 46 | case 8: 47 | return n->num.unsignd ? &t_u64 : &t_s64; 48 | case 4: 49 | return n->num.unsignd ? &t_u32 : &t_s32; 50 | case 0: 51 | break; 52 | default: 53 | assert(0); 54 | } 55 | if (n->num.unsignd) { 56 | if (n->num.u64 <= INT_MAX) 57 | return &t_int; 58 | else if (n->num.u64 <= UINT_MAX) 59 | return &t_uint; 60 | else if (n->num.u64 <= LONG_MAX) 61 | return &t_long; 62 | else if (n->num.u64 <= ULONG_MAX) 63 | return &t_ulong; 64 | else if (n->num.u64 <= LLONG_MAX) 65 | return &t_llong; 66 | else if (n->num.u64 <= ULLONG_MAX) 67 | return &t_ullong; 68 | } else { 69 | if (n->num.s64 >= INT_MIN && n->num.s64 <= INT_MAX) 70 | return &t_int; 71 | else if (n->num.s64 >= LONG_MIN && n->num.s64 <= LONG_MAX) 72 | return &t_long; 73 | else if (n->num.s64 >= LLONG_MIN && n->num.s64 <= LLONG_MAX) 74 | return &t_llong; 75 | } 76 | 77 | assert(0); 78 | return NULL; 79 | } 80 | 81 | static int num_ir_post(const struct func *func, struct node *n, 82 | struct ply_probe *pb) 83 | { 84 | struct irstate *irs = &n->sym->irs; 85 | 86 | if ((n->num.unsignd && (n->num.u64 <= INT32_MAX)) || 87 | (n->num.s64 >= INT32_MIN && n->num.s64 <= INT32_MAX)) { 88 | irs->loc = LOC_IMM; 89 | irs->imm = n->num.s64; 90 | irs->size = type_sizeof(n->sym->type); 91 | return 0; 92 | } 93 | 94 | /* we need to load the constant to a register, so ignore any 95 | * advise about stack allocation. */ 96 | irs->hint.stack = 0; 97 | 98 | ir_init_sym(pb->ir, n->sym); 99 | 100 | /* use special instruction pair to load 64-bit immediate to 101 | * register. second instruction is a dummy except for the 102 | * upper 32 bits of the immediate. */ 103 | ir_emit_insn(pb->ir, LDDW_IMM((uint32_t)n->num.u64), irs->reg, 0); 104 | ir_emit_insn(pb->ir, INSN(0, 0, 0, 0, n->num.u64 >> 32), 0, 0); 105 | return 0; 106 | } 107 | 108 | static const struct func num_func = { 109 | .name = ":num", 110 | 111 | .ir_post = num_ir_post, 112 | }; 113 | 114 | 115 | static struct type *string_type(struct node *n) 116 | { 117 | size_t len = ((strlen(n->string.data) ? : 1) + 7) & ~7; 118 | 119 | return type_array_of(&t_char, len); 120 | } 121 | 122 | static int string_ir_post(const struct func *func, struct node *n, 123 | struct ply_probe *pb) 124 | { 125 | struct irstate *irs = &n->sym->irs; 126 | 127 | if (n->string.virtual) 128 | return 0; 129 | 130 | ir_init_sym(pb->ir, n->sym); 131 | 132 | ir_emit_data(pb->ir, irs->stack, n->string.data, 133 | type_sizeof(n->sym->type)); 134 | return 0; 135 | } 136 | 137 | static const struct func string_func = { 138 | .name = ":string", 139 | 140 | .ir_post = string_ir_post, 141 | }; 142 | 143 | 144 | static int env_rewrite(const struct func *func, struct node *n, 145 | struct ply_probe *pb) 146 | { 147 | struct node *new; 148 | const char *val; 149 | int64_t num; 150 | char *end; 151 | 152 | val = getenv(n->expr.func + 1); 153 | if (!val || !val[0]) 154 | goto string; 155 | 156 | errno = 0; 157 | num = strtoll(val, &end, 0); 158 | if (!errno && !*end) { 159 | new = node_num(&n->loc, strdup(val)); 160 | } else { 161 | string: 162 | new = node_string(&n->loc, strdup(val ? : "")); 163 | } 164 | 165 | node_replace(n, new); 166 | return 1; 167 | } 168 | 169 | static const struct func env_func = { 170 | .name = ":env", 171 | .type = &t_void, 172 | .static_ret = 1, 173 | 174 | .rewrite = env_rewrite, 175 | }; 176 | 177 | 178 | static int ident_ir_post(const struct func *func, struct node *n, 179 | struct ply_probe *pb) 180 | { 181 | if (n->sym->type->ttype == T_MAP && !n->sym->irs.hint.lval) { 182 | struct irstate *irs = &n->sym->irs; 183 | 184 | irs->hint.stack = 1; 185 | ir_init_sym(pb->ir, n->sym); 186 | 187 | /* 188 | * It'd be nice if we can save the map fd to a register 189 | * and then move it to the stack for the symbol. 190 | * But it was rejected by the kernel: 191 | * 192 | * error: output from kernel bpf verifier: 193 | * 0: (bf) r6 = r1 194 | * 1: (18) r1 = 0xffff9271503e5400 195 | * 3: (63) *(u32 *)(r10 -4) = r1 196 | * invalid size of register spill 197 | * 198 | * Actually, it doesn't matter which value we pass it 199 | * to the (async) print. All we need is a type info 200 | * and it's provided already. 201 | * 202 | * So let's just fill it by zero. 203 | */ 204 | ir_emit_insn(pb->ir, ST_IMM(bpf_width(irs->size), irs->stack, 0), 205 | BPF_REG_BP, 0); 206 | } 207 | 208 | return 0; 209 | } 210 | 211 | static const struct func ident_func = { 212 | .name = ":ident", 213 | 214 | .ir_post = ident_ir_post, 215 | }; 216 | 217 | 218 | static struct func block_func = { 219 | .name = "{}", 220 | .type = &t_vargs_func, 221 | .static_ret = 1, 222 | }; 223 | 224 | 225 | static const struct func *built_in_func_get(struct node *n) 226 | { 227 | const struct func *func; 228 | int err; 229 | 230 | SLIST_FOREACH(func, &heads, entry) { 231 | if (!strcmp(func->name, n->expr.func)) 232 | return func; 233 | } 234 | 235 | return NULL; 236 | } 237 | 238 | static int built_in_sym_alloc(struct ply_probe *pb, struct node *n) 239 | { 240 | const struct func *func = NULL; 241 | int err; 242 | 243 | switch (n->ntype) { 244 | case N_EXPR: 245 | func = built_in_func_get(n); 246 | if (func) 247 | break; 248 | 249 | if (!n->expr.args) { 250 | switch (n->expr.func[0]) { 251 | case '$': 252 | func = &env_func; 253 | break; 254 | default: 255 | n->expr.ident = 1; 256 | func = &ident_func; 257 | } 258 | } 259 | break; 260 | case N_NUM: 261 | func = &num_func; 262 | break; 263 | case N_STRING: 264 | func = &string_func; 265 | break; 266 | } 267 | 268 | if (!func) 269 | return -ENOENT; 270 | 271 | err = func_static_validate(func, n); 272 | if (err) 273 | return err; 274 | 275 | if (func == &ctx_func) { 276 | n->expr.ident = 1; 277 | n->sym = sym_alloc(&pb->locals, n, func); 278 | } else { 279 | n->sym = sym_alloc(&pb->ply->globals, n, func); 280 | } 281 | 282 | /* infer statically known types early */ 283 | if (n->ntype == N_NUM) 284 | n->sym->type = num_type(n); 285 | else if (n->ntype == N_STRING) 286 | n->sym->type = string_type(n); 287 | else if (func->static_ret) 288 | n->sym->type = func_return_type(func); 289 | return 0; 290 | } 291 | 292 | static int built_in_probe(struct ply_probe *pb) 293 | { 294 | return 0; 295 | } 296 | 297 | int built_in_ir_pre(struct ply_probe *pb) 298 | { 299 | struct sym **sym; 300 | 301 | symtab_foreach(&pb->locals, sym) { 302 | if ((*sym)->name && (*sym)->func == &ctx_func) { 303 | ir_init_sym(pb->ir, *sym); 304 | 305 | /* Kernel sets r1 to the address of the probe 306 | * context (i.e. a struct pt_regs for 307 | * {k,u}probes etc.). If we're using it we 308 | * need to get a reference to it before it is 309 | * clobbered. */ 310 | ir_emit_reg_to_sym(pb->ir, *sym, BPF_REG_1); 311 | } 312 | } 313 | 314 | return 0; 315 | } 316 | 317 | struct provider built_in = { 318 | .name = "!built-in", 319 | 320 | .sym_alloc = built_in_sym_alloc, 321 | .probe = built_in_probe, 322 | 323 | .ir_pre = built_in_ir_pre, 324 | 325 | }; 326 | 327 | void built_in_register(struct func *func) 328 | { 329 | SLIST_INSERT_HEAD(&heads, func, entry); 330 | } 331 | 332 | void built_in_init(void) 333 | { 334 | built_in_register(&ctx_func); 335 | built_in_register(&block_func); 336 | 337 | aggregation_init(); 338 | buffer_init(); 339 | flow_init(); 340 | math_init(); 341 | memory_init(); 342 | print_init(); 343 | proc_init(); 344 | } 345 | -------------------------------------------------------------------------------- /src/libply/built-in/built-in.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #ifndef _PLY_BUILT_IN_H 8 | #define _PLY_BUILT_IN_H 9 | 10 | #define __ply_built_in __attribute__(( \ 11 | section("built_ins"), \ 12 | aligned(__alignof__(struct func)) \ 13 | )) 14 | 15 | extern const struct func __start_built_ins; 16 | extern const struct func __stop_built_ins; 17 | 18 | void built_in_register(struct func *func); 19 | 20 | void aggregation_init(void); 21 | void buffer_init(void); 22 | void flow_init(void); 23 | void math_init(void); 24 | void memory_init(void); 25 | void print_init(void); 26 | void proc_init(void); 27 | 28 | #endif /* _PLY_BUILT_IN_H */ 29 | -------------------------------------------------------------------------------- /src/libply/built-in/flow.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "built-in.h" 14 | 15 | 16 | struct if_priv { 17 | unsigned rewritten; 18 | 19 | int16_t miss_label; 20 | int16_t end_label; 21 | }; 22 | 23 | 24 | static int iftest_ir_post(const struct func *func, struct node *n, 25 | struct ply_probe *pb) 26 | { 27 | struct node *expr, *estmt, *ifn = n->up; 28 | struct if_priv *ifp = ifn->sym->priv; 29 | int reg; 30 | 31 | expr = n->prev; 32 | estmt = n->next->next->next; 33 | 34 | if (expr->sym->irs.loc == LOC_REG) { 35 | reg = expr->sym->irs.reg; 36 | } else { 37 | reg = BPF_REG_0; 38 | ir_emit_sym_to_reg(pb->ir, reg, expr->sym); 39 | } 40 | 41 | ifp->miss_label = ir_alloc_label(pb->ir); 42 | if (estmt) 43 | ifp->end_label = ir_alloc_label(pb->ir); 44 | 45 | ir_emit_insn(pb->ir, JMP_IMM(BPF_JEQ, 0, ifp->miss_label), reg, 0); 46 | return 0; 47 | } 48 | 49 | static struct func iftest_func = { 50 | .name = ":iftest", 51 | .type = &t_void, 52 | .static_ret = 1, 53 | 54 | .ir_post = iftest_ir_post, 55 | }; 56 | 57 | 58 | static int ifjump_ir_post(const struct func *func, struct node *n, 59 | struct ply_probe *pb) 60 | { 61 | struct node *ifn = n->up; 62 | struct if_priv *ifp = ifn->sym->priv; 63 | 64 | if (ifp->end_label) 65 | ir_emit_insn(pb->ir, JMP_IMM(BPF_JA, 0, ifp->end_label), 0, 0); 66 | 67 | ir_emit_label(pb->ir, ifp->miss_label); 68 | return 0; 69 | } 70 | 71 | static struct func ifjump_func = { 72 | .name = ":ifjump", 73 | .type = &t_void, 74 | .static_ret = 1, 75 | 76 | .ir_post = ifjump_ir_post, 77 | }; 78 | 79 | 80 | static int if_ir_post(const struct func *func, struct node *n, 81 | struct ply_probe *pb) 82 | { 83 | struct if_priv *ifp = n->sym->priv; 84 | 85 | if (ifp->end_label) 86 | ir_emit_label(pb->ir, ifp->end_label); 87 | return 0; 88 | } 89 | 90 | static int if_rewrite(const struct func *func, struct node *n, 91 | struct ply_probe *pb) 92 | { 93 | struct if_priv *ifp = n->sym->priv; 94 | struct node *expr, *stmt; 95 | 96 | if (ifp->rewritten) 97 | return 0; 98 | 99 | expr = n->expr.args; 100 | stmt = expr->next; 101 | 102 | node_insert(expr, node_expr(&n->loc, ":iftest", NULL)); 103 | node_insert(stmt, node_expr(&n->loc, ":ifjump", NULL)); 104 | ifp->rewritten = 1; 105 | return 0; 106 | } 107 | 108 | static int if_type_infer(const struct func *func, struct node *n) 109 | { 110 | struct node *expr = n->expr.args; 111 | 112 | /* TODO: leaked */ 113 | if (!n->sym->priv) 114 | n->sym->priv = xcalloc(1, sizeof(struct if_priv)); 115 | 116 | if (!expr->sym->type) 117 | return 0; 118 | 119 | if (type_base(expr->sym->type)->ttype != T_SCALAR) { 120 | _ne(expr, "condition of '%N' must be a scalar value, " 121 | "but '%N' is of type '%T'\n", n, expr, expr->sym->type); 122 | return -EINVAL; 123 | } 124 | 125 | n->sym->type = &t_void; 126 | return 0; 127 | } 128 | 129 | static struct func if_func = { 130 | .name = "if", 131 | .type = &t_vargs_func, 132 | .static_ret = 1, 133 | .type_infer = if_type_infer, 134 | .rewrite = if_rewrite, 135 | 136 | .ir_post = if_ir_post, 137 | }; 138 | 139 | 140 | static struct ply_return exit_ev_handler(struct buffer_ev *ev, void *_null) 141 | { 142 | int *code = (void *)ev->data; 143 | 144 | _d("exit:%d\n", *code); 145 | return (struct ply_return){ .val = *code, .exit = 1 }; 146 | } 147 | 148 | static int exit_rewrite(const struct func *func, struct node *n, 149 | struct ply_probe *pb) 150 | { 151 | struct node *bwrite, *expr, *ev; 152 | struct buffer_evh *evh; 153 | uint64_t id; 154 | 155 | evh = n->sym->priv; 156 | id = evh->id; 157 | 158 | expr = n->expr.args; 159 | n->expr.args = NULL; 160 | 161 | ev = node_expr(&n->loc, ":struct", 162 | __node_num(&n->loc, sizeof(evh->id), NULL, &id), 163 | node_expr(&n->loc, ":struct", expr, NULL), 164 | NULL); 165 | 166 | bwrite = node_expr(&n->loc, "bwrite", 167 | node_expr(&n->loc, "ctx", NULL), 168 | node_expr(&n->loc, "stdbuf", NULL), 169 | ev, 170 | NULL); 171 | 172 | node_replace(n, bwrite); 173 | return 1; 174 | } 175 | 176 | static int exit_type_infer(const struct func *func, struct node *n) 177 | { 178 | struct node *expr = n->expr.args; 179 | struct buffer_evh *evh; 180 | 181 | if (n->sym->type) 182 | return 0; 183 | 184 | if (!expr->sym->type) 185 | return 0; 186 | 187 | if (type_base(expr->sym->type)->ttype != T_SCALAR) { 188 | _ne(expr, "argument to '%N' must be a scalar value, " 189 | "but '%N' is of type '%T'\n", n, expr, expr->sym->type); 190 | return -EINVAL; 191 | } 192 | 193 | evh = xcalloc(1, sizeof(*evh)); 194 | 195 | evh->handle = exit_ev_handler; 196 | buffer_evh_register(evh); 197 | 198 | /* TODO: leaked */ 199 | n->sym->priv = evh; 200 | n->sym->type = &t_void; 201 | return 0; 202 | } 203 | 204 | static struct func exit_func = { 205 | .name = "exit", 206 | .type = &t_unary_func, 207 | .type_infer = exit_type_infer, 208 | 209 | .rewrite = exit_rewrite, 210 | }; 211 | 212 | void flow_init(void) 213 | { 214 | built_in_register(&iftest_func); 215 | built_in_register(&ifjump_func); 216 | built_in_register(&if_func); 217 | built_in_register(&exit_func); 218 | } 219 | -------------------------------------------------------------------------------- /src/libply/built-in/print.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "built-in.h" 15 | 16 | struct printf_evh { 17 | const char *fmt; 18 | struct node *n; 19 | 20 | struct buffer_evh evh; 21 | }; 22 | 23 | struct printf_data { 24 | struct type *t; 25 | struct tfield *f; 26 | void *data; 27 | }; 28 | 29 | union value { 30 | char c; 31 | signed char sc; 32 | unsigned char uc; 33 | signed short ss; 34 | unsigned short us; 35 | signed int si; 36 | unsigned int ui; 37 | signed long sl; 38 | unsigned long ul; 39 | signed long long sll; 40 | unsigned long long ull; 41 | intmax_t smax; 42 | uintmax_t umax; 43 | ssize_t ssz; 44 | size_t sz; 45 | ptrdiff_t diff; 46 | double d; 47 | long double ld; 48 | uintptr_t ptr; 49 | char str[0]; 50 | }; 51 | 52 | int printf_int(FILE *fp, const char *spec, const char *type, union value *val) 53 | { 54 | int unsignd = strspn(type, "ouxX"); 55 | 56 | switch (*(type - 1)) { 57 | case 'h': 58 | if (*(type - 2) == 'h') { 59 | if (unsignd) 60 | return fprintf(fp, spec, val->uc); 61 | else 62 | return fprintf(fp, spec, val->sc); 63 | } else { 64 | if (unsignd) 65 | return fprintf(fp, spec, val->us); 66 | else 67 | return fprintf(fp, spec, val->ss); 68 | } 69 | case 'j': 70 | if (unsignd) 71 | return fprintf(fp, spec, val->umax); 72 | else 73 | return fprintf(fp, spec, val->smax); 74 | case 'l': 75 | if (*(type - 2) == 'l') { 76 | longlong: 77 | if (unsignd) 78 | return fprintf(fp, spec, val->ull); 79 | else 80 | return fprintf(fp, spec, val->sll); 81 | } else { 82 | if (unsignd) 83 | return fprintf(fp, spec, val->ul); 84 | else 85 | return fprintf(fp, spec, val->sl); 86 | } 87 | case 'q': 88 | goto longlong; 89 | case 't': 90 | return fprintf(fp, spec, val->diff); 91 | case 'z': 92 | if (unsignd) 93 | return fprintf(fp, spec, val->sz); 94 | else 95 | return fprintf(fp, spec, val->ssz); 96 | default: 97 | if (unsignd) 98 | return fprintf(fp, spec, val->ui); 99 | else 100 | return fprintf(fp, spec, val->si); 101 | } 102 | 103 | assert(0); 104 | return 0; 105 | } 106 | 107 | int printf_float(FILE *fp, const char *spec, const char *type, union value *val) 108 | { 109 | switch (*(type - 1)) { 110 | case 'L': 111 | return fprintf(fp, spec, val->ld); 112 | default: 113 | return fprintf(fp, spec, val->d); 114 | } 115 | 116 | assert(0); 117 | return 0; 118 | } 119 | 120 | int printf_xfprintxf(struct printxf *pxf, 121 | FILE *fp, const char *spec, void *_pd) 122 | { 123 | struct printf_data *pd = _pd; 124 | const char *type; 125 | union value *val; 126 | size_t size; 127 | int ret = -ENOSYS; 128 | 129 | if (!pd->f->type) 130 | return fputs(spec, fp); 131 | 132 | /* We copy the value to ensure aligned access for all widths, 133 | * not all CPUs handle unaligned accesses gracefully. Always 134 | * allocate at least enough space to fit a long long, in case 135 | * the user does something like printf("%lld\n", (char)i). */ 136 | size = max((ssize_t)sizeof(*val), type_sizeof(pd->f->type)); 137 | 138 | /* Allocate an extra NUL byte to ensure that any value can 139 | * safely be interpreted as string. */ 140 | val = xcalloc(1, size + 1); 141 | memcpy(val, pd->data + type_offsetof(pd->t, pd->f->name), size); 142 | 143 | for (type = spec; *(type + 1); type++); 144 | 145 | switch (*type) { 146 | case 'c': 147 | ret = fprintf(fp, spec, val->c); 148 | break; 149 | case 'p': 150 | ret = fprintf(fp, spec, (void *)val->ptr); 151 | break; 152 | case 's': 153 | ret = fprintf(fp, spec, val->str); 154 | break; 155 | case 'v': 156 | ret = type_fprint(pd->f->type, fp, val->str); 157 | break; 158 | 159 | case 'd': 160 | case 'i': 161 | case 'o': 162 | case 'u': 163 | case 'x': case 'X': 164 | ret = printf_int(fp, spec, type, val); 165 | break; 166 | case 'a': case 'A': 167 | case 'e': case 'E': 168 | case 'f': case 'F': 169 | case 'g': case 'G': 170 | ret = printf_float(fp, spec, type, val); 171 | break; 172 | default: 173 | ret = fputs(spec, fp); 174 | } 175 | 176 | free(val); 177 | 178 | pd->f++; 179 | return ret; 180 | } 181 | 182 | struct printxf printf_printxf = { 183 | .xfprintxf = { 184 | ['a'] = printf_xfprintxf, ['A'] = printf_xfprintxf, 185 | ['c'] = printf_xfprintxf, ['d'] = printf_xfprintxf, 186 | ['e'] = printf_xfprintxf, ['E'] = printf_xfprintxf, 187 | ['f'] = printf_xfprintxf, ['F'] = printf_xfprintxf, 188 | ['g'] = printf_xfprintxf, ['G'] = printf_xfprintxf, 189 | ['i'] = printf_xfprintxf, ['o'] = printf_xfprintxf, 190 | ['p'] = printf_xfprintxf, ['s'] = printf_xfprintxf, 191 | ['u'] = printf_xfprintxf, ['v'] = printf_xfprintxf, 192 | ['x'] = printf_xfprintxf, ['X'] = printf_xfprintxf, 193 | }, 194 | }; 195 | 196 | static struct ply_return printf_ev_handler(struct buffer_ev *ev, void *_pevh) 197 | { 198 | struct printf_evh *pevh = _pevh; 199 | 200 | if (!pevh->n) { 201 | fputs(pevh->fmt, stdout); 202 | } else { 203 | struct printf_data pd = { 204 | .t = pevh->n->sym->type, 205 | .f = pevh->n->sym->type->sou.fields, 206 | .data = ev->data, 207 | }; 208 | 209 | xfprintxf(&printf_printxf, stdout, pevh->fmt, &pd); 210 | } 211 | 212 | return (struct ply_return){ }; 213 | } 214 | 215 | static int printf_rewrite(const struct func *func, struct node *n, 216 | struct ply_probe *pb) 217 | { 218 | struct node *bwrite, *exprs, *ev; 219 | struct printf_evh *pevh; 220 | uint64_t id; 221 | 222 | pevh = n->sym->priv; 223 | id = pevh->evh.id; 224 | 225 | exprs = n->expr.args->next; 226 | n->expr.args->next = NULL; 227 | 228 | ev = node_expr(&n->loc, ":struct", 229 | __node_num(&n->loc, sizeof(pevh->evh.id), NULL, &id), 230 | exprs ? node_expr(&n->loc, ":struct", exprs, NULL) : NULL, 231 | NULL); 232 | 233 | bwrite = node_expr(&n->loc, "bwrite", 234 | node_expr(&n->loc, "ctx", NULL), 235 | node_expr(&n->loc, "stdbuf", NULL), 236 | ev, 237 | NULL); 238 | 239 | node_replace(n, bwrite); 240 | 241 | pevh->n = ev->expr.args->next; 242 | return 1; 243 | } 244 | 245 | static int printf_type_infer(const struct func *func, struct node *n) 246 | { 247 | struct node *expr = n->expr.args; 248 | struct printf_evh *pevh; 249 | 250 | if (n->sym->type) 251 | return 0; 252 | 253 | if (!n->expr.args) { 254 | _ne(n, "format specifier missing.\n"); 255 | return -EINVAL; 256 | } 257 | 258 | if (n->expr.args->ntype != N_STRING) { 259 | _ne(n, "format specifier must be a string literal.\n"); 260 | return -EINVAL; 261 | } 262 | 263 | n->expr.args->string.virtual = 1; 264 | 265 | pevh = xcalloc(1, sizeof(*pevh)); 266 | 267 | pevh->evh.handle = printf_ev_handler; 268 | pevh->evh.priv = pevh; 269 | buffer_evh_register(&pevh->evh); 270 | 271 | pevh->fmt = n->expr.args->string.data; 272 | 273 | /* TODO: leaked */ 274 | n->sym->priv = pevh; 275 | n->sym->type = &t_void; 276 | return 0; 277 | } 278 | 279 | static struct func printf_func = { 280 | .name = "printf", 281 | .type = &t_vargs_func, 282 | .type_infer = printf_type_infer, 283 | 284 | .rewrite = printf_rewrite, 285 | }; 286 | 287 | struct print_ev_data { 288 | struct node *n; 289 | struct ply *ply; 290 | }; 291 | 292 | static struct ply_return print_ev_handler(struct buffer_ev *ev, void *_n) 293 | { 294 | struct print_ev_data *data = _n; 295 | struct node *n = data->n; 296 | struct type *t = n->sym->type; 297 | struct tfield *f; 298 | 299 | tfields_foreach(f, t->sou.fields) { 300 | if (f != t->sou.fields) 301 | fputs(", ", stdout); 302 | 303 | if (f->type->ttype == T_MAP) { 304 | type_fprint(f->type, stdout, data->ply); 305 | continue; 306 | } 307 | 308 | type_fprint(f->type, stdout, 309 | ev->data + type_offsetof(t, f->name)); 310 | } 311 | 312 | putchar('\n'); 313 | return (struct ply_return){ }; 314 | } 315 | 316 | static int print_rewrite(const struct func *func, struct node *n, 317 | struct ply_probe *pb) 318 | { 319 | struct node *bwrite, *exprs, *ev; 320 | struct buffer_evh *evh; 321 | struct print_ev_data *data; 322 | uint64_t id; 323 | 324 | evh = n->sym->priv; 325 | id = evh->id; 326 | 327 | exprs = n->expr.args; 328 | n->expr.args = NULL; 329 | 330 | ev = node_expr(&n->loc, ":struct", 331 | __node_num(&n->loc, sizeof(evh->id), NULL, &id), 332 | node_expr(&n->loc, ":struct", exprs, NULL), 333 | NULL); 334 | 335 | bwrite = node_expr(&n->loc, "bwrite", 336 | node_expr(&n->loc, "ctx", NULL), 337 | node_expr(&n->loc, "stdbuf", NULL), 338 | ev, 339 | NULL); 340 | 341 | node_replace(n, bwrite); 342 | 343 | data = malloc(sizeof(*data)); 344 | if (data == NULL) 345 | return -1; 346 | 347 | data->n = ev->expr.args->next; 348 | data->ply = pb->ply; 349 | 350 | /* TODO: leaked */ 351 | evh->priv = data; 352 | return 1; 353 | } 354 | 355 | static int print_type_infer(const struct func *func, struct node *n) 356 | { 357 | struct buffer_evh *evh; 358 | 359 | if (n->sym->type) 360 | return 0; 361 | 362 | evh = xcalloc(1, sizeof(*evh)); 363 | 364 | evh->handle = print_ev_handler; 365 | buffer_evh_register(evh); 366 | 367 | /* TODO: leaked */ 368 | n->sym->priv = evh; 369 | n->sym->type = &t_void; 370 | return 0; 371 | } 372 | 373 | static struct func print_func = { 374 | .name = "print", 375 | .type = &t_vargs_func, 376 | .type_infer = print_type_infer, 377 | 378 | .rewrite = print_rewrite, 379 | }; 380 | 381 | void print_init(void) 382 | { 383 | built_in_register(&printf_func); 384 | built_in_register(&print_func); 385 | } 386 | -------------------------------------------------------------------------------- /src/libply/compile.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Tobias Waldekranz 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | static int pass_sym_alloc(struct node *n, void *_pb) 17 | { 18 | struct ply_probe *pb = _pb; 19 | struct provider *built_in = provider_get("!built-in"); 20 | int err = 0; 21 | 22 | if (n->sym) 23 | return 0; 24 | 25 | switch (n->ntype) { 26 | case N_EXPR: 27 | err = pb->provider->sym_alloc(pb, n); 28 | if (!err || (err != -ENOENT)) 29 | break; 30 | 31 | /* fall-through */ 32 | case N_NUM: 33 | case N_STRING: 34 | err = built_in->sym_alloc(pb, n); 35 | } 36 | 37 | if (err) { 38 | if ((err == -ENOENT)) 39 | _ne(n, "unknown symbol '%N'.\n", n); 40 | } else { 41 | assert(n->sym); 42 | } 43 | 44 | return err; 45 | } 46 | 47 | 48 | static int pass_type_infer(struct node *n, void *_pb) 49 | { 50 | struct ply_probe *pb = _pb; 51 | 52 | if (n->sym->func->type_infer) 53 | return n->sym->func->type_infer(n->sym->func, n); 54 | 55 | return 0; 56 | } 57 | 58 | static int pass_type_report(struct node *n, void *_pb) 59 | { 60 | if (!n->sym->type) 61 | _ne(n, "type of symbol '%N' is unknown\n", n); 62 | 63 | return 0; 64 | } 65 | 66 | static int pass_type_validate(struct node *n, void *_pb) 67 | { 68 | if (!n->sym->type) 69 | return -EINVAL; 70 | 71 | return 0; 72 | } 73 | 74 | static int pass_rewrite(struct node *n, void *_pb) 75 | { 76 | struct ply_probe *pb = _pb; 77 | 78 | if (n->sym->func->rewrite) 79 | return n->sym->func->rewrite(n->sym->func, n, pb); 80 | 81 | return 0; 82 | } 83 | 84 | static char *pass_ir_comment(struct node *n, const char *phase) 85 | { 86 | char *comment; 87 | 88 | switch (n->ntype) { 89 | case N_EXPR: 90 | asprintf(&comment, "%s %s()", phase, n->expr.func); 91 | break; 92 | case N_STRING: 93 | asprintf(&comment, "%s \"%s\"", phase, n->string.data); 94 | break; 95 | case N_NUM: 96 | if (n->num.unsignd) 97 | asprintf(&comment, "%s <%#" PRIx64 ">", phase, n->num.u64); 98 | else 99 | asprintf(&comment, "%s <%" PRId64 ">", phase, n->num.s64); 100 | break; 101 | } 102 | 103 | return comment; 104 | } 105 | 106 | static int pass_ir_pre(struct node *n, void *_pb) 107 | { 108 | struct ply_probe *pb = _pb; 109 | int err = 0; 110 | 111 | ir_emit_comment(pb->ir, pass_ir_comment(n, ">pre ")); /* TODO comment leaked */ 112 | 113 | if (n->sym->func->ir_pre) 114 | err = n->sym->func->ir_pre(n->sym->func, n, pb); 115 | 116 | /* ir_emit_comment(pb->ir, pass_ir_comment(n, "
ir, pass_ir_comment(n, ">post")); /* TODO comment leaked */
126 | 
127 | 	if (n->sym->func->ir_post)
128 | 		err = n->sym->func->ir_post(n->sym->func, n, pb);
129 | 
130 | 	/* ir_emit_comment(pb->ir, pass_ir_comment(n, "ast, pre, post, pb);
141 | 		if (err)
142 | 			return err;
143 | 	}
144 | 
145 | 	return 0;
146 | }
147 | 
148 | static int run_ir(struct ply *ply)
149 | {
150 | 	struct provider *built_in = provider_get("!built-in");
151 | 	struct ply_probe *pb;
152 | 	int err;
153 | 
154 | 	ply_probe_foreach(ply, pb) {
155 | 		err = pb->provider->ir_pre ?
156 | 			pb->provider->ir_pre(pb) : 0;
157 | 		if (err)
158 | 			return err;
159 | 
160 | 		err = built_in->ir_pre ?
161 | 			built_in->ir_pre(pb) : 0;
162 | 		if (err)
163 | 			return err;
164 | 
165 | 		err = node_walk(pb->ast, pass_ir_pre, pass_ir_post, pb);
166 | 		if (err)
167 | 			return err;
168 | 
169 | 		err = built_in->ir_post ?
170 | 			built_in->ir_post(pb) : 0;
171 | 		if (err)
172 | 			return err;
173 | 
174 | 		err = pb->provider->ir_post ?
175 | 			pb->provider->ir_post(pb) : 0;
176 | 		if (err)
177 | 			return err;
178 | 
179 | 		ir_emit_insn(pb->ir, EXIT, 0, 0);
180 | 	}
181 | 
182 | 	return 0;
183 | }
184 | 
185 | static int run_bpf(struct ply *ply)
186 | {
187 | 	struct ply_probe *pb;
188 | 	int err;
189 | 
190 | 	ply_probe_foreach(ply, pb) {
191 | 		err = ir_bpf_generate(pb->ir);
192 | 		if (err)
193 | 			return err;
194 | 	}
195 | 
196 | 	return 0;
197 | }
198 | 
199 | int ply_compile(struct ply *ply)
200 | {
201 | 	struct pass *pass;
202 | 	int err = 0, rewrites;
203 | 
204 | 	for (rewrites = 0; rewrites < 10; rewrites++) {
205 | 		err =         run_walk(ply, NULL, pass_sym_alloc);
206 | 		err = err ? : run_walk(ply, NULL, pass_type_infer);
207 | 		err = err ? : run_walk(ply, NULL, pass_rewrite);
208 | 		if (err < 0)
209 | 			return err;
210 | 
211 | 		if (!err)
212 | 			break;
213 | 	}
214 | 
215 | 	assert(!err);
216 | 
217 | 	err = err ? : run_walk(ply, NULL, pass_sym_alloc);
218 | 	err = err ? : run_walk(ply, NULL, pass_type_infer);
219 | 
220 | 	err = err ? : run_walk(ply, NULL, pass_type_report);
221 | 	err = err ? : run_walk(ply, NULL, pass_type_validate);
222 | 
223 | 	err = err ? : run_ir(ply);
224 | 	err = err ? : run_bpf(ply);
225 | 
226 | 	return err;
227 | }
228 | 


--------------------------------------------------------------------------------
/src/libply/func.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | 
  9 | #include 
 10 | #include 
 11 | 
 12 | int func_pass_ir_post(const struct func *func, struct node *n,
 13 | 		      struct ply_probe *pb)
 14 | {
 15 | 	struct node *child;
 16 | 
 17 | 	child = n->expr.args;
 18 | 
 19 | 	ir_init_sym(pb->ir, n->sym);
 20 | 	ir_emit_sym_to_sym(pb->ir, n->sym, child->sym);
 21 | 	return 0;
 22 | 
 23 | }
 24 | 
 25 | 
 26 | static int func_validate_expr(const struct func *func, struct node *n, int strict)
 27 | {
 28 | 	struct tfield *f;
 29 | 	struct node *arg;
 30 | 	int fargs, nargs = 0;
 31 | 
 32 | 	if (func->type->ttype != T_FUNC) {
 33 | 		nargs = node_nargs(n);
 34 | 
 35 | 		if (nargs) {
 36 | 			fargs = type_nargs(func->type);
 37 | 			goto too_many;
 38 | 		}
 39 | 
 40 | 		return 0;
 41 | 	}
 42 | 
 43 | 	for (f = func->type->func.args, arg = n->expr.args;
 44 | 	     f && f->type && arg; f++, arg = arg->next, nargs++) {
 45 | 		if ((!strict && (f->type->ttype == T_VOID))
 46 | 		    || (!strict && !arg->sym->type)
 47 | 		    || (!strict && (arg->sym->type->ttype == T_VOID))
 48 | 		    || type_compatible(arg->sym->type, f->type))
 49 | 			continue;
 50 | 
 51 | 		_ne(n, "%O argument to '%N' is of type '%T', expected '%T'.\n",
 52 | 		    nargs, n, arg->sym->type, f->type);
 53 | 	}
 54 | 
 55 | 	if ((!f || !f->type) && !arg)
 56 | 		return 0;
 57 | 
 58 | 	nargs = node_nargs(n);
 59 | 	fargs = type_nargs(func->type);
 60 | 	if (f && f->type) {
 61 | 		_ne(n, "too few arguments to %N; expected%s %d, got %d.\n",
 62 | 		    n, func->type->func.vargs? " at least" : "", fargs, nargs);
 63 | 		return -EINVAL;
 64 | 	}
 65 | 
 66 | 	if (func->type->func.vargs)
 67 | 		return 0;
 68 | 
 69 | too_many:
 70 | 	_ne(n, "too many arguments to %N; expected %d, got %d.\n",
 71 | 	    n, fargs, nargs);
 72 | 	return -EINVAL;
 73 | }
 74 | 
 75 | int func_static_validate(const struct func *func, struct node *n)
 76 | {
 77 | 	int err = 0;
 78 | 
 79 | 	if (!func->type)
 80 | 		goto check_callback;
 81 | 
 82 | 	switch (n->ntype) {
 83 | 	case N_EXPR:
 84 | 		/* if (func->type->ttype != T_FUNC) { */
 85 | 		/* 	_ne(n, "%N is not callable.\n", n); */
 86 | 		/* 	return -EINVAL; */
 87 | 		/* } */
 88 | 		err = func_validate_expr(func, n, 0);
 89 | 		break;
 90 | 
 91 | 	/* case N_IDENT: */
 92 | 	/* 	if (func->type->ttype == T_FUNC) { */
 93 | 	/* 		_ne(n, "%N is a function.\n", n); */
 94 | 	/* 		return -EINVAL; */
 95 | 	/* 	} */
 96 | 	/* 	break; */
 97 | 
 98 | 	default:
 99 | 		/* num, str. nothing to validate. */
100 | 		break;
101 | 	}
102 | 
103 | check_callback:
104 | 	if (!err && func->static_validate)
105 | 		err = func->static_validate(func, n);
106 | 
107 | 	return err;
108 | }
109 | 
110 | struct type *func_return_type(const struct func *func)
111 | {
112 | 	if (!func->type)
113 | 		return NULL;
114 | 
115 | 	if (func->type->ttype == T_FUNC)
116 | 		return func->type->func.type;
117 | 
118 | 	return func->type;
119 | }
120 | 


--------------------------------------------------------------------------------
/src/libply/grammar.y:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | /* Don't allow any ambiguity in the grammar */
  8 | %expect 0
  9 | 
 10 | %defines
 11 | %locations
 12 | %define api.pure
 13 | %define parse.error verbose
 14 | 
 15 | %lex-param   { void *scanner }
 16 | %parse-param { void *scanner }
 17 | %parse-param { struct ply *ply }
 18 | 
 19 | %code requires {
 20 | #include 
 21 | 
 22 | #define YYLTYPE struct nloc
 23 | 
 24 | struct ply;
 25 | } 
 26 | 
 27 | %{
 28 | #include 
 29 | #include 
 30 | 
 31 | #include "grammar.h"
 32 | #include "lexer.h"
 33 | 
 34 | void yyerror(struct nloc *loc, yyscan_t scanner, struct ply *ply, const char *s)
 35 | {
 36 | 	fprintf(stderr, "%d: error: %s\n", loc->first_line, s);
 37 | }
 38 | 
 39 | extern int __ply_probe_alloc(struct ply *ply, struct node *pspec, struct node *ast);
 40 | 
 41 | %}
 42 | 
 43 | %initial-action {
 44 | 	@$.last_line = 1;
 45 | };
 46 | 
 47 | %define api.value.type { struct node * }
 48 | 
 49 | %token ENDPRED IF ELSE RETURN DELETE EQ NE LE GE LSH RSH AND XOR OR DEREF PSPEC IDENT AGG STRING NUMBER
 50 | 
 51 | /* C if-else associativity */
 52 | %right ')' ELSE
 53 | 
 54 | %start probes
 55 | 
 56 | %%
 57 | 
 58 | probes
 59 | : probe
 60 | | probe probes
 61 | ;
 62 | 
 63 | probe
 64 | : PSPEC stmt      { __ply_probe_alloc(ply, $1, $2); }
 65 | | PSPEC predicate { __ply_probe_alloc(ply, $1, $2); }
 66 | ;
 67 | 
 68 | /* Support dtrace-style predicates as well as normal if guards. I.e.
 69 |  * `provider:probe if (pid == 42) @ = count();` is equivalent to
 70 |  * `provider:probe / pid == 42 / { @ = count(); }`. */
 71 | predicate
 72 | : '/' expr ENDPRED stmts '}' {
 73 | 	$$ = node_expr(&@$, "if", $2, node_expr(&@$, "{}", $4, NULL), NULL);
 74 |  }
 75 | 
 76 | stmts
 77 | : stmt
 78 | | stmt stmts { $$ = node_append($1, $2); }
 79 | ;
 80 | 
 81 | stmt
 82 | : block
 83 | | branch
 84 | | jump
 85 | | delete
 86 | | expr_stmt
 87 | | assign
 88 | | aggregate
 89 | ;
 90 | 
 91 | block
 92 | : '{' '}'	{ $$ = node_expr(&@$, "{}", NULL); }
 93 | | '{' stmts '}' { $$ = node_expr(&@$, "{}", $2, NULL); }
 94 | ;
 95 | 
 96 | branch
 97 | : IF '(' expr ')' stmt			{ $$ = node_expr(&@$, "if", $3, $5, NULL); }
 98 | | IF '(' expr ')' stmt ELSE stmt	{ $$ = node_expr(&@$, "if", $3, $5, $7, NULL); }
 99 | ;
100 | 
101 | jump
102 | : RETURN ';' { $$ = node_expr(&@$, "return", NULL); }
103 | ;
104 | 
105 | delete
106 | : DELETE map ';' { $$ = node_expr(&@$, "delete", $2, NULL); }
107 | ;
108 | 
109 | expr_stmt
110 | : expr ';'
111 | ;
112 | 
113 | assign
114 | : map '=' expr ';' { $$ = node_expr(&@$, "=", $1, $3, NULL); }
115 | ;
116 | 
117 | aggregate
118 | : aggregation '=' expr ';' { $$ = node_expr(&@$, "@=", $1, $3, NULL); }
119 | ;
120 | 
121 | opt_exprs
122 | : exprs
123 | | %empty { $$ = NULL; }
124 | ;
125 | 
126 | 
127 | exprs
128 | : expr
129 | | expr ',' exprs { $$ = node_append($1, $3); }
130 | ;
131 | 
132 | expr
133 | : logor
134 | ;
135 | 
136 | logor
137 | : logxor
138 | | logor OR logxor { $$ = node_expr(&@$, "||", $1, $3, NULL); }
139 | ;
140 | 
141 | logxor
142 | : logand
143 | | logxor XOR logand { $$ = node_expr(&@$, "^^", $1, $3, NULL); }
144 | ;
145 | 
146 | logand
147 | : or
148 | | logand AND or { $$ = node_expr(&@$, "&&", $1, $3, NULL); }
149 | ;
150 | 
151 | or
152 | : xor
153 | | or '|' xor { $$ = node_expr(&@$, "|", $1, $3, NULL); }
154 | ;
155 | 
156 | xor
157 | : and
158 | | xor '^' and { $$ = node_expr(&@$, "^", $1, $3, NULL); }
159 | ;
160 | 
161 | and
162 | : eq
163 | | and '&' eq { $$ = node_expr(&@$, "&", $1, $3, NULL); }
164 | ;
165 | 
166 | eq
167 | : rel
168 | | eq EQ rel { $$ = node_expr(&@$, "==", $1, $3, NULL); }
169 | | eq NE rel { $$ = node_expr(&@$, "!=", $1, $3, NULL); }
170 | ;
171 | 
172 | rel
173 | : shift
174 | | rel '<' shift { $$ = node_expr(&@$,  "<", $1, $3, NULL); }
175 | | rel '>' shift { $$ = node_expr(&@$,  ">", $1, $3, NULL); }
176 | | rel LE  shift { $$ = node_expr(&@$, "<=", $1, $3, NULL); }
177 | | rel GE  shift { $$ = node_expr(&@$, ">=", $1, $3, NULL); }
178 | ;
179 | 
180 | shift
181 | : term
182 | | shift LSH term { $$ = node_expr(&@$, "<<", $1, $3, NULL); }
183 | | shift RSH term { $$ = node_expr(&@$, ">>", $1, $3, NULL); }
184 | ;
185 | 
186 | term
187 | : fact
188 | | term '+' fact { $$ = node_expr(&@$, "+", $1, $3, NULL); }
189 | | term '-' fact { $$ = node_expr(&@$, "-", $1, $3, NULL); }
190 | ;
191 | 
192 | fact
193 | : unary
194 | | fact '*' unary { $$ = node_expr(&@$, "*", $1, $3, NULL); }
195 | | fact '/' unary { $$ = node_expr(&@$, "/", $1, $3, NULL); }
196 | | fact '%' unary { $$ = node_expr(&@$, "%", $1, $3, NULL); }
197 | ;
198 | 
199 | unary
200 | : basic
201 | | func
202 | | map
203 | | basic '.'   IDENT { $$ = node_expr(&@$,  ".", $1, node_string(&@3, $3->expr.func), NULL); }
204 | | basic DEREF IDENT { $$ = node_expr(&@$, "->", $1, node_string(&@3, $3->expr.func), NULL); }
205 | /* | '&' unary { $$ = node_expr(&@$, "u&", $2, NULL); } */
206 | | '*' unary { $$ = node_expr(&@$, "u*", $2, NULL); }
207 | | '-' unary { $$ = node_expr(&@$, "u-", $2, NULL); }
208 | | '~' unary { $$ = node_expr(&@$, "u~", $2, NULL); }
209 | | '!' unary { $$ = node_expr(&@$, "u!", $2, NULL); }
210 | ;
211 | 
212 | basic
213 | : NUMBER
214 | | STRING
215 | | IDENT
216 | | AGG
217 | | '(' expr ')'	{ $$ = $2; }
218 | ;
219 | 
220 | func
221 | : IDENT '(' opt_exprs ')' { $$ = $3 ? node_expr_append(&@$, $1, $3) : $1; }
222 | ;
223 | 
224 | map
225 | : IDENT key { $$ = node_expr(&@$, "[]", $1, $2, NULL); }
226 | ;
227 | 
228 | aggregation
229 | : AGG key { $$ = node_expr(&@$, "[]", $1, $2, NULL); }
230 | ;
231 | 
232 | key
233 | : '[' exprs ']' { $$ = node_expr(&@$, ":struct", $2, NULL); }
234 | ;
235 | 
236 | %%
237 | 
238 | 


--------------------------------------------------------------------------------
/src/libply/lexer.l:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright Tobias Waldekranz 
 3 |  *
 4 |  * SPDX-License-Identifier: GPL-2.0
 5 |  */
 6 | 
 7 | %option reentrant noyywrap never-interactive nounistd
 8 | %option bison-locations
 9 | 
10 | %{
11 | 
12 | /* ignore harmless bug in old versions of flex */
13 | #pragma GCC diagnostic ignored "-Wsign-compare"
14 | 
15 | 
16 | #include 
17 | 
18 | #include "grammar.h"
19 | 
20 | static void loc_update(struct nloc *loc, const char *token) {
21 | 	loc->first_line = loc->last_line;
22 | 	loc->first_column = loc->last_column;
23 | 
24 | 	for (; *token; token++) {
25 | 		if (*token == '\n') {
26 | 			loc->last_line++;
27 | 			loc->last_column = 0;
28 | 			continue;
29 | 		}
30 | 
31 | 		loc->last_column++;
32 | 	}
33 | }
34 | 
35 | #define YY_USER_ACTION loc_update(yylloc, yytext);
36 | 
37 | %}
38 | 
39 | uaz		[_a-zA-Z]
40 | uazd		[_a-zA-Z0-9]
41 | 
42 | ident		{uaz}{uazd}*
43 | agg		@{uazd}*
44 | env		${uazd}+
45 | pspec		{ident}:[^ \n\r\t]*
46 | 
47 | %x COMMENT
48 | %%
49 | 
50 | "/"[ \n\r\t]*"{"	{ return ENDPRED; }
51 | "if"			{ return IF;      }
52 | "else"			{ return ELSE;    }
53 | "return"		{ return RETURN;  }
54 | "delete"		{ return DELETE;  }
55 | "&&"			{ return AND;     }
56 | "^^"			{ return XOR;     }
57 | "||"			{ return OR;      }
58 | "<<"			{ return LSH;     }
59 | ">>"			{ return RSH;     }
60 | "<="			{ return LE;      }
61 | ">="			{ return GE;      }
62 | "=="			{ return EQ;      }
63 | "!="			{ return NE;      }
64 | "->"			{ return DEREF;   }
65 | 
66 | "BEGIN"			{ *yylval = node_string(yylloc, strdup(yytext));       return PSPEC;  }
67 | "END"			{ *yylval = node_string(yylloc, strdup(yytext));       return PSPEC;  }
68 | 
69 | \"[^\0\"]*\"		{ *yylval = node_string(yylloc, strdup(yytext));       return STRING; }
70 | [_0-9]+			{ *yylval = node_num   (yylloc, strdup(yytext));       return NUMBER; }
71 | 0b[_01]+		{ *yylval = node_num   (yylloc, strdup(yytext));       return NUMBER; }
72 | 0[xX][_0-9a-fA-F]+	{ *yylval = node_num   (yylloc, strdup(yytext));       return NUMBER; }
73 | {ident}			{ *yylval = node_expr  (yylloc, strdup(yytext), NULL); return IDENT;  }
74 | {agg}			{ *yylval = node_expr_ident(yylloc, strdup(yytext));   return AGG;    }
75 | {env}			{ *yylval = node_expr_ident(yylloc, strdup(yytext));   return IDENT;  }
76 | {pspec}			{ *yylval = node_string(yylloc, strdup(yytext));       return PSPEC;  }
77 | 
78 | #.*\n			;
79 | "/*"			BEGIN(COMMENT);
80 | {
81 | "*/"            BEGIN(INITIAL);
82 | [^*\n]+         ; /* eat comment except '*' and newline characters */
83 | "*"             ; /* eat single '*' character */
84 | \n              yylineno++; /* increment line number counter */
85 | }
86 | 
87 | [=@$.,;+\-*/%<>&~\^|!()\[\]{}]	{ return *yytext; }
88 | 
89 | [ \n\r\t]		;
90 | 
91 | . { fprintf(stderr, "%d: error: unknown token\n", yylloc->first_line); yyterminate(); }
92 | 
93 | %%
94 | 


--------------------------------------------------------------------------------
/src/libply/node.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | #include 
 12 | #include 
 13 | #include 
 14 | 
 15 | #include 
 16 | #include "grammar.h"
 17 | #include "lexer.h"
 18 | 
 19 | void node_print(struct node *n, FILE *fp)
 20 | {
 21 | 	switch (n->ntype) {
 22 | 	case N_EXPR:
 23 | 		fprintf(fp, "\e[34m%s\e[0m", n->expr.func);
 24 | 		break;
 25 | 	case N_NUM:
 26 | 		if (n->num.unsignd)
 27 | 			fprintf(fp, "%"PRIu64, n->num.u64);
 28 | 		else
 29 | 			fprintf(fp, "%"PRId64, n->num.s64);
 30 | 		break;
 31 | 	case N_STRING:
 32 | 		fprintf(fp, "\"%s\"", n->string.data);
 33 | 		break;
 34 | 
 35 | 	default:
 36 | 		fputs("", fp);
 37 | 	}
 38 | }
 39 | 
 40 | static int node_nloc_valid(struct nloc *nloc)
 41 | {
 42 | 	return nloc->first_line | nloc->first_column |
 43 | 		nloc->last_line | nloc->last_column;
 44 | }
 45 | 
 46 | static void node_nloc_print(struct node *n, FILE *fp)
 47 | {
 48 | 	fputs("\e[1m", fp);
 49 | 
 50 | 	/* TODO: get real filename */
 51 | 	fprintf(fp, ":");
 52 | 
 53 | 	if (n->loc.first_line != n->loc.last_line)
 54 | 		fprintf(fp, "%d-%d:", n->loc.first_line, n->loc.last_line);
 55 | 	else
 56 | 		fprintf(fp, "%d:", n->loc.first_line);
 57 | 
 58 | 	if (n->loc.first_column != n->loc.last_column)
 59 | 		fprintf(fp, "%d-%d", n->loc.first_column, n->loc.last_column);
 60 | 	else
 61 | 		fprintf(fp, "%d", n->loc.first_column);
 62 | 
 63 | 	fputs("\e[0m", fp);
 64 | }
 65 | 
 66 | int node_vfprintxf(struct printxf *pxf, FILE *fp, const char *spec, va_list ap)
 67 | {
 68 | 	struct node *n;
 69 | 
 70 | 	n = va_arg(ap, struct node *);
 71 | 
 72 | 	if (strchr(spec, '#') && node_nloc_valid(&n->loc)) {
 73 | 		node_nloc_print(n, fp);
 74 | 		return 0;
 75 | 	}
 76 | 
 77 | 	node_print(n, fp);
 78 | 	return 0;
 79 | }
 80 | 
 81 | int node_walk(struct node *n,
 82 | 	      int (*pre)(struct node *, void *),
 83 | 	      int (*post)(struct node *, void *),
 84 | 	      void *ctx)
 85 | {
 86 | 	int sum = 0, ret = 0;
 87 | 	
 88 | 	if (pre) {
 89 | 		ret = pre(n, ctx);
 90 | 		if (ret < 0)
 91 | 			return ret;
 92 | 
 93 | 		sum += ret;
 94 | 	}
 95 | 
 96 | 	if (n->ntype == N_EXPR) {
 97 | 		struct node *arg;
 98 | 
 99 | 		node_expr_foreach(n, arg) {
100 | 			ret = node_walk(arg, pre, post, ctx);
101 | 			if (ret < 0)
102 | 				return ret;
103 | 
104 | 			sum += ret;
105 | 		}
106 | 	}
107 | 
108 | 	if (post) {
109 | 		ret = post(n, ctx);
110 | 		if (ret < 0)
111 | 			return ret;
112 | 
113 | 		sum += ret;
114 | 	}
115 | 
116 | 	return sum;
117 | }
118 | 
119 | int node_replace(struct node *n, struct node *new)
120 | {
121 | 	new->up = n->up;
122 | 
123 | 	if (n->prev) {
124 | 		new->prev = n->prev;
125 | 		n->prev->next = new;
126 | 	}
127 | 
128 | 	if (n->next) {
129 | 		new->next = n->next;
130 | 		n->next->prev = new;
131 | 	}
132 | 
133 | 	if (new->up
134 | 	    && (new->up->ntype == N_EXPR)
135 | 	    && (new->up->expr.args == n))
136 | 		new->up->expr.args = new;
137 | 
138 | 	/* TODO: don't leak memory */
139 | 	return 0;
140 | }
141 | 
142 | /* helpers */
143 | 
144 | int node_is(struct node *n, const char *func)
145 | {
146 | 	if (!n || (n->ntype != N_EXPR))
147 | 		return 0;
148 | 
149 | 	return !strcmp(n->expr.func, func);
150 | 
151 | }
152 | 
153 | 
154 | /* constructors */
155 | 
156 | static struct node *node_new(enum ntype ntype, const struct nloc *loc)
157 | {
158 | 	struct node *n;
159 | 
160 | 	n = xcalloc(1, sizeof(*n));
161 | 	n->ntype = ntype;
162 | 
163 | 	if (loc)
164 | 		n->loc = *loc;
165 | 	return n;
166 | }
167 | 
168 | void __string_escape(char *dst, const char *src)
169 | {
170 | 	while (*src) {
171 | 		if (*src == '\\' && *(src + 1)) {
172 | 			src++;
173 | 
174 | 			switch (*src) {
175 | 			case '\\': *dst++ = '\\'; break;
176 | 			case 'n': *dst++ = '\n'; break;
177 | 			case 'r': *dst++ = '\r'; break;
178 | 			case 't': *dst++ = '\t'; break;
179 | 			default: assert(!"TODO"); break;
180 | 			}
181 | 
182 | 			src++;
183 | 		} else {
184 | 			*dst++ = *src++;
185 | 		}
186 | 	}
187 | }
188 | 
189 | struct node *node_string(const struct nloc *loc, char *data)
190 | {
191 | 	struct node *n = node_new(N_STRING, loc);
192 | 	size_t len;
193 | 
194 | 	/* remove quotes */
195 | 	if (data[0] == '"') {
196 | 		char *unquoted;
197 | 
198 | 		len = strlen(data) - 2;
199 | 
200 | 		unquoted = xcalloc(1, len + 1);
201 | 		strncpy(unquoted, data + 1, len);
202 | 		free(data);
203 | 		data = unquoted;
204 | 	}
205 | 
206 | 	len = (strlen(data) + 8) & ~7;
207 | 	n->string.data = xcalloc(1, len);
208 | 	__string_escape(n->string.data, data);
209 | 	free(data);
210 | 	return n;
211 | }
212 | 
213 | struct node *__node_num(const struct nloc *loc, size_t size,
214 | 			int64_t *s64, uint64_t *u64)
215 | {
216 | 	struct node *n = node_new(N_NUM, loc);
217 | 
218 | 	if (s64) {
219 | 		n->num.s64 = *s64;
220 | 	} else {
221 | 		n->num.u64 = *u64;
222 | 		n->num.unsignd = 1;
223 | 	}
224 | 
225 | 	n->num.size = size;
226 | 	return n;
227 | }
228 | 
229 | struct node *node_num(const struct nloc *loc, const char *numstr)
230 | {
231 | 	uint64_t u64;
232 | 	int64_t s64;
233 | 	int ret;
234 | 
235 | 	ret = strtonum(numstr, &s64, &u64);
236 | 	assert(ret);
237 | 
238 | 	if (ret < 0)
239 | 		return __node_num(loc, 0, &s64, NULL);
240 | 
241 | 	return __node_num(loc, 0, NULL, &u64);
242 | }
243 | 
244 | void node_insert(struct node *prev, struct node *n)
245 | {
246 | 	n->up = prev->up;
247 | 
248 | 	n->prev = prev;
249 | 	n->next = prev->next;
250 | 	prev->next = n;
251 | }
252 | 
253 | struct node *node_append(struct node *head, struct node *tail)
254 | {
255 | 	struct node *last;
256 | 
257 | 	for (last = head; last->next; last = last->next);
258 | 
259 | 	last->next = tail;
260 | 	tail->prev = last;
261 | 	return head;
262 | 	
263 | }
264 | 
265 | struct node *node_expr_append(const struct nloc *loc,
266 | 			      struct node *n, struct node *arg)
267 | {
268 | 	struct node *last;
269 | 	assert(n->ntype == N_EXPR);
270 | 
271 | 	if (loc)
272 | 		n->loc = *loc;
273 | 
274 | 	arg->up = n;
275 | 
276 | 	if (!n->expr.args) {
277 | 		n->expr.args = arg;
278 | 		return n;
279 | 	}
280 | 
281 | 	node_append(n->expr.args, arg);
282 | 	return n;
283 | }
284 | 
285 | struct node *node_expr(const struct nloc *loc, char *func, ...)
286 | {
287 |         va_list ap;
288 | 	struct node *n, *arg;
289 | 
290 | 	n = node_new(N_EXPR, loc);
291 | 
292 | 	n->expr.func = func;
293 | 
294 |         va_start(ap, func);
295 | 
296 |         while ((arg = va_arg(ap, struct node *)))
297 | 		node_expr_append(NULL, n, arg);
298 | 
299 |         va_end(ap);
300 | 
301 | 	return n;
302 | 	
303 | }
304 | 
305 | struct node *node_expr_ident(const struct nloc *loc, char *func)
306 | {
307 | 	struct node *n = node_expr(loc, func, NULL);
308 | 
309 | 	n->expr.ident = 1;
310 | 	return n;
311 | }
312 | 
313 | __attribute__((constructor))
314 | static void node_init(void)
315 | {
316 | 	printxf_default.vfprintxf['N'] = node_vfprintxf;
317 | }
318 | 


--------------------------------------------------------------------------------
/src/libply/provider.c:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright Tobias Waldekranz 
 3 |  *
 4 |  * SPDX-License-Identifier: GPL-2.0
 5 |  */
 6 | 
 7 | #include 
 8 | #include 
 9 | #include 
10 | 
11 | #include 
12 | #include 
13 | 
14 | SLIST_HEAD(provider_list, provider);
15 | static struct provider_list heads = SLIST_HEAD_INITIALIZER(heads);
16 | 
17 | /* supported providers */
18 | extern struct provider kprobe;
19 | extern struct provider kretprobe;
20 | extern struct provider tracepoint;
21 | extern struct provider built_in;
22 | extern struct provider begin_provider;
23 | extern struct provider end_provider;
24 | extern struct provider interval;
25 | extern struct provider profile;
26 | 
27 | struct provider *provider_get(const char *name)
28 | {
29 | 	struct provider *p;
30 | 	char *search;
31 | 
32 | 	search = strtok(strdup(name), ":");
33 | 
34 | 	SLIST_FOREACH(p, &heads, entry) {
35 |  		if (strstr(p->name, search) == p->name)
36 | 			break;
37 | 	}
38 | 
39 | 	free(search);
40 | 	return p;
41 | }
42 | 
43 | void provider_init(void)
44 | {
45 | 	SLIST_INSERT_HEAD(&heads, &end_provider, entry);
46 | 	SLIST_INSERT_HEAD(&heads, &begin_provider, entry);
47 | 	SLIST_INSERT_HEAD(&heads, &built_in, entry);
48 | 	SLIST_INSERT_HEAD(&heads, &interval, entry);
49 | 	SLIST_INSERT_HEAD(&heads, &profile, entry);
50 | 	SLIST_INSERT_HEAD(&heads, &tracepoint, entry);
51 | 	SLIST_INSERT_HEAD(&heads, &kretprobe, entry);
52 | 	/* place kprobe at head so that 'k' can match first. */
53 | 	SLIST_INSERT_HEAD(&heads, &kprobe, entry);
54 | }
55 | 


--------------------------------------------------------------------------------
/src/libply/provider/interval.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright  Namhyung Kim 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | 
 12 | #include 
 13 | #include 
 14 | 
 15 | 
 16 | struct interval_data {
 17 | 	unsigned long long interval;
 18 | 	int fd;
 19 | };
 20 | 
 21 | static int interval_sym_alloc(struct ply_probe *pb, struct node *n)
 22 | {
 23 | 	return -ENOENT;
 24 | }
 25 | 
 26 | static int interval_probe(struct ply_probe *pb)
 27 | {
 28 | 	char *unit;
 29 | 	char *num;
 30 | 	size_t i;
 31 | 	unsigned long long intvl;
 32 | 	struct interval_data *data;
 33 | 	struct {
 34 | 		const char *str;
 35 | 		unsigned long long factor;
 36 | 	} interval_units[] = {
 37 | 		{ "m"  , 60 * 1e9 },
 38 | 		{ "s"  , 1e9 },
 39 | 		{ "ms" , 1e6 },
 40 | 		{ "us" , 1e3 },
 41 | 		{ "ns" , 1 },
 42 | 	};
 43 | 
 44 | 	num = strchr(pb->probe, ':');
 45 | 	if (num == NULL) {
 46 | 		_e("interval doesn't have unit: %s\n", pb->probe);
 47 | 		return -1;
 48 | 	}
 49 | 	num = strdup(num + 1);
 50 | 	if (num == NULL) {
 51 | 		_e("memory allocation failure\n");
 52 | 		return -1;
 53 | 	}
 54 | 	intvl = strtoull(num, &unit, 0);
 55 | 
 56 | 	if (unit == NULL || *unit == '\0')
 57 | 		unit = "s";
 58 | 
 59 | 	for (i = 0; i < ARRAY_SIZE(interval_units); i++) {
 60 | 		if (strcmp(unit, interval_units[i].str))
 61 | 			continue;
 62 | 
 63 | 		intvl *= interval_units[i].factor;
 64 | 		break;
 65 | 	}
 66 | 	free(num);
 67 | 
 68 | 	if (i == ARRAY_SIZE(interval_units)) {
 69 | 		_e("invalid time unit: %s\n", pb->probe);
 70 | 		return -1;
 71 | 	}
 72 | 
 73 | 	data = xcalloc(1, sizeof(*data));
 74 | 	data->interval = intvl;
 75 | 	data->fd = -1;
 76 | 
 77 | 	pb->provider_data = data;
 78 | 	return 0;
 79 | }
 80 | 
 81 | static int interval_attach(struct ply_probe *pb)
 82 | {
 83 | 	struct interval_data *data = pb->provider_data;
 84 | 
 85 | 	data->fd = perf_event_attach_raw(pb, PERF_TYPE_SOFTWARE,
 86 | 					 PERF_COUNT_SW_CPU_CLOCK,
 87 | 					 data->interval, 0);
 88 | 	if (data->fd < 0) {
 89 | 		_e("interval attach failed\n");
 90 | 		return data->fd;
 91 | 	}
 92 | 	return 0;
 93 | }
 94 | 
 95 | static int interval_detach(struct ply_probe *pb)
 96 | {
 97 | 	struct interval_data *data = pb->provider_data;
 98 | 
 99 | 	close(data->fd);
100 | 	data->fd = -1;
101 | 	return 0;
102 | }
103 | 
104 | struct provider interval = {
105 | 	.name = "interval",
106 | 	.prog_type = BPF_PROG_TYPE_PERF_EVENT,
107 | 
108 | 	.sym_alloc = interval_sym_alloc,
109 | 	.probe     = interval_probe,
110 | 
111 | 	.attach = interval_attach,
112 | 	.detach = interval_detach,
113 | };
114 | 


--------------------------------------------------------------------------------
/src/libply/provider/kprobe.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | #include 
 12 | 
 13 | #include 
 14 | 
 15 | #include 
 16 | #include 
 17 | 
 18 | #include "xprobe.h"
 19 | 
 20 | /* regs */
 21 | 
 22 | static int kprobe_regs_ir_post(const struct func *func, struct node *n,
 23 | 			       struct ply_probe *pb)
 24 | {
 25 | 	struct node *ctx = n->expr.args;
 26 | 
 27 | 	n->sym->irs = ctx->sym->irs;
 28 | 	return 0;
 29 | }
 30 | 
 31 | static int kprobe_regs_rewrite(const struct func *func, struct node *n,
 32 | 			       struct ply_probe *pb)
 33 | {
 34 | 	node_expr_append(&n->loc, n, node_expr(&n->loc, "ctx", NULL));
 35 | 	return 0;
 36 | }
 37 | 
 38 | static struct type t_pt_regsp = {
 39 | 	.ttype = T_POINTER,
 40 | 
 41 | 	.ptr = {
 42 | 		.type = &t_pt_regs,
 43 | 
 44 | 		/* 'regs' is a pointer, but the kernel verifier will
 45 | 		 * mark 32-bit accesses as invalid even on 32-bit
 46 | 		 * ISAs, so we always treat it as a 64-bit pointer */
 47 | 		 .bpf = 1,
 48 | 	},
 49 | };
 50 | 
 51 | const struct func kprobe_regs_func = {
 52 | 	.name = "regs",
 53 | 	.type = &t_pt_regsp,
 54 | 	.static_ret = 1,
 55 | 
 56 | 	.rewrite = kprobe_regs_rewrite,
 57 | 	.ir_post = kprobe_regs_ir_post,
 58 | };
 59 | 
 60 | /* caller */
 61 | 
 62 | static int caller_fprint(struct type *t, FILE *fp, const void *data)
 63 | {
 64 | 	struct ksyms *ks = t->priv;
 65 | 	uintptr_t addr;
 66 | 
 67 | 	addr = *((uintptr_t *)data);
 68 | 
 69 | 	return ksym_fprint(ks, fp, addr);
 70 | }
 71 | 
 72 | static struct type t_caller_t = {
 73 | 	.ttype = T_TYPEDEF,
 74 | 
 75 | 	.tdef = {
 76 | 		.name = "caller_t",
 77 | 		.type = &t_reg_t,
 78 | 	},
 79 | 
 80 | 	.fprint = caller_fprint,
 81 | };
 82 | 
 83 | static int kprobe_caller_rewrite(const struct func *func, struct node *n,
 84 | 				 struct ply_probe *pb)
 85 | {
 86 | 	struct node *new;
 87 | 	const char *reg;
 88 | 
 89 | 	if (n->expr.args)
 90 | 		return 0;
 91 | 
 92 | 	n->sym->type->priv = pb->ply->ksyms;
 93 | 
 94 | 	reg = arch_register_pc();
 95 | 
 96 | 	/* argN => (*regs).REG */
 97 | 	new = node_expr(&n->loc, ".",
 98 | 			node_expr(&n->loc, "u*", node_expr_ident(&n->loc, "regs"), NULL),
 99 | 			node_string(&n->loc, strdup(reg)),
100 | 			NULL);
101 | 
102 | 	node_expr_append(&n->loc, n, new);
103 | 	return 1;
104 | }
105 | 
106 | static const struct func kprobe_caller_func = {
107 | 	.name = "caller",
108 | 
109 | 	/* for now, in the future we could read dwarf symbols to
110 | 	 * figure out the real type. */
111 | 	.type = &t_caller_t,
112 | 	.static_ret = 1,
113 | 
114 | 	.rewrite = kprobe_caller_rewrite,
115 | 	.ir_post = func_pass_ir_post,
116 | };
117 | 
118 | 
119 | /* argN */
120 | 
121 | static inline int is_arg(const char *name)
122 | {
123 | 	return (strstr(name, "arg") == name)
124 | 		&& (strlen(name) == 4)
125 | 		&& (name[3] >= '0' && name[3] <= '9');
126 | }
127 | 
128 | static int kprobe_arg_rewrite(const struct func *func, struct node *n,
129 | 			      struct ply_probe *pb)
130 | {
131 | 	struct node *new;
132 | 	const char *reg;
133 | 	int arg;
134 | 
135 | 	arg = n->expr.func[3] - '0';
136 | 	reg = arch_register_argument(arg);
137 | 	if (!reg) {
138 | 		_e("%#N: the location of %N is unknown\n", n, n);
139 | 
140 | 		/* TODO: add ABI mappings for specifying arguments
141 | 		 * passed on the stack. */
142 | 		return -EINVAL;
143 | 	}
144 | 
145 | 	/* argN => (*regs).REG */
146 | 	new = node_expr(&n->loc, ".",
147 | 			node_expr(&n->loc, "u*", node_expr_ident(&n->loc, "regs"), NULL),
148 | 			node_string(&n->loc, strdup(reg)),
149 | 			NULL);
150 | 
151 | 	node_replace(n, new);
152 | 	return 1;
153 | }
154 | 
155 | static const struct func kprobe_arg_func = {
156 | 	.name = "argN",
157 | 
158 | 	/* for now, in the future we could read dwarf symbols to
159 | 	 * figure out the real type. */
160 | 	.type = &t_ulong,
161 | 	.static_ret = 1,
162 | 
163 | 	.rewrite = kprobe_arg_rewrite,
164 | };
165 | 
166 | 
167 | static int kprobe_sym_alloc(struct ply_probe *pb, struct node *n)
168 | {
169 | 	const struct func *func = NULL;
170 | 	int err;
171 | 
172 | 	switch (n->ntype) {
173 | 	case N_EXPR:
174 | 		if (!strcmp(n->expr.func, "regs")) {
175 | 			func = &kprobe_regs_func;
176 | 			n->expr.ident = 1;
177 | 		} else if (!strcmp(n->expr.func, "caller")) {
178 | 			func = &kprobe_caller_func;
179 | 			n->expr.ident = 1;
180 | 		} else if (is_arg(n->expr.func)) {
181 | 			func = &kprobe_arg_func;
182 | 		}
183 | 		break;
184 | 	default:
185 | 		break;
186 | 	}
187 | 
188 | 	if (!func)
189 | 		return -ENOENT;
190 | 
191 | 	err = func_static_validate(func, n);
192 | 	if (err)
193 | 		return err;
194 | 
195 | 	n->sym = sym_alloc(&pb->locals, n, func);
196 | 
197 | 	if (func->static_ret)
198 | 		n->sym->type = func_return_type(func);
199 | 	return 0;
200 | }
201 | 
202 | 
203 | 
204 | static int kprobe_probe(struct ply_probe *pb)
205 | {
206 | 	struct xprobe *xp;
207 | 
208 | 	xp = xcalloc(1, sizeof(*xp));
209 | 	xp->type = 'p';
210 | 	xp->ctrl_name = "kprobe_events";
211 | 	xp->pattern = strchr(pb->probe, ':');
212 | 	assert(xp->pattern);
213 | 	xp->pattern++;
214 | 
215 | 	pb->provider_data = xp;
216 | 	return 0;
217 | }
218 | 
219 | struct provider kprobe = {
220 | 	.name = "kprobe",
221 | 	.prog_type = BPF_PROG_TYPE_KPROBE,
222 | 
223 | 	.sym_alloc = kprobe_sym_alloc,
224 | 	.probe     = kprobe_probe,
225 | 
226 | 	.attach = xprobe_attach,
227 | 	.detach = xprobe_detach,
228 | };
229 | 


--------------------------------------------------------------------------------
/src/libply/provider/kprobe.h:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright Tobias Waldekranz 
 3 |  *
 4 |  * SPDX-License-Identifier: GPL-2.0
 5 |  */
 6 | 
 7 | #ifndef _PLY_PROVIDER_KPROBE_H
 8 | #define _PLY_PROVIDER_KPROBE_H
 9 | 
10 | extern const struct func kprobe_regs_func;
11 | 
12 | int kprobe_ir_pre(struct ply_probe *pb);
13 | 
14 | #endif	/* _PLY_PROVIDER_KPROBE_H */
15 | 


--------------------------------------------------------------------------------
/src/libply/provider/kretprobe.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | #include 
 12 | 
 13 | #include 
 14 | 
 15 | #include 
 16 | #include 
 17 | 
 18 | #include "xprobe.h"
 19 | #include "kprobe.h"
 20 | 
 21 | 
 22 | /* retval */
 23 | 
 24 | static int kretprobe_retval_rewrite(const struct func *func, struct node *n,
 25 | 				    struct ply_probe *pb)
 26 | {
 27 | 	struct node *new;
 28 | 	const char *reg;
 29 | 
 30 | 	if (n->expr.args)
 31 | 		return 0;
 32 | 
 33 | 	reg = arch_register_return();
 34 | 
 35 | 	/* retval => (*regs).REG */
 36 | 	new = node_expr(&n->loc, ".",
 37 | 			node_expr(&n->loc, "u*", node_expr_ident(&n->loc, "regs"), NULL),
 38 | 			node_string(&n->loc, strdup(reg)),
 39 | 			NULL);
 40 | 
 41 | 	node_expr_append(&n->loc, n, new);
 42 | 	return 1;
 43 | }
 44 | 
 45 | static const struct func kretprobe_retval_func = {
 46 | 	.name = "retval",
 47 | 
 48 | 	/* for now, in the future we could read dwarf symbols to
 49 | 	 * figure out the real type. */
 50 | 	.type = &t_long,
 51 | 	.static_ret = 1,
 52 | 
 53 | 	.rewrite = kretprobe_retval_rewrite,
 54 | 	.ir_post = func_pass_ir_post,
 55 | };
 56 | 
 57 | 
 58 | static int kretprobe_sym_alloc(struct ply_probe *pb, struct node *n)
 59 | {
 60 | 	const struct func *func = NULL;
 61 | 	int err;
 62 | 
 63 | 	switch (n->ntype) {
 64 | 	case N_EXPR:
 65 | 		if (!strcmp(n->expr.func, "regs")) {
 66 | 			func = &kprobe_regs_func;
 67 | 			n->expr.ident = 1;
 68 | 		} else if (!strcmp(n->expr.func, "retval")) {
 69 | 			func = &kretprobe_retval_func;
 70 | 			n->expr.ident = 1;
 71 | 		}
 72 | 		break;
 73 | 	default:
 74 | 		break;
 75 | 	}
 76 | 
 77 | 	if (!func)
 78 | 		return -ENOENT;
 79 | 
 80 | 	err = func_static_validate(func, n);
 81 | 	if (err)
 82 | 		return err;
 83 | 
 84 | 	n->sym = sym_alloc(&pb->locals, n, func);
 85 | 
 86 | 	if (func->static_ret)
 87 | 		n->sym->type = func_return_type(func);
 88 | 	return 0;
 89 | }
 90 | 
 91 | 
 92 | 
 93 | static int kretprobe_probe(struct ply_probe *pb)
 94 | {
 95 | 	struct xprobe *xp;
 96 | 
 97 | 	xp = xcalloc(1, sizeof(*xp));
 98 | 	xp->type = 'r';
 99 | 	xp->ctrl_name = "kprobe_events";
100 | 	xp->pattern = strchr(pb->probe, ':');
101 | 	assert(xp->pattern);
102 | 	xp->pattern++;
103 | 
104 | 	pb->provider_data = xp;
105 | 	return 0;
106 | }
107 | 
108 | struct provider kretprobe = {
109 | 	.name = "kretprobe",
110 | 	.prog_type = BPF_PROG_TYPE_KPROBE,
111 | 
112 | 	.sym_alloc = kretprobe_sym_alloc,
113 | 	.probe     = kretprobe_probe,
114 | 
115 | 	.attach = xprobe_attach,
116 | 	.detach = xprobe_detach,
117 | };
118 | 


--------------------------------------------------------------------------------
/src/libply/provider/profile.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright  Ism Hong 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | 
 12 | #include 
 13 | #include 
 14 | 
 15 | 
 16 | struct profile_data {
 17 | 	int cpu;
 18 | 	int ncpus;
 19 | 	unsigned long long freq;
 20 | 	int *evfds;
 21 | };
 22 | 
 23 | static int profile_sym_alloc(struct ply_probe *pb, struct node *n)
 24 | {
 25 | 	return -ENOENT;
 26 | }
 27 | 
 28 | static int profile_probe(struct ply_probe *pb)
 29 | {
 30 | 	int cpu = -1, ncpus = 0;
 31 | 	struct profile_data *data;
 32 | 	int freq = -1;
 33 | 
 34 | 	/*
 35 | 	 * Expected format is either profile:[n]hz where n is a number between
 36 | 	 * 1 and 1000, or profile:[c]:[n]hz where c is the CPU to profile.
 37 | 	 */
 38 | 	if (sscanf(pb->probe, "profile:%d:%dhz", &cpu, &freq) != 2) {
 39 | 		cpu = -1;
 40 | 		if (sscanf(pb->probe, "profile:%dhz", &freq) != 1)
 41 | 			return -EINVAL;
 42 | 	}
 43 | 
 44 | 	if (freq < 0 || freq > 1000)
 45 | 		return -EINVAL;
 46 | 
 47 | 	ncpus = sysconf(_SC_NPROCESSORS_ONLN);
 48 | 
 49 | 	if (cpu < -1 || cpu > ncpus)
 50 | 		return -EINVAL;
 51 | 
 52 | 	if (cpu >= 0)
 53 | 		ncpus = 1;
 54 | 
 55 | 	data = calloc(1, sizeof(*data));
 56 | 	if (!data)
 57 | 		return -ENOMEM;
 58 | 
 59 | 	data->evfds = calloc(ncpus, sizeof (int));
 60 | 	if (!data->evfds) {
 61 | 		free(data);
 62 | 		return -ENOMEM;
 63 | 	}
 64 | 
 65 | 	data->freq = (unsigned long long)freq;
 66 | 	data->cpu = cpu;
 67 | 	data->ncpus = ncpus ;
 68 | 
 69 | 	pb->provider_data = data;
 70 | 	return 0;
 71 | }
 72 | 
 73 | static int profile_attach(struct ply_probe *pb)
 74 | {
 75 | 	struct profile_data *data = pb->provider_data;
 76 | 	int cpu;
 77 | 
 78 | 	if (data->cpu != -1) {
 79 | 		data->evfds[0] = perf_event_attach_profile(pb, data->cpu,
 80 | 						 data->freq);
 81 | 		if (data->evfds[0] < 0) {
 82 | 			_e("%s: Unable to attach profile probe: %s\n",
 83 | 			   pb->probe, strerror(errno));
 84 | 			return data->evfds[0];
 85 | 		}
 86 | 	} else {
 87 | 		for (cpu = 0; cpu < data->ncpus; cpu++) {
 88 | 			data->evfds[cpu] = perf_event_attach_profile(pb, cpu, data->freq);
 89 | 			if (data->evfds[cpu] < 0) {
 90 | 				_e("%s: Unable to attach profile probe: %s\n",
 91 | 						pb->probe, strerror(errno));
 92 | 				return data->evfds[cpu];
 93 | 			}
 94 | 		}
 95 | 	}
 96 | 
 97 | 	return 0;
 98 | }
 99 | 
100 | static int profile_detach(struct ply_probe *pb)
101 | {
102 | 	struct profile_data *data = pb->provider_data;
103 | 
104 | 	for (int i = 0; i < data->ncpus; i++) {
105 | 		if (data->evfds[i] > 0)
106 | 			close(data->evfds[i]);
107 | 	}
108 | 	free(data->evfds);
109 | 	free(data);
110 | 
111 | 	return 0;
112 | }
113 | 
114 | struct provider profile = {
115 | 	.name = "profile",
116 | 	.prog_type = BPF_PROG_TYPE_PERF_EVENT,
117 | 
118 | 	.sym_alloc = profile_sym_alloc,
119 | 	.probe 	   = profile_probe,
120 | 
121 | 	.attach = profile_attach,
122 | 	.detach = profile_detach,
123 | };
124 | 


--------------------------------------------------------------------------------
/src/libply/provider/special.c:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright  Namhyung Kim 
 3 |  *
 4 |  * SPDX-License-Identifier: GPL-2.0
 5 |  */
 6 | 
 7 | #include 
 8 | 
 9 | #include 
10 | #include 
11 | 
12 | static int special_sym_alloc(struct ply_probe *pb, struct node *n)
13 | {
14 | 	return -ENOENT;
15 | }
16 | 
17 | static int special_probe(struct ply_probe *pb)
18 | {
19 | 	pb->special = 1;
20 | 	return 0;
21 | }
22 | 
23 | struct provider begin_provider = {
24 | 	.name = "BEGIN",
25 | 	.prog_type = BPF_PROG_TYPE_RAW_TRACEPOINT,
26 | 
27 | 	.probe     = special_probe,
28 | 	.sym_alloc = special_sym_alloc,
29 | };
30 | 
31 | struct provider end_provider = {
32 | 	.name = "END",
33 | 	.prog_type = BPF_PROG_TYPE_RAW_TRACEPOINT,
34 | 
35 | 	.probe     = special_probe,
36 | 	.sym_alloc = special_sym_alloc,
37 | };
38 | 


--------------------------------------------------------------------------------
/src/libply/provider/xprobe.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | #include 
 12 | #include 
 13 | 
 14 | #include 
 15 | #include 
 16 | 
 17 | #include "xprobe.h"
 18 | 
 19 | #ifdef FNM_EXTMATCH
 20 | /* Support extended matching if we're on glibc. */
 21 | #  define PLY_FNM_FLAGS FNM_EXTMATCH
 22 | #else
 23 | #  define PLY_FNM_FLAGS 0
 24 | #endif
 25 | 
 26 | static int xprobe_stem(struct ply_probe *pb, char type, char *stem, size_t size)
 27 | {
 28 | 	return snprintf(stem, size, "%c:%s/p%"PRIxPTR"_",
 29 | 			type, pb->ply->group, (uintptr_t)pb);
 30 | }
 31 | 
 32 | static int __xprobe_create(FILE *ctrl, const char *stem, const char *func)
 33 | {
 34 | 	char *funcname;
 35 | 	char *offs;
 36 | 
 37 | 	if (strchr(func, '/'))
 38 | 		funcname = strdup(strrchr(func, '/') + 1);
 39 | 	else
 40 | 		funcname = strdup(func);
 41 | 	assert(funcname);
 42 | 
 43 | 	while (1) {
 44 | 		offs = strpbrk(funcname, "+-:;~!@#$%^&*()[]{}<>|?=., ");
 45 | 		if (!offs)
 46 | 			break;
 47 | 
 48 | 		*offs = '_';
 49 | 	}
 50 | 
 51 | 	fputs(stem,     ctrl);
 52 | 	fputs(funcname, ctrl);
 53 | 	fputc( ' ',     ctrl);
 54 | 	fputs(func,     ctrl);
 55 | 	fputc('\n',     ctrl);
 56 | 	_d("writing xprobe: %s%s %s\n", stem, funcname, func);
 57 | 
 58 | 	free(funcname);
 59 | 	return strlen(stem) + 2 * strlen(func) + 2;
 60 | }
 61 | 
 62 | static int xprobe_glob(struct ply_probe *pb, glob_t *gl)
 63 | {
 64 | 	char *evglob;
 65 | 	int err;
 66 | 
 67 | 	asprintf(&evglob, TRACEPATH "events/%s/p%"PRIxPTR"_*",
 68 | 		 pb->ply->group, (uintptr_t)pb);
 69 | 
 70 | 	err = glob(evglob, 0, NULL, gl);
 71 | 	free(evglob);
 72 | 
 73 | 	if (!err)
 74 | 		return 0;
 75 | 
 76 | 	return err == GLOB_NOMATCH ? -ENOENT : EINVAL;
 77 | }
 78 | 
 79 | static char *xprobe_func(struct ply_probe *pb, char *path)
 80 | {
 81 | 	char *slash;
 82 | 
 83 | 	path += strlen(TRACEPATH "events/");
 84 | 	path += strlen(pb->ply->group);
 85 | 
 86 | 	slash = strchr(path, '/');
 87 | 	assert(slash);
 88 | 	*slash = '\0';
 89 | 	return path;
 90 | }
 91 | 
 92 | 
 93 | int xprobe_detach(struct ply_probe *pb)
 94 | {
 95 | 	struct xprobe *xp = pb->provider_data;
 96 | 	glob_t gl;
 97 | 	size_t i, evstart;
 98 | 	int err, pending;
 99 | 
100 | 	if (!xp->ctrl)
101 | 		return 0;
102 | 
103 | 	for (i = 0; i < xp->n_evs; i++)
104 | 		close(xp->evfds[i]);
105 | 
106 | 	err = xprobe_glob(pb, &gl);
107 | 	if (err)
108 | 		return err;
109 | 
110 | 	assert(gl.gl_pathc == xp->n_evs);
111 | 
112 | 	evstart = strlen(TRACEPATH "events/");
113 | 	pending = 0;
114 | 
115 | 	for (i = 0; i < gl.gl_pathc; i++) {
116 | 		fputs("-:", xp->ctrl);
117 | 		pending += 2;
118 | 		fputs(&gl.gl_pathv[i][evstart], xp->ctrl);
119 | 		pending += strlen(&gl.gl_pathv[i][evstart]);
120 | 		fputc('\n', xp->ctrl);
121 | 		_d("writing xprobe: -:%s\n", &gl.gl_pathv[i][evstart]);
122 | 		pending++;
123 | 
124 | 		/* The kernel parser doesn't deal with a probe definition
125 | 		 * being split across two writes. So if there's less than
126 | 		 * 512 bytes left, flush the buffer. */
127 | 		if (pending > (0x1000 - 0x200)) {
128 | 			err = fflush(xp->ctrl);
129 | 			if (err)
130 | 				break;
131 | 
132 | 			pending = 0;
133 | 		}
134 | 	}
135 | 
136 | 	globfree(&gl);
137 | 	fclose(xp->ctrl);
138 | 	return err;
139 | }
140 | 
141 | 
142 | static int xprobe_create_pattern(struct ply_probe *pb)
143 | {
144 | 	struct xprobe *xp = pb->provider_data;
145 | 	struct ksym *sym;
146 | 	int err, init = 0, pending = 0;
147 | 
148 | 	ksyms_foreach(sym, pb->ply->ksyms) {
149 | 		if (!strcmp(sym->sym, "_sinittext"))
150 | 			init++;
151 | 		if (!strcmp(sym->sym, "_einittext"))
152 | 			init--;
153 | 
154 | 		/* Ignore all functions in the init segment. They are
155 | 		 * not tracable. */
156 | 		if (init)
157 | 			continue;
158 | 
159 | 		/* Ignore GCC-internal symbols. */
160 | 		if (strchr(sym->sym, '.'))
161 | 			continue;
162 | 
163 | 		if (fnmatch(xp->pattern, sym->sym, PLY_FNM_FLAGS))
164 | 			continue;
165 | 
166 | 		pending += __xprobe_create(xp->ctrl, xp->stem, sym->sym);
167 | 		xp->n_evs++;
168 | 
169 | 		/* The kernel parser doesn't deal with a probe definition
170 | 		 * being split across two writes. So if there's less than
171 | 		 * 512 bytes left, flush the buffer. */
172 | 		if (pending > (0x1000 - 0x200)) {
173 | 			err = fflush(xp->ctrl);
174 | 			if (err) {
175 | 				_e("%s: Unable to create xprobe: %s\n",
176 | 				   sym->sym, strerror(errno));
177 | 				return -errno;
178 | 			}
179 | 
180 | 			pending = 0;
181 | 		}
182 | 	}
183 | 
184 | 	return 0;
185 | }	
186 | 
187 | static int xprobe_create(struct ply_probe *pb)
188 | {
189 | 	struct xprobe *xp = pb->provider_data;
190 | 	int err = 0;
191 | 
192 | 	xprobe_stem(pb, xp->type, xp->stem, sizeof(xp->stem));
193 | 
194 | 	if (strpbrk(xp->pattern, "?*[!@") && pb->ply->ksyms) {
195 | 		err = xprobe_create_pattern(pb);
196 | 	} else {
197 | 		__xprobe_create(xp->ctrl, xp->stem, xp->pattern);
198 | 		xp->n_evs++;
199 | 	}
200 | 
201 | 	if (!err) {
202 | 		err = fflush(xp->ctrl) ? -errno : 0;
203 | 		if (err) {
204 | 			_e("%s: Unable to create xprobe: %s\n",
205 | 			   pb->probe, strerror(errno));
206 | 		}
207 | 	}
208 | 	return err;
209 | }
210 | 
211 | static int __xprobe_attach(struct ply_probe *pb)
212 | {
213 | 	struct xprobe *xp = pb->provider_data;
214 | 	glob_t gl;
215 | 	int err, i;
216 | 
217 | 	err = xprobe_glob(pb, &gl);
218 | 	if (err)
219 | 		return err;
220 | 
221 | 	if (gl.gl_pathc != xp->n_evs) {
222 | 		_d("n:%d c:%d\n", xp->n_evs, gl.gl_pathc);
223 | 		pause();
224 | 	}
225 | 	
226 | 	assert(gl.gl_pathc == xp->n_evs);
227 | 	for (i = 0; i < (int)gl.gl_pathc; i++) {
228 | 		xp->evfds[i] = perf_event_attach(pb, gl.gl_pathv[i],
229 | 						 pb->special);
230 | 		if (xp->evfds[i] < 0) {
231 | 			err = xp->evfds[i];
232 | 			_e("%s: Unable to attach xprobe: %s\n",
233 | 			   pb->probe, strerror(errno));
234 | 			break;
235 | 		}
236 | 	}
237 | 
238 | 	globfree(&gl);
239 | 	return err;
240 | }
241 | 
242 | int xprobe_attach(struct ply_probe *pb)
243 | {
244 | 	struct xprobe *xp = pb->provider_data;
245 | 	char *func;
246 | 	int err;
247 | 
248 | 	/* TODO: mode should be a+ and we should clean this up on
249 | 	 * detach. */
250 | 	xp->ctrl = fopenf("a+", TRACEPATH "%s", xp->ctrl_name);
251 | 	if (!xp->ctrl)
252 | 		return -errno;
253 | 
254 | 	err = setvbuf(xp->ctrl, NULL, _IOFBF, 0x1000);
255 | 	if (err) {
256 | 		err = -errno;
257 | 		goto err_close;
258 | 	}
259 | 
260 | 	err = xprobe_create(pb);
261 | 	if (err)
262 | 		goto err_close;
263 | 
264 | 	xp->evfds = xcalloc(xp->n_evs, sizeof(xp->evfds));
265 | 
266 | 	err = __xprobe_attach(pb);
267 | 	if (err)
268 | 		goto err_destroy;
269 | 
270 | 	return 0;
271 | 
272 | err_destroy:
273 | 	/* xprobe_destroy(xp); */
274 | 
275 | err_close:
276 | 	fclose(xp->ctrl);
277 | 	return err;
278 | }
279 | 


--------------------------------------------------------------------------------
/src/libply/provider/xprobe.h:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright Tobias Waldekranz 
 3 |  *
 4 |  * SPDX-License-Identifier: GPL-2.0
 5 |  */
 6 | 
 7 | #ifndef _PLY_PROVIDER_XPROBE_H
 8 | #define _PLY_PROVIDER_XPROBE_H
 9 | 
10 | struct xprobe {
11 | 	FILE *ctrl;
12 | 	const char *ctrl_name;
13 | 
14 | 	char *pattern;
15 | 	char stem[0x40];
16 | 
17 | 	size_t n_evs;
18 | 	int *evfds;
19 | 
20 | 	char type;
21 | };
22 | 
23 | int xprobe_detach(struct ply_probe *pb);
24 | int xprobe_attach(struct ply_probe *pb);
25 | 
26 | #endif	/* _PLY_PROVIDER_XPROBE_H */
27 | 


--------------------------------------------------------------------------------
/src/libply/sym.c:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright Tobias Waldekranz 
 3 |  *
 4 |  * SPDX-License-Identifier: GPL-2.0
 5 |  */
 6 | 
 7 | #include 
 8 | #include 
 9 | #include 
10 | #include 
11 | #include 
12 | 
13 | #include 
14 | 
15 | struct sym *__sym_alloc(struct symtab *st, const char *name,
16 | 			const struct func *func)
17 | {
18 | 	struct sym *sym;
19 | 
20 | 	st->syms = realloc(st->syms, ++st->len * sizeof(*st->syms));
21 | 	assert(st->syms);
22 | 
23 | 	st->syms[st->len - 1] = xcalloc(1, sizeof(struct sym));
24 | 	sym = st->syms[st->len - 1];
25 | 	sym->st    = st;
26 | 	sym->name  = name;
27 | 	sym->func  = func;
28 | 	sym->mapfd = -1;
29 | 	return sym;
30 | }
31 | 
32 | static struct sym *sym_alloc_ident(struct symtab *st, struct node *n,
33 | 				   const struct func *func)
34 | {
35 | 	struct sym **sym;
36 | 
37 | 	symtab_foreach(st, sym) {
38 | 		if ((*sym)->name && !strcmp((*sym)->name, n->expr.func))
39 | 			return *sym;
40 | 	}
41 | 
42 | 	return __sym_alloc(st, n->expr.func, func);
43 | }
44 | 
45 | struct sym *sym_alloc(struct symtab *st, struct node *n,
46 | 		      const struct func *func)
47 | {
48 | 	if ((n->ntype == N_EXPR) && n->expr.ident)
49 | 		return sym_alloc_ident(st, n, func);
50 | 
51 | 	return __sym_alloc(st, NULL, func);
52 | }
53 | 
54 | void sym_dump(struct sym *sym, FILE *fp)
55 | {
56 | 	type_dump(sym->type, sym->name, fp);
57 | }
58 | 
59 | void symtab_dump(struct symtab *st, FILE *fp)
60 | {
61 | 	struct sym **sym;
62 | 
63 | 	symtab_foreach(st, sym) {
64 | 		if (!(*sym)->name)
65 | 			continue;
66 | 
67 | 		sym_dump(*sym, fp);
68 | 		fputc('\n', fp);
69 | 	}
70 | }
71 | 


--------------------------------------------------------------------------------
/src/ply/.gitignore:
--------------------------------------------------------------------------------
1 | ply
2 | self-test.bytes
3 | 


--------------------------------------------------------------------------------
/src/ply/Makefile.am:
--------------------------------------------------------------------------------
 1 | sbin_PROGRAMS = ply
 2 | 
 3 | ply_CPPFLAGS = -include $(top_builddir)/config.h -I $(top_srcdir)/include
 4 | ply_LDADD    = ../libply/libply.la
 5 | ply_SOURCES  = ply.c self-test.sh
 6 | 
 7 | BUILT_SOURCES = self-test.bytes
 8 | 
 9 | self-test.bytes: self-test.sh
10 | 	od -A n -t u1 <$<  | sed -e 's/\([0-9]\+\)/\1,/g' >$@
11 | 


--------------------------------------------------------------------------------
/src/ply/ply-gdb.py:
--------------------------------------------------------------------------------
  1 | import gdb
  2 | 
  3 | class FuncPrinter(object):
  4 |     def __init__(self, val, ptr):
  5 |         self.val, self.ptr = val, ptr
  6 | 
  7 |     def to_string(self):
  8 |         return self.ptr + "plyF(" + self.val["name"].string() + ")"
  9 | 
 10 |     def display_hint(self):
 11 |         return "func"
 12 | 
 13 | def nodeStr(n, stop=False):
 14 |     out = ""
 15 | 
 16 |     if str(n["ntype"]) == "N_EXPR":
 17 |         out += n["expr"]["func"].string()
 18 | 
 19 |         if not stop:
 20 |             arg = n["expr"]["args"]
 21 |             while arg != 0:
 22 |                 out += " " + nodeStr(arg, stop=True)
 23 |                 arg = arg["next"]
 24 |     elif str(n["ntype"]) == "N_STRING":
 25 |         out += "\"" + n["string"]["data"].string() + "\""
 26 |     elif str(n["ntype"]) == "N_NUM":
 27 |         if n["num"]["unsignd"]:
 28 |             out += "<" + str(n["num"]["u64"]) + ">"
 29 |         else:
 30 |             out += "<" + str(n["num"]["s64"]) + ">"
 31 |     else:
 32 |         out += "???"
 33 | 
 34 |     return out
 35 |     
 36 | 
 37 | class NodePrinter(object):
 38 |     def __init__(self, val, ptr):
 39 |         self.val, self.ptr = val, ptr
 40 | 
 41 |     def to_string(self):
 42 |         return self.ptr + "plyN(" + nodeStr(self.val) + ")"
 43 | 
 44 |     def display_hint(self):
 45 |         return "node"
 46 | 
 47 | class SymPrinter(object):
 48 |     def __init__(self, val, ptr):
 49 |         self.val, self.ptr = val, ptr
 50 | 
 51 |     def to_string(self):
 52 |         if self.val["name"]:
 53 |             name = self.val["name"].string()
 54 |         else:
 55 |             name = ""
 56 | 
 57 |         return self.ptr + "plyS(" + name + ")"
 58 | 
 59 |     def display_hint(self):
 60 |         return "sym"
 61 | 
 62 | 
 63 | ttypeStrs = {
 64 |     "T_VOID": lambda t: "void",
 65 |     "T_TYPEDEF": lambda t: t["tdef"]["name"].string(),
 66 |     "T_SCALAR": lambda t: t["scalar"]["name"].string(),
 67 |     "T_POINTER": lambda t: "*" + typeStr(t["ptr"]["type"].dereference()),
 68 |     "T_ARRAY": lambda t: typeStr(t["array"]["type"].dereference()) +
 69 |         "[" + str(t["array"]["len"]) + "]",
 70 |     "T_STRUCT": lambda t: "struct " + t["struct"]["name"].string(),
 71 |     "T_FUNC": lambda t: typeStr(t["func"]["type"].dereference()) + " (*)()",
 72 |     "T_MAP": lambda t: typeStr(t["func"]["vtype"].dereference()) +
 73 |         "{" + typeStr(t["func"]["vtype"].dereference()) + "}",
 74 | }
 75 | 
 76 | def typeStr(t):
 77 |     ttype = str(t["ttype"])
 78 | 
 79 |     if ttype in ttypeStrs:
 80 |         return ttypeStrs[ttype](t)
 81 | 
 82 |     return "???"
 83 | 
 84 | class TypePrinter(object):
 85 |     def __init__(self, val, ptr):
 86 |         self.val, self.ptr = val, ptr
 87 | 
 88 |     def to_string(self):
 89 |         return self.ptr + "plyT(" + typeStr(self.val) + ")"
 90 | 
 91 |     def display_hint(self):
 92 |         return "type"
 93 | 
 94 | plyPrinters = {
 95 |     "func": FuncPrinter,
 96 |     "node": NodePrinter,
 97 |     "sym" : SymPrinter,
 98 |     "type": TypePrinter,
 99 | }
100 | 
101 | def plyPrinterGet(v):
102 |     ptr = ""
103 |     if v.type.code == gdb.TYPE_CODE_PTR:
104 |         try:
105 |             v = v.dereference()
106 |         except gdb.error:
107 |             return None
108 | 
109 |         ptr = "*"
110 | 
111 |     if v.type.code != gdb.TYPE_CODE_STRUCT:
112 |         return None
113 | 
114 |     if v.type.tag in plyPrinters:
115 |         return plyPrinters[v.type.tag](v, ptr)
116 | 
117 |     return None
118 | 
119 | gdb.pretty_printers.append(plyPrinterGet)
120 | 


--------------------------------------------------------------------------------
/src/ply/ply.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright Tobias Waldekranz 
  3 |  *
  4 |  * SPDX-License-Identifier: GPL-2.0
  5 |  */
  6 | #include 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | #include 
 12 | #include 
 13 | #include 
 14 | 
 15 | #include 
 16 | #include 
 17 | #include 
 18 | 
 19 | #include 
 20 | 
 21 | static void usage()
 22 | {
 23 | 	fputs("ply - Dynamic tracing utility\n"
 24 | 	      "\n"
 25 | 	      "Usage:\n"
 26 | 	      "  ply [options] \n"
 27 | 	      "  ply [options] \n"
 28 | 	      "\n"
 29 | 	      "Options:\n"
 30 | 	      "  -c COMMAND     Run COMMAND in a shell, exit upon completion.\n"
 31 | 	      "  -d             Enable debug output.\n"
 32 | 	      "  -e             Exit after compiling.\n"
 33 | 	      "  -h             Print usage message and exit.\n"
 34 | 	      "  -k             Keep going in face of trace buffer overruns.\n"
 35 | 	      "  -S             Show generated BPF.\n"
 36 | 	      "  -T             Run self-test.\n"
 37 | 	      "  -u             Always turn off buffering of stdout/stderr.\n"
 38 | 	      "  -v             Print version information.\n",
 39 | 	      stderr);
 40 | }
 41 | 
 42 | static void self_test(char *plybin)
 43 | {
 44 | 	static unsigned char script[] = {
 45 | #		include "self-test.bytes"
 46 | 	};
 47 | 	char *cmd;
 48 | 	FILE *sh;
 49 | 
 50 | 	if (asprintf(&cmd, "PLYBIN=%s /bin/sh", plybin) < 0)
 51 | 		goto err;
 52 | 
 53 | 	sh = popen(cmd, "w");
 54 | 	free(cmd);
 55 | 	if (!sh)
 56 | 		goto err;
 57 | 
 58 | 	if (fwrite(script, sizeof(script), 1, sh) != 1)
 59 | 		goto err;
 60 | 
 61 | 	exit(pclose(sh) ? 1 : 0);
 62 | 
 63 | err:
 64 | 	_e("unable to run self-test\n");
 65 | 	exit(1);
 66 | }
 67 | 
 68 | static void memlock_uncap(void)
 69 | {
 70 | 	struct rlimit limit;
 71 | 	rlim_t current;
 72 | 	int err;
 73 | 
 74 | 	err = getrlimit(RLIMIT_MEMLOCK, &limit);
 75 | 	if (err) {
 76 | 		_e("unable to retrieve memlock limit, "
 77 | 		   "maps are likely limited in size\n");
 78 | 		return;
 79 | 	}
 80 | 
 81 | 	current = limit.rlim_cur;
 82 | 
 83 | 	/* The total size of all maps that ply is allowed to create is
 84 | 	 * limited by the amount of memory that can be locked into
 85 | 	 * RAM. By default, this limit can be quite low (64kB on a
 86 | 	 * standard x86_64 box running a recent kernel). So this
 87 | 	 * simply tells the kernel to allow ply to use as much as it
 88 | 	 * needs. */
 89 | 	limit.rlim_cur = limit.rlim_max = RLIM_INFINITY;
 90 | 	err = setrlimit(RLIMIT_MEMLOCK, &limit);
 91 | 	if (err) {
 92 | 		const char *suffix = "B";
 93 | 
 94 | 		if (!(current & 0xfffff)) {
 95 | 			suffix = "MB";
 96 | 			current >>= 20;
 97 | 		} else if (!(current & 0x3ff)) {
 98 | 			suffix = "kB";
 99 | 			current >>= 10;
100 | 		}
101 | 
102 | 		_w("could not remove memlock size restriction\n");
103 | 		_w("total map size is limited to %lu%s\n", current, suffix);
104 | 		return;
105 | 	}
106 | 
107 | 	_d("unlimited memlock\n");
108 | }
109 | 
110 | void dump(struct ply *ply)
111 | {
112 | 	struct ply_probe *pb;
113 | 
114 | 	if (!ply->probes) {
115 | 		printf("NO PROBES\n");
116 | 		return;
117 | 	}
118 | 
119 | 	printf("\n\n-- globals\n");
120 | 	symtab_dump(&ply->globals, stdout);
121 | 
122 | 	ply_probe_foreach(ply, pb) {
123 | 		printf("%s\n", pb->probe ? : "");
124 | 
125 | 		if (pb->ast)
126 | 			ast_fprint(stdout, pb->ast);
127 | 		else
128 | 			printf("NO AST\n");
129 | 
130 | 		printf("\n-- locals\n");
131 | 		symtab_dump(&pb->locals, stdout);
132 | 		printf("-- ir\n");
133 | 		ir_dump(pb->ir, stdout);
134 | 	}
135 | }
136 | 
137 | static void version()
138 | {
139 | 	printf("%s (linux-version:%u~%u.%u.%u)\n",
140 | 	       PACKAGE_STRING, LINUX_VERSION_CODE,
141 | 	       (LINUX_VERSION_CODE >> 16) & 0xff,
142 | 	       (LINUX_VERSION_CODE >>  8) & 0xff,
143 | 	       (LINUX_VERSION_CODE >>  0) & 0xff);
144 | }
145 | 
146 | static const char *sopts = "c:dehkSTuv";
147 | static struct option lopts[] = {
148 | 	{ "command",    required_argument, 0, 'c' },
149 | 	{ "debug",      no_argument,       0, 'd' },
150 | 	{ "dry-run",    no_argument,       0, 'e' },
151 | 	{ "help",       no_argument,       0, 'h' },
152 | 	{ "keep-going", no_argument,       0, 'k' },
153 | 	{ "dump",       no_argument,       0, 'S' },
154 | 	{ "self-test",  no_argument,       0, 'T' },
155 | 	{ "unbuffer",   no_argument,       0, 'u' },
156 | 	{ "version",    no_argument,       0, 'v' },
157 | 
158 | 	{ NULL }
159 | };
160 | 
161 | FILE *get_src(int argc, char **argv)
162 | {
163 | 	if (!argc)
164 | 		return NULL;
165 | 
166 | 	/* if the argument names an existing file that we have access
167 | 	 * to, use it as the source. */
168 | 	if (!access(argv[0], R_OK))
169 | 		return fopen(argv[0], "r");
170 | 
171 | 	/* TODO concat multiple argvs to one string and parse that as
172 | 	 * a ply script */
173 | 
174 | 	/* otherwise, parse the argument as a ply script. */
175 | 	return fmemopen(argv[0], strlen(argv[0]), "r");
176 | }
177 | 
178 | int inferior_prep(const char *cmd, int *infpid, int *inftrig)
179 | {
180 | 	int err, pid, trig[2];
181 | 
182 | 	err = pipe(trig);
183 | 	if (err)
184 | 		return err;
185 | 
186 | 	*inftrig = trig[1];
187 | 
188 | 	pid = fork();
189 | 	if (pid < 0)
190 | 		return pid;
191 | 
192 | 	if (pid) {
193 | 		char str[16];
194 | 
195 | 		*infpid = pid;
196 | 
197 | 		/* allow scripts to reference the pid of the inferior
198 | 		 * as $target. */
199 | 		snprintf(str, sizeof(str), "%d", pid);
200 | 		setenv("target", str, 0);
201 | 		return 0;
202 | 	}
203 | 
204 | 	/* wait for parent to compile and get ready */
205 | 	if (read(trig[0], &err, sizeof(err)) != sizeof(err))
206 | 		return -EINVAL;
207 | 
208 | 	/* if parent sends us an error, don't run the command. most
209 | 	 * probably the script did not compile. */
210 | 	if (err)
211 | 		exit(0);
212 | 
213 | 	return execl("/bin/sh", "sh", "-c", cmd, NULL);
214 | }
215 | 
216 | static int term_sig = 0;
217 | static void term(int sig)
218 | {
219 | 	term_sig = sig;
220 | 	return;
221 | }
222 | static const struct sigaction term_action = {
223 | 	.sa_handler = term,
224 | 	.sa_flags = 0,
225 | };
226 | 
227 | int main(int argc, char **argv)
228 | {
229 | 	struct ply *ply;
230 | 	struct ply_return ret = { .err = 1 };
231 | 	int opt, infpid, inftrig;
232 | 	int f_dryrun, f_dump;
233 | 	FILE *src;
234 | 	char *cmd = NULL;
235 | 
236 | 	f_dryrun = f_dump = 0;
237 | 	while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) > 0) {
238 | 		switch (opt) {
239 | 		case 'c':
240 | 			cmd = optarg;
241 | 			break;
242 | 		case 'd':
243 | 			ply_config.verify = 1;
244 | 			ply_debug = 1;
245 | 			break;
246 | 		case 'e':
247 | 			f_dryrun = 1;
248 | 			ply_config.ksyms = 0;
249 | 			break;
250 | 		case 'h':
251 | 			usage(); exit(0);
252 | 			break;
253 | 		case 'k':
254 | 			ply_config.strict = 0;
255 | 			break;
256 | 		case 'S':
257 | 			f_dump = 1;
258 | 			break;
259 | 		case 'T':
260 | 			self_test(argv[0]); exit(1);
261 | 			break;
262 | 		case 'u':
263 | 			setvbuf(stdout, NULL, _IONBF, 0);
264 | 			setvbuf(stderr, NULL, _IONBF, 0);
265 | 			break;
266 | 		case 'v':
267 | 			version(); exit(0);
268 | 			break;
269 | 
270 | 		default:
271 | 			_e("unknown option '%c'\n", opt);
272 | 			usage(); exit(1);
273 | 			break;
274 | 		}
275 | 	}
276 | 
277 | 	src = get_src(argc - optind, &argv[optind]);
278 | 	if (!src) {
279 | 		_e("no input\n");
280 | 		usage(); exit(1);
281 | 	}
282 | 
283 | 	if (cmd && inferior_prep(cmd, &infpid, &inftrig))
284 | 		exit(1);
285 | 
286 | 	/* TODO figure this out dynamically. terminfo? */
287 | 	ply_config.unicode = 1;
288 | 
289 | 	ply_init();
290 | 
291 | 	ply_alloc(&ply);
292 | 	ret.val = ply_fparse(ply, src);
293 | 	if (ret.val)
294 | 		goto err;
295 | 
296 | 	ret.val = ply_compile(ply);
297 | 
298 | 	if (f_dump)
299 | 		dump(ply);
300 | 
301 | 	if (ret.val)
302 | 		goto err;
303 | 
304 | 	if (f_dryrun)
305 | 		goto unload;
306 | 
307 | 	memlock_uncap();
308 | 
309 | 	ret = ply_load(ply);
310 | 	if (ret.exit || ret.err)
311 | 		goto err;
312 | 
313 | 	ply_start(ply);
314 | 	_d("ply: active\n");
315 | 
316 | 	sigaction(SIGINT, &term_action, NULL);
317 | 	sigaction(SIGCHLD, &term_action, NULL);
318 | 
319 | 	if (cmd) {
320 | 		int err = 0;
321 | 
322 | 		if (write(inftrig, &err, sizeof(err)) != sizeof(err)) {
323 | 			fprintf(stderr, "ply: unable to start command\n");
324 | 			ret.err = 1;
325 | 			ret.val = -EIO;
326 | 			goto stop;
327 | 		}
328 | 	}
329 | 
330 | 	ret = ply_loop(ply);
331 | 	if (ret.err && (ret.val == EINTR) && term_sig)
332 | 		ret.err = 0;
333 | stop:
334 | 	_d("ply: deactivating\n");
335 | 	ply_stop(ply);
336 | 
337 | 	ply_maps_print(ply);
338 | 
339 | unload:
340 | 	ply_unload(ply);
341 | 
342 | err:
343 | 	ply_free(ply);
344 | 
345 | 	if (ret.err) {
346 | 		if (ret.val)
347 | 			printf("ERR:%d\n", ret.val);
348 | 
349 | 		return 1;
350 | 	}
351 | 
352 | 	return ret.exit ? ret.val : 0;
353 | }
354 | 


--------------------------------------------------------------------------------
/src/ply/self-test.sh:
--------------------------------------------------------------------------------
  1 | #!/bin/sh
  2 | 
  3 | set -e
  4 | 
  5 | if [ ! "$PLYBIN" ]; then
  6 |     echo PLYBIN is not set
  7 |     exit 1
  8 | fi
  9 | 
 10 | err=0
 11 | 
 12 | if [ -f /proc/config.gz ]; then
 13 |     echo -n "Verifying kernel config (/proc/config.gz)... "
 14 |     kconf="zcat /proc/config.gz"
 15 | elif [ -f /boot/config-$(uname -r) ]; then
 16 |     echo -n "Verifying kernel config (/boot/config-$(uname -r))... "
 17 |     kconf="cat /boot/config-$(uname -r)"
 18 | fi
 19 | 
 20 | if [ "$kconf" ]; then
 21 |     $kconf | awk '
 22 | 	/^CONFIG_BPF_SYSCALL=y$/    { bpf=1 }
 23 | 	/^CONFIG_KPROBES=y$/        { kprobes=1 }
 24 | 	/^CONFIG_UPROBES=y$/        { uprobes=1 }
 25 | 	/^CONFIG_TRACEPOINTS=y$/    { tracepoints=1 }
 26 | 	/^CONFIG_FTRACE=y$/         { ftrace=1 }
 27 | 	/^CONFIG_DYNAMIC_FTRACE=y$/ { dftrace=1 }
 28 | 	/^CONFIG_PERF_EVENTS=y$/    { perf_events=1 }
 29 | 	END {
 30 | 	    if (bpf && (kprobes || tracepoints) && ftrace) {
 31 | 	       print("OK");
 32 | 	       err = 0;
 33 | 	    } else {
 34 | 	       print("ERROR");
 35 | 	       err = 1;
 36 | 	    }
 37 | 
 38 | 	    if (!bpf)
 39 | 	       print("  CONFIG_BPF_SYSCALL is not set");
 40 | 	    if (!kprobes)
 41 | 	       print("  CONFIG_KPROBES is not set");
 42 | 	    if (!uprobes)
 43 | 	       print("  CONFIG_UPROBES is not set");
 44 | 	    if (!tracepoints)
 45 | 	       print("  CONFIG_TRACEPOINTS is not set");
 46 | 	    if (!ftrace)
 47 | 	       print("  CONFIG_FTRACE is not set");
 48 | 	    if (!dftrace)
 49 | 	       print("  CONFIG_DYNAMIC_FTRACE is not set");
 50 | 	    if (!perf_events)
 51 | 	       print("  CONFIG_PERF_EVENTS is not set");
 52 | 
 53 | 	    exit(err);
 54 | 	}' || err=1
 55 | else
 56 |     echo "WARN: Unable to verify kernel config"
 57 | fi
 58 | 
 59 | echo -n "Ensuring that debugfs is mounted... "
 60 | if mount -t debugfs | grep -qw "/sys/kernel/debug"; then
 61 |     echo "OK"
 62 | else
 63 |     echo "ERROR"
 64 |     err=1
 65 | fi
 66 | 
 67 | if [ $(id -u) -ne 0 ]; then
 68 |     echo "WARN: not running as root, ply requires cap_sys_admin"
 69 | fi
 70 | 
 71 | echo -n "Verifying kprobe... "
 72 | if $PLYBIN 'kprobe:schedule { exit(0); }' 2>/dev/null; then
 73 |     echo "OK"
 74 | else
 75 |     echo "ERROR"
 76 |     err=1
 77 | fi
 78 | 
 79 | echo -n "Verifying tracepoint... "
 80 | if $PLYBIN 'tracepoint:sched/sched_switch { exit(0); }' 2>/dev/null; then
 81 |     echo "OK"
 82 | else
 83 |     echo "ERROR"
 84 |     err=1
 85 | fi
 86 | 
 87 | echo -n "Verifying special... "
 88 | if $PLYBIN 'BEGIN { exit(0); }' 2>/dev/null; then
 89 |     echo "OK"
 90 | else
 91 |     echo "ERROR"
 92 |     err=1
 93 | fi
 94 | 
 95 | echo -n "Verifying interval... "
 96 | if $PLYBIN 'interval:1s { exit(0); }' 2>/dev/null; then
 97 |     echo "OK"
 98 | else
 99 |     echo "ERROR"
100 |     err=1
101 | fi
102 | 
103 | exit $err
104 | 


--------------------------------------------------------------------------------
/test/.gitignore:
--------------------------------------------------------------------------------
1 | !/Makefile
2 | /cache
3 | /work
4 | 


--------------------------------------------------------------------------------
/test/Makefile:
--------------------------------------------------------------------------------
  1 | check-timeout := 300
  2 | 
  3 | arch-list := armv7 aarch64 x86_64
  4 | 
  5 | tc-arch-armv7   := armv7-eabihf
  6 | tc-arch-aarch64 := aarch64
  7 | tc-arch-x86_64  := x86-64
  8 | 
  9 | host-armv7   := arm
 10 | host-aarch64 := aarch64
 11 | host-x86_64  := x86_64
 12 | 
 13 | al = alpine-minirootfs-3.20.2-$(1)
 14 | tc = $(tc-arch-$(1))--musl--bleeding-edge-2024.05-1
 15 | 
 16 | al-repo := https://dl-cdn.alpinelinux.org/alpine/v3.20
 17 | tc-repo := https://toolchains.bootlin.com/downloads/releases/toolchains
 18 | 
 19 | al-tar = $(call al,$(1)).tar.gz
 20 | al-url = $(al-repo)/releases/$(1)/$(call al-tar,$(1))
 21 | tc-tar = $(call tc,$(1)).tar.xz
 22 | tc-url = $(tc-repo)/$(tc-arch-$(1))/tarballs/$(call tc-tar,$(1))
 23 | 
 24 | qemu-opts = -cpu max -m 256M \
 25 | 	-nographic -no-reboot \
 26 | 	-kernel work/$(1)-rootfs/boot/vmlinuz-virt \
 27 | 	-initrd work/$(1)-rootfs.cpio.gz \
 28 | 	-device i6300esb \
 29 | 	-append "$(2) panic=-1 root=initramfs ramdisk_size=64000 rdinit=/sbin/init quiet"
 30 | 
 31 | qemu-armv7 = qemu-system-arm -M virt,highmem=off \
 32 | 	$(call qemu-opts,armv7)
 33 | 
 34 | qemu-aarch64 = qemu-system-aarch64 -M virt \
 35 | 	-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
 36 | 	$(call qemu-opts,aarch64)
 37 | 
 38 | qemu-x86_64 = qemu-system-x86_64 -M pc \
 39 | 	-bios /usr/share/qemu/OVMF.fd \
 40 | 	$(call qemu-opts,x86_64,console=ttyS0)
 41 | 
 42 | 
 43 | all: check
 44 | check: $(addsuffix -check,$(arch-list))
 45 | build: $(addsuffix -build,$(arch-list))
 46 | 
 47 | define arch
 48 | 
 49 | $(1)-shell: work/$(1)-rootfs.cpio.gz
 50 | 	$(qemu-$(1))
 51 | 
 52 | $(1)-check: work/$(1)-rootfs.cpio.gz
 53 | 	rm -f work/$(1)-check
 54 | 	$(qemu-$(1)) \
 55 | 		-device virtio-serial \
 56 | 		-device virtserialport,name=check,chardev=check \
 57 | 		-chardev file,id=check,path=work/$(1)-check
 58 | 	test "`cat work/$(1)-check`" -eq 0
 59 | 
 60 | 
 61 | # Install ply & test scripts
 62 | 
 63 | work/$(1)-rootfs.cpio.gz: \
 64 | 		work/$(1)-rootfs \
 65 | 		work/$(1)-rootfs/boot/vmlinuz-virt \
 66 | 		$(1)-install \
 67 | 		work/$(1)-rootfs/lib/ply/test.sh
 68 | 	cd work/$(1)-rootfs && find . \
 69 | 		| cpio -o -H newc \
 70 | 		| gzip >../$(1)-rootfs.cpio.gz
 71 | 
 72 | work/$(1)-rootfs/lib/ply/test.sh: rootfs rootfs/lib/ply/test.sh |work/$(1)-rootfs
 73 | 	rsync -a $$cache/lx-$(1)
115 | 
116 | 	[ -f cache/$$$$(cat cache/lx-$(1))-$(1).tar.gz ] \
117 | 		|| wget \
118 | 			-O cache/$$$$(cat cache/lx-$(1))-$(1).tar.gz \
119 | 			$(al-repo)/main/$(1)/$$$$(cat cache/lx-$(1)).apk
120 | 
121 | 	tar -C work/$(1)-rootfs -maxf \
122 | 		cache/$$$$(cat cache/lx-$(1))-$(1).tar.gz 2>/dev/null
123 | 
124 | 
125 | # Toolchain
126 | 
127 | cache/$(call tc,$(1)): cache/$(call tc-tar,$(1))
128 | 	tar -C $$(@D) -maxf $$<
129 | 
130 | cache/$(call tc-tar,$(1)): |cache
131 | 	wget -O $$@ $(call tc-url,$(1))
132 | 
133 | endef
134 | 
135 | $(eval $(call arch,armv7))
136 | $(eval $(call arch,aarch64))
137 | $(eval $(call arch,x86_64))
138 | 
139 | cache:
140 | 	mkdir -p $@
141 | 
142 | ../configure:
143 | 	cd .. && ./autogen.sh
144 | 
145 | 
146 | .PHONY: all check build \
147 | 	$(addsuffix -build,$(arch-list)) $(addsuffix -install,$(arch-list)) \
148 | 	$(addsuffix -check,$(arch-list)) $(addsuffix -shell,$(arch-list))
149 | 


--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
 1 | Testing
 2 | =======
 3 | 
 4 | Build and test `ply` for/on multiple architectures. Toolchains and
 5 | root filesystems are automatically downloaded, the user only has to
 6 | make sure that the `squashfs-tools` and `qemu-system` packages are
 7 | installed, in order to use it.
 8 | 
 9 | OS profile images from the [NetBox][1] project provide the root
10 | filesystems; toolchains are fetched from Bootlin's [toolchain
11 | site][2]. The following machine types are supported:
12 | 
13 | | Machine | NetBox Platform |
14 | |---------|-----------------|
15 | | aarch64 | envoy           |
16 | | armv5   | basis           |
17 | | armv7   | dagger          |
18 | | powerpc | coronet         |
19 | | x86_64  | zero            |
20 | 
21 | Three primary `make` targets per machine are provided for the
22 | end-user:
23 | 
24 | - `MACH-build`: Verify that `ply` can be successfully built for `MACH`
25 |   without warnings.
26 | - `MACH-check`: Verify that the `ply` test suite can be successfully
27 |   run on `MACH` (implies `MACH-build`).
28 | - `MACH-shell`: Start an interactive session to a QEMU instance
29 |   running `MACH` (implies `MACH-build`).
30 | 
31 | Machine names and NetBox platform names may be used interchangeably,
32 | i.e. `make armv7-check` is equivalent to `make dagger-check`.
33 | 
34 | [1]: https://github.com/westermo/netbox
35 | [2]: https://toolchains.bootlin.com
36 | 


--------------------------------------------------------------------------------
/test/rootfs/etc/fstab:
--------------------------------------------------------------------------------
1 | devtmpfs	/dev			devtmpfs	defaults	0 0
2 | proc		/proc			proc		defaults	0 0
3 | sysfs		/sys			sysfs		defaults	0 0
4 | debugfs		/sys/kernel/debug	debugfs		defaults	0 0
5 | tracefs		/sys/kernel/tracing	tracefs		defaults	0 0
6 | tmpfs		/tmp			tmpfs		defaults	0 0
7 | 


--------------------------------------------------------------------------------
/test/rootfs/etc/hostname:
--------------------------------------------------------------------------------
1 | ply-test
2 | 


--------------------------------------------------------------------------------
/test/rootfs/etc/init.d/test.sh:
--------------------------------------------------------------------------------
1 | run [S] /lib/ply/test-wrapper.sh -- ply test suite
2 | 


--------------------------------------------------------------------------------
/test/rootfs/etc/inittab:
--------------------------------------------------------------------------------
1 | ::sysinit:/bin/mount -a
2 | ::sysinit:/bin/hostname -F /etc/hostname
3 | ::sysinit:/lib/ply/test-wrapper.sh
4 | console::respawn:/sbin/getty -L console 115200 vt100
5 | 


--------------------------------------------------------------------------------
/test/rootfs/etc/shadow:
--------------------------------------------------------------------------------
1 | root:::0:::::
2 | 


--------------------------------------------------------------------------------
/test/rootfs/lib/ply/test-wrapper.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | 
 3 | for vport in /dev/vport*; do
 4 |     name=$(cat /sys/class/virtio-ports/$(basename $vport)/name)
 5 |     if [ "$name" = "check" ]; then
 6 | 	echo "Running test suite" >/dev/console
 7 | 	/lib/ply/test.sh >/dev/console 2>&1
 8 | 
 9 | 	echo $? >$vport
10 | 	sync
11 | 
12 | 	poweroff -ff
13 | 	exit
14 |     fi
15 | done;
16 | 
17 | echo "Launching interactive shell" >/dev/console
18 | 


--------------------------------------------------------------------------------
/test/rootfs/lib/ply/test.sh:
--------------------------------------------------------------------------------
  1 | #!/bin/sh
  2 | 
  3 | total_fails=0
  4 | 
  5 | atomics_supported()
  6 | {
  7 |     case $(uname -m) in
  8 | 	arm*)
  9 | 	    # No JIT support for atomic operations as of Linux 6.6.
 10 | 	    # Alpine kernels set CONFIG_BPF_JIT_ALWAYS_ON, which means
 11 | 	    # we can't run ply scripts that generate those.
 12 | 	    return 1
 13 | 	    ;;
 14 |     esac
 15 | 
 16 |     return 0
 17 | }
 18 | 
 19 | fail()
 20 | {
 21 |     echo "  FAIL $case expected \"$1\", got \"$2\""
 22 |     total_fails=$(($total_fails + 1))
 23 | }
 24 | 
 25 | ply_simple()
 26 | {
 27 |     stdout=$(ply -c true "tracepoint:sched/sched_process_exit { ${1} }")
 28 |     code=$?
 29 | }
 30 | 
 31 | case=self-test
 32 | ply -T || fail "zero exitcode" "non-zero exitcode"
 33 | 
 34 | case=exit && ply_simple 'exit(42);' && \
 35 |     [ $code -eq 42 ] || fail 42 $code
 36 | 
 37 | case=if-stmt && ply_simple 'if (pid > 1) exit(0); else exit(1);' && \
 38 |     [ $code -eq 0 ] || fail 0 $code
 39 | 
 40 | case=print && ply_simple 'print("test"); exit(0);' && \
 41 |     [ $stdout = test ] || fail test "$stdout"
 42 | 
 43 | 
 44 | case=wildcard
 45 | ply -c \
 46 |     "dd if=/dev/zero of=/dev/null bs=1 count=100" \
 47 |     "kprobe:vfs_*r[ei][at][de] { @[comm, caller] = count(); }" >/tmp/wildcard \
 48 | && \
 49 | cat /tmp/wildcard | awk '
 50 |     /dd.*vfs_read/  { if ($NF >= 100) read  = 1; }
 51 |     /dd.*vfs_write/ { if ($NF >= 100) write = 1; }
 52 |     END             { exit(!(read && write)); }' \
 53 | || fail "at least 100 reads/writes" "$(cat /tmp/wildcard)"
 54 | 
 55 | 
 56 | if atomics_supported; then
 57 |     case=quantize
 58 |     ply -c \
 59 | 	"dd if=/dev/zero of=/dev/null bs=10240 count=10" \
 60 | 	'kr:vfs_read if (!strcmp(comm, "dd")) {
 61 |     		 @["rdsz"] = quantize(retval);
 62 |      }' >/tmp/quantize \
 63 | 	&& \
 64 | 	grep -qe '8k\s*,\s*16k\s*)\s*10' /tmp/quantize \
 65 | 	    || fail "10 reads in (8k, 16k]" "$(cat /tmp/quantize)"
 66 | fi
 67 | 
 68 | case=interval
 69 | ply -c 'for i in `seq 3`; do dd if=/dev/zero of=/dev/null count=10; sleep 1; done' \
 70 |     'k:vfs_read { @[pid] = count(); }
 71 |      i:1 { print(@); clear(@); }' >/tmp/interval \
 72 | && \
 73 | cat /tmp/interval | awk '/^@:/ { count++; } END { exit(count < 3); }' \
 74 | || fail "at least 3 print" "$(cat /tmp/interval)"
 75 | 
 76 | case=tracepoint-dyn
 77 | ply -c 'for i in $(seq 10); do uname >/dev/null; done' \
 78 |     'tracepoint:sched/sched_process_exec {
 79 |         @[dyn(data->filename)] = count();
 80 |     }' >/tmp/tracepoint-dyn \
 81 | && \
 82 | cat /tmp/tracepoint-dyn | awk '
 83 |     /uname/  { unames = $NF; }
 84 |     END      { exit(!(unames >= 10)); }' \
 85 | || fail "at least 10 unames" "$(cat /tmp/tracepoint-dyn)"
 86 | 
 87 | case=profile
 88 | ply 'BEGIN { printf("profile provider unit test\n"); c["profile_test"] = 0; }
 89 |      profile:0:100hz
 90 |      {
 91 |          if (c["profile_test"] == 100)
 92 |              exit(0);
 93 |          else
 94 |              c["profile_test"] = c["profile_test"] + 1;
 95 |      }' >/tmp/profile \
 96 | && \
 97 | cat /tmp/profile | awk -F': ' '
 98 |     /profile_test/  { count = $2; }
 99 |     END             { exit(count != 100); }' \
100 | || fail "count should be 100 for profile provider test" "$(cat /tmp/profile)"
101 | 
102 | exit $total_fails
103 | 


--------------------------------------------------------------------------------