├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.en.md ├── README.md ├── build.rs ├── examples ├── perf.folded └── perf.svg └── src ├── args.rs ├── bpf ├── hash.h ├── lua.h ├── lua54.h ├── profile.bpf.c └── profile.h ├── main.rs ├── perf.rs └── perf ├── eh.rs ├── maps.rs ├── syscall.rs └── var.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | src/bpf/vmlinux.h 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cstdlib": "c", 4 | "algorithm": "c", 5 | "functional": "c", 6 | "typeinfo": "c", 7 | "bit": "c", 8 | "istream": "c", 9 | "limits": "c", 10 | "ranges": "c", 11 | "streambuf": "c" 12 | }, 13 | "rust-analyzer.linkedProjects": [ 14 | "./Cargo.toml" 15 | ] 16 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lua-perf" 3 | version = "0.1.0" 4 | edition = "2021" 5 | keywords = ["lua", "perf", "lua-perf"] 6 | build = "build.rs" 7 | readme = "README.en.md" 8 | license-file = "LICENSE" 9 | description = "A perf tool for C and Lua hybrid code" 10 | homepage = "https://github.com/findstr/lua-perf" 11 | repository = "https://github.com/findstr/lua-perf" 12 | categories = ["command-line-utilities", "development-tools::profiling"] 13 | documentation = "https://github.com/findstr/lua-perf/blob/main/README.en.md" 14 | exclude = [ 15 | "examples/*", 16 | ".vscode/*", 17 | ] 18 | 19 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 20 | 21 | [dependencies] 22 | libc = "0.2.147" 23 | procfs = "0.16.0-RC1" 24 | procmaps = "0.4.1" 25 | memmap2 = "0.9.0" 26 | gimli = "0.28.0" 27 | rustc-demangle = "0.1.21" 28 | regex = "1.6" 29 | iced-x86 = "1.17" 30 | byteorder = "1.4.3" 31 | anyhow = "1.0" 32 | libbpf-rs="0.21.2" 33 | plain = "0.2" 34 | nix = "0.27.1" 35 | tracing = "0.1" 36 | tracing-subscriber = {version = "0.3", features = ["ansi", "env-filter", "fmt"]} 37 | blazesym = "=0.2.0-alpha.6" 38 | goblin = { version = "0.7.1", features = ["elf64"] } 39 | clap = { version = "4.4.6", features = ["derive", "env"] } 40 | clap_derive = "4.4.2" 41 | time = { version = "0.3", features = ["formatting", "local-offset", "macros"]} 42 | psutil = "3.2.2" 43 | lazy_static = "1.4.0" 44 | ctrlc = "3.4.1" 45 | capstone = "0.11.0" 46 | 47 | [build-dependencies] 48 | libbpf-cargo = "0.21.2" 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 重归混沌 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # lua-perf 2 | [![cn](https://img.shields.io/badge/lang-cn-red.svg)](./README.md) 3 | 4 | `lua-perf` is a performance profiling tool implemented based on `eBPF`, currently supporting `Lua 5.4`. 5 | 6 | ## Features 7 | 8 | - Provides performance analysis for mixed `C` and `Lua` code, as well as pure `C` code. 9 | - Uses stack sampling technique with minimal performance impact on the target process, making it suitable for production environments. 10 | - Performs stack backtracing in the kernel space using `eh-frame`, eliminating the need for the target process to use the `-fno-omit-frame-pointer` option to preserve stack frame pointers. 11 | 12 | ## Requirements 13 | 14 | To use `lua-perf`, make sure you meet the following requirements: 15 | 16 | - The installed kernel version needs to be `5.17` or above. 17 | 18 | ## Generating Flame Graphs 19 | 20 | To generate flame graphs, you need to use `lua-perf` in conjunction with the [FlameGraph](https://github.com/brendangregg/FlameGraph.git) tool. Here's how you can do it: 21 | 22 | 1. First, run the command `sudo lua-perf -p -f ` to sample the call stacks of the target process and generate a `perf.fold` file in the current directory. `` is the process ID of the target process, which can be a process inside a Docker container or a process on the host machine. `` is the stack sampling frequency, with a default value of `1000` (1000 samples per second). 23 | 24 | 2. Next, convert the `perf.fold` file to a flame graph by running `./FlameGraph/flamegraph.pl perf.folded > perf.svg`. 25 | 26 | 3. Finally, you will find the generated flame graph, `perf.svg`, in the current directory. 27 | 28 | Here's an example flame graph: 29 | 30 | ![perf](./examples/perf.svg) 31 | 32 | ## Logging 33 | In the BPF program, bpf_trace_printk is used to print logs. If you suspect any abnormalities in the performance sampling, you can view the logs using the following commands: 34 | 35 | ``` 36 | sudo mount -t tracefs nodev /sys/kernel/tracing 37 | sudo cat /sys/kernel/debug/tracing/trace_pipe 38 | These commands will help you access the logs and view them. If you have any further questions, feel free to ask. 39 | ``` 40 | 41 | ## Known Issues 42 | 43 | `lua-perf` currently has the following known issues: 44 | 45 | - Lack of support for `CFA_expression`, which may result in failed stack backtracing in extreme cases. 46 | - When analyzing Lua stacks, the search for the `L` pointer is currently done by assuming it is stored in register `rbx`, which is correct for most cases with `GCC -O2`. However, depending on the optimization level of GCC, the value of `L` may be stored in a different register, leading to failures in Lua stack analysis. 47 | - The analysis of `CFA` instructions does not handle `vdso` at the moment, causing stack backtracing failures for function calls in `vdso`. 48 | - The process of merging C stacks and Lua stacks uses a heuristic strategy, which may have some flaws in extreme cases (none have been found so far). 49 | 50 | ## Future Work 51 | 52 | The following tasks are planned for `lua-perf`: 53 | 54 | - Support for `CFA_expression` 55 | - Support for `vdso` 56 | - Dynamic analysis of the `L` register 57 | - Optimization of the merging strategy for C stacks and Lua stacks 58 | - Support for more versions of Lua 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-perf 2 | [![en](https://img.shields.io/badge/lang-en-red.svg)](./README.en.md) 3 | 4 | `lua-perf`是一个基于`eBPF`实现的性能分析工具,目前仅支持`Lua 5.4`。 5 | 6 | ## 功能 7 | 8 | - 提供对`C`和`Lua`混合代码的性能分析,同时也支持纯`C`代码。 9 | - 采用栈采样技术,并且对目标进程的性能影响非常小,可以在生产环境中使用。 10 | - 通过使用`eh-frame`在内核空间进行栈回溯,不要求目标进程使用`-fno-omit-frame-pointer`选项来保留栈帧指针。 11 | 12 | ## 执行要求 13 | 14 | 为了使用`lua-perf`,您需要满足以下要求: 15 | 16 | - 安装的`Kernel`版本需要在`5.17`以上。 17 | 18 | ## 生成火焰图 19 | 20 | 要生成火焰图,您需要使用`lua-perf`配合[FlameGraph](https://github.com/brendangregg/FlameGraph.git)工具进行操作。以下是步骤: 21 | 22 | 1. 首先,使用命令 `sudo lua-perf -p -f ` 对目标进程进行栈采样,并在当前目录下生成 `perf.fold` 文件。其中 `` 是目标进程的进程ID,可以是Docker内的进程或者宿主机上的进程。`` 是栈的采样频率,默认为 `1000`(即每秒采样1000次)。 23 | 24 | 2. 然后,使用命令 `./FlameGraph/flamegraph.pl perf.folded > perf.svg` 将 `perf.fold` 文件转换成火焰图。 25 | 26 | 3. 最后,您就可以在当前目录下找到生成的火焰图 `perf.svg`。 27 | 28 | 这是一个示例火焰图: 29 | 30 | ![perf](./examples/perf.svg) 31 | 32 | ## 日志 33 | 34 | 在BPF程序中,使用了`bpf_printk`来打印日志, 当你怀疑性能采样结果可能有异常时,您可以通过以下命令来查看日志。 35 | 36 | ```shell 37 | sudo mount -t tracefs nodev /sys/kernel/tracing 38 | sudo cat /sys/kernel/debug/tracing/trace_pipe 39 | ``` 40 | 41 | ## 已知问题 42 | 43 | `lua-perf`目前存在以下已知问题: 44 | 45 | - 尚不支持`CFA_expression`,在某些极端情况下可能会导致调用栈回溯失败。 46 | - 在分析Lua栈时,动态分析`L`寄存器(目前仅支持gcc/clang O0~03) 47 | - 在分析`CFA`指令时,暂时没有处理 `vdso`,因此在 `vdso` 中的函数调用会导致栈回溯失败。 48 | - 在合并进程的C栈和Lua栈时,采用了启发式的合并策略,极端情况下可能存在一些瑕疵(目前尚未发现)。 49 | 50 | ## 待完成事项 51 | 52 | 以下是`lua-perf`计划完成的事项: 53 | 54 | - 支持`CFA_expression` 55 | - 支持`vdso` 56 | - 优化C栈和Lua栈的合并策略 57 | - 支持更多版本的Lua 58 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::path::PathBuf; 4 | use std::process::Command; 5 | use std::str::FromStr; 6 | 7 | extern crate libbpf_cargo; 8 | use libbpf_cargo::SkeletonBuilder; 9 | 10 | const BPF_SRC: &str = concat!("src/bpf/", "profile.bpf.c"); 11 | 12 | fn btf_dump() { 13 | let mut out = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR must be set in build script")); 14 | out.push("vmlinux.h"); 15 | let path = out.to_str().unwrap(); 16 | let btf = File::create(&out).expect(format!("failed to open: {}", path).as_str()); 17 | let mut cmd = Command::new("bpftool") 18 | .args(&["btf", "dump", "file", "/sys/kernel/btf/vmlinux", "format", "c"]) 19 | .stdout(btf) 20 | .spawn() 21 | .expect("failed to generate vmlinux.h"); 22 | cmd.wait().expect("fail to generate vmlinux.h"); 23 | println!("cargo:cargo rerun-if-not-exists={}", path); 24 | } 25 | 26 | fn output() ->String { 27 | let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR must be set in build script"); 28 | let out_dir_str = out_dir.to_str().unwrap().clone(); 29 | String::from_str(out_dir_str).unwrap() 30 | } 31 | fn build_skel() { 32 | let mut out = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR must be set in build script")); 33 | out.push("profile.skel.rs"); 34 | let include = format!("-I{}", output()); 35 | SkeletonBuilder::new() 36 | .debug(true) 37 | .clang_args(include) 38 | .source(BPF_SRC) 39 | .build_and_generate(&out) 40 | .unwrap(); 41 | println!("cargo:rerun-if-changed={BPF_SRC}"); 42 | println!("cargo:rerun-if-changed=src/bpf/lstate.h"); 43 | println!("cargo:rerun-if-changed=src/bpf/profile.h"); 44 | println!("cargo:rerun-if-changed=src/bpf/hash.h"); 45 | } 46 | 47 | fn main() { 48 | btf_dump(); 49 | build_skel(); 50 | } 51 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Parser)] 4 | #[command(name = "lua-perf")] 5 | #[command(author = "findstr ")] 6 | #[command(version = "0.1")] 7 | #[command(about = "A perf tool for C and Lua hybrid code")] 8 | pub struct Args { 9 | #[arg(short, long, help = "PID of the process to profile")] 10 | pub pid: libc::pid_t, 11 | #[clap(short, long, default_value_t = 1000, help="Profile sample frequency(HZ)")] 12 | pub freq: u64, 13 | } -------------------------------------------------------------------------------- /src/bpf/hash.h: -------------------------------------------------------------------------------- 1 | #ifndef _HASH_H 2 | #define _HASH_H 3 | // Avoid pulling in any other headers. 4 | typedef unsigned int uint32_t; 5 | 6 | // murmurhash2 from 7 | // https://github.com/aappleby/smhasher/blob/92cf3702fcfaadc84eb7bef59825a23e0cd84f56/src/MurmurHash2.cpp 8 | static __always_inline uint32_t murmur_hash2(const u32 *data, int len, uint32_t seed) { 9 | /* 'm' and 'r' are mixing constants generated offline. 10 | They're not really 'magic', they just happen to work well. */ 11 | 12 | const uint32_t m = 0x5bd1e995; 13 | const int r = 24; 14 | 15 | /* Initialize the hash to a 'random' value */ 16 | 17 | uint32_t h = seed ^ len; 18 | 19 | /* Mix 4 bytes at a time into the hash */ 20 | 21 | // MAX_STACK_DEPTH * 2 = 256 (because we hash 32 bits at a time). 22 | for (int i = 0; i < 256; i++) { 23 | if (len < 4) { 24 | break; 25 | } 26 | uint32_t k = *data; 27 | k *= m; 28 | k ^= k >> r; 29 | k *= m; 30 | 31 | h *= m; 32 | h ^= k; 33 | 34 | data++; 35 | len -= 4; 36 | } 37 | 38 | /* Do a few final mixes of the hash to ensure the last few 39 | // bytes are well-incorporated. */ 40 | 41 | h ^= h >> 13; 42 | h *= m; 43 | h ^= h >> 15; 44 | 45 | return h; 46 | } 47 | 48 | #endif /* _LINUX_JHASH_H */ -------------------------------------------------------------------------------- /src/bpf/lua.h: -------------------------------------------------------------------------------- 1 | #ifndef _LUA_H 2 | #define _LUA_H 3 | 4 | #include "lua54.h" 5 | 6 | #endif -------------------------------------------------------------------------------- /src/bpf/lua54.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** $Id: lstate.h $ 3 | ** Global State 4 | ** See Copyright Notice in lua.h 5 | */ 6 | 7 | #ifndef lstate_h 8 | #define lstate_h 9 | 10 | #include "vmlinux.h" 11 | #include "profile.h" 12 | 13 | typedef struct lua_State lua_State; 14 | typedef struct lua_Debug lua_Debug; 15 | typedef unsigned char lu_byte; 16 | typedef signed char ls_byte; 17 | typedef size_t lu_mem; 18 | typedef ptrdiff_t l_mem; 19 | typedef int sig_atomic_t; 20 | typedef uint32_t Instruction; 21 | typedef struct CallInfo CallInfo; 22 | typedef uint32_t l_uint32; 23 | typedef uint64_t lua_Integer; 24 | typedef double lua_Number; 25 | typedef ptrdiff_t lua_KContext; 26 | typedef int (*lua_CFunction) (lua_State *L); 27 | typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); 28 | typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); 29 | typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont); 30 | typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); 31 | 32 | #define CommonHeader struct GCObject *next; lu_byte tt; lu_byte marked 33 | #define TValuefields Value value_; lu_byte tt_ 34 | #define l_signalT sig_atomic_t 35 | #define STRCACHE_N 53 36 | #define STRCACHE_M 2 37 | #define LUA_NUMTYPES 9 38 | #define MAXIWTHABS 128 39 | 40 | /* 41 | ** Bits in CallInfo status 42 | */ 43 | #define CIST_OAH (1<<0) /* original value of 'allowhook' */ 44 | #define CIST_C (1<<1) /* call is running a C function */ 45 | #define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ 46 | #define CIST_HOOKED (1<<3) /* call is running a debug hook */ 47 | #define CIST_YPCAL (1<<4) /* doing a yieldable protected call */ 48 | #define CIST_TAIL (1<<5) /* call was tail called */ 49 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ 50 | #define CIST_FIN (1<<7) /* function "called" a finalizer */ 51 | #define CIST_TRAN (1<<8) /* 'ci' has transfer information */ 52 | #define CIST_CLSRET (1<<9) /* function is closing tbc variables */ 53 | /* Bits 10-12 are used for CIST_RECST (see below) */ 54 | #define CIST_RECST 10 55 | #if defined(LUA_COMPAT_LT_LE) 56 | #define CIST_LEQ (1<<13) /* using __lt for __le */ 57 | #endif 58 | 59 | #define isLua(ci) (!((ci)->callstatus & CIST_C)) 60 | 61 | typedef enum { 62 | TM_INDEX, 63 | TM_NEWINDEX, 64 | TM_GC, 65 | TM_MODE, 66 | TM_LEN, 67 | TM_EQ, /* last tag method with fast access */ 68 | TM_ADD, 69 | TM_SUB, 70 | TM_MUL, 71 | TM_MOD, 72 | TM_POW, 73 | TM_DIV, 74 | TM_IDIV, 75 | TM_BAND, 76 | TM_BOR, 77 | TM_BXOR, 78 | TM_SHL, 79 | TM_SHR, 80 | TM_UNM, 81 | TM_BNOT, 82 | TM_LT, 83 | TM_LE, 84 | TM_CONCAT, 85 | TM_CALL, 86 | TM_CLOSE, 87 | TM_N /* number of elements in the enum */ 88 | } TMS; 89 | 90 | typedef struct GCObject { 91 | CommonHeader; 92 | } GCObject; 93 | 94 | typedef union Value { 95 | struct GCObject *gc; /* collectable objects */ 96 | void *p; /* light userdata */ 97 | lua_CFunction f; /* light C functions */ 98 | lua_Integer i; /* integer numbers */ 99 | lua_Number n; /* float numbers */ 100 | /* not used, but may avoid warnings for uninitialized value */ 101 | lu_byte ub; 102 | } Value; 103 | 104 | typedef struct TValue { 105 | TValuefields; 106 | } TValue; 107 | 108 | typedef struct UpVal { 109 | CommonHeader; 110 | union { 111 | TValue *p; /* points to stack or to its own value */ 112 | ptrdiff_t offset; /* used while the stack is being reallocated */ 113 | } v; 114 | union { 115 | struct { /* (when open) */ 116 | struct UpVal *next; /* linked list */ 117 | struct UpVal **previous; 118 | } open; 119 | TValue value; /* the value (when closed) */ 120 | } u; 121 | } UpVal; 122 | 123 | typedef struct TString { 124 | CommonHeader; 125 | lu_byte extra; /* reserved words for short strings; "has hash" for longs */ 126 | lu_byte shrlen; /* length for short strings */ 127 | unsigned int hash; 128 | union { 129 | size_t lnglen; /* length for long strings */ 130 | struct TString *hnext; /* linked list for hash table */ 131 | } u; 132 | char contents[1]; 133 | } TString; 134 | 135 | typedef struct stringtable { 136 | TString **hash; 137 | int nuse; /* number of elements */ 138 | int size; 139 | } stringtable; 140 | 141 | typedef union StackValue { 142 | TValue val; 143 | struct { 144 | TValuefields; 145 | unsigned short delta; 146 | } tbclist; 147 | } StackValue; 148 | 149 | typedef StackValue *StkId; 150 | 151 | typedef union { 152 | StkId p; /* actual pointer */ 153 | ptrdiff_t offset; /* used while the stack is being reallocated */ 154 | } StkIdRel; 155 | 156 | struct CallInfo { 157 | StkIdRel func; /* function index in the stack */ 158 | StkIdRel top; /* top for this function */ 159 | struct CallInfo *previous, *next; /* dynamic call link */ 160 | union { 161 | struct { /* only for Lua functions */ 162 | const Instruction *savedpc; 163 | volatile l_signalT trap; 164 | int nextraargs; /* # of extra arguments in vararg functions */ 165 | } l; 166 | struct { /* only for C functions */ 167 | lua_KFunction k; /* continuation in case of yields */ 168 | ptrdiff_t old_errfunc; 169 | lua_KContext ctx; /* context info. in case of yields */ 170 | } c; 171 | } u; 172 | union { 173 | int funcidx; /* called-function index */ 174 | int nyield; /* number of values yielded */ 175 | int nres; /* number of values returned */ 176 | struct { /* info about transferred values (for call/return hooks) */ 177 | unsigned short ftransfer; /* offset of first value transferred */ 178 | unsigned short ntransfer; /* number of values transferred */ 179 | } transferinfo; 180 | } u2; 181 | short nresults; /* expected number of results from this function */ 182 | unsigned short callstatus; 183 | }; 184 | 185 | 186 | typedef struct global_State { 187 | lua_Alloc frealloc; /* function to reallocate memory */ 188 | void *ud; /* auxiliary data to 'frealloc' */ 189 | l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ 190 | l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ 191 | lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ 192 | lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */ 193 | stringtable strt; /* hash table for strings */ 194 | TValue l_registry; 195 | TValue nilvalue; /* a nil value */ 196 | unsigned int seed; /* randomized seed for hashes */ 197 | lu_byte currentwhite; 198 | lu_byte gcstate; /* state of garbage collector */ 199 | lu_byte gckind; /* kind of GC running */ 200 | lu_byte gcstopem; /* stops emergency collections */ 201 | lu_byte genminormul; /* control for minor generational collections */ 202 | lu_byte genmajormul; /* control for major generational collections */ 203 | lu_byte gcstp; /* control whether GC is running */ 204 | lu_byte gcemergency; /* true if this is an emergency collection */ 205 | lu_byte gcpause; /* size of pause between successive GCs */ 206 | lu_byte gcstepmul; /* GC "speed" */ 207 | lu_byte gcstepsize; /* (log2 of) GC granularity */ 208 | GCObject *allgc; /* list of all collectable objects */ 209 | GCObject **sweepgc; /* current position of sweep in list */ 210 | GCObject *finobj; /* list of collectable objects with finalizers */ 211 | GCObject *gray; /* list of gray objects */ 212 | GCObject *grayagain; /* list of objects to be traversed atomically */ 213 | GCObject *weak; /* list of tables with weak values */ 214 | GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ 215 | GCObject *allweak; /* list of all-weak tables */ 216 | GCObject *tobefnz; /* list of userdata to be GC */ 217 | GCObject *fixedgc; /* list of objects not to be collected */ 218 | /* fields for generational collector */ 219 | GCObject *survival; /* start of objects that survived one GC cycle */ 220 | GCObject *old1; /* start of old1 objects */ 221 | GCObject *reallyold; /* objects more than one cycle old ("really old") */ 222 | GCObject *firstold1; /* first OLD1 object in the list (if any) */ 223 | GCObject *finobjsur; /* list of survival objects with finalizers */ 224 | GCObject *finobjold1; /* list of old1 objects with finalizers */ 225 | GCObject *finobjrold; /* list of really old objects with finalizers */ 226 | struct lua_State *twups; /* list of threads with open upvalues */ 227 | lua_CFunction panic; /* to be called in unprotected errors */ 228 | struct lua_State *mainthread; 229 | TString *memerrmsg; /* message for memory-allocation errors */ 230 | TString *tmname[TM_N]; /* array with tag-method names */ 231 | struct Table *mt[LUA_NUMTYPES]; /* metatables for basic types */ 232 | TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ 233 | lua_WarnFunction warnf; /* warning function */ 234 | void *ud_warn; /* auxiliary data to 'warnf' */ 235 | } global_State; 236 | 237 | 238 | /* 239 | ** 'per thread' state 240 | */ 241 | struct lua_State { 242 | CommonHeader; 243 | lu_byte status; 244 | lu_byte allowhook; 245 | unsigned short nci; /* number of items in 'ci' list */ 246 | StkIdRel top; /* first free slot in the stack */ 247 | global_State *l_G; 248 | CallInfo *ci; /* call info for current function */ 249 | StkIdRel stack_last; /* end of stack (last element + 1) */ 250 | StkIdRel stack; /* stack base */ 251 | UpVal *openupval; /* list of open upvalues in this stack */ 252 | StkIdRel tbclist; /* list of to-be-closed variables */ 253 | GCObject *gclist; 254 | struct lua_State *twups; /* list of threads with open upvalues */ 255 | struct lua_longjmp *errorJmp; /* current error recover point */ 256 | CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ 257 | volatile lua_Hook hook; 258 | ptrdiff_t errfunc; /* current error handling function (stack index) */ 259 | l_uint32 nCcalls; /* number of nested (non-yieldable | C) calls */ 260 | int oldpc; /* last pc traced */ 261 | int basehookcount; 262 | int hookcount; 263 | volatile l_signalT hookmask; 264 | }; 265 | 266 | #define ClosureHeader \ 267 | CommonHeader; lu_byte nupvalues; GCObject *gclist 268 | 269 | typedef struct CClosure { 270 | ClosureHeader; 271 | lua_CFunction f; 272 | TValue upvalue[1]; /* list of upvalues */ 273 | } CClosure; 274 | 275 | 276 | typedef struct LClosure { 277 | ClosureHeader; 278 | struct Proto *p; 279 | UpVal *upvals[1]; /* list of upvalues */ 280 | } LClosure; 281 | 282 | 283 | typedef union Closure { 284 | CClosure c; 285 | LClosure l; 286 | } Closure; 287 | 288 | typedef struct AbsLineInfo { 289 | int pc; 290 | int line; 291 | } AbsLineInfo; 292 | 293 | typedef struct Upvaldesc { 294 | TString *name; /* upvalue name (for debug information) */ 295 | lu_byte instack; /* whether it is in stack (register) */ 296 | lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ 297 | lu_byte kind; /* kind of corresponding variable */ 298 | } Upvaldesc; 299 | 300 | typedef struct LocVar { 301 | TString *varname; 302 | int startpc; /* first point where variable is active */ 303 | int endpc; /* first point where variable is dead */ 304 | } LocVar; 305 | 306 | typedef struct Proto { 307 | CommonHeader; 308 | lu_byte numparams; /* number of fixed (named) parameters */ 309 | lu_byte is_vararg; 310 | lu_byte maxstacksize; /* number of registers needed by this function */ 311 | int sizeupvalues; /* size of 'upvalues' */ 312 | int sizek; /* size of 'k' */ 313 | int sizecode; 314 | int sizelineinfo; 315 | int sizep; /* size of 'p' */ 316 | int sizelocvars; 317 | int sizeabslineinfo; /* size of 'abslineinfo' */ 318 | int linedefined; /* debug information */ 319 | int lastlinedefined; /* debug information */ 320 | TValue *k; /* constants used by the function */ 321 | Instruction *code; /* opcodes */ 322 | struct Proto **p; /* functions defined inside the function */ 323 | Upvaldesc *upvalues; /* upvalue information */ 324 | ls_byte *lineinfo; /* information about source lines (debug information) */ 325 | AbsLineInfo *abslineinfo; /* idem */ 326 | LocVar *locvars; /* information about local variables (debug information) */ 327 | TString *source; /* used for debug information */ 328 | GCObject *gclist; 329 | } Proto; 330 | 331 | #define getproto(o) (clLvalue(o)->p) 332 | 333 | 334 | union GCUnion { 335 | GCObject gc; /* common header */ 336 | struct TString ts; 337 | union Closure cl; 338 | struct Proto p; 339 | struct lua_State th; /* thread */ 340 | struct UpVal upv; 341 | }; 342 | 343 | /* 344 | ** basic types 345 | */ 346 | #define LUA_TNONE (-1) 347 | 348 | #define LUA_TNIL 0 349 | #define LUA_TBOOLEAN 1 350 | #define LUA_TLIGHTUSERDATA 2 351 | #define LUA_TNUMBER 3 352 | #define LUA_TSTRING 4 353 | #define LUA_TTABLE 5 354 | #define LUA_TFUNCTION 6 355 | #define LUA_TUSERDATA 7 356 | #define LUA_TTHREAD 8 357 | 358 | #define LUA_NUMTYPES 9 359 | 360 | #define BIT_ISCOLLECTABLE (1 << 6) 361 | 362 | #define makevariant(t,v) ((t) | ((v) << 4)) 363 | #define cast(t, exp) ((t)(exp)) 364 | #define cast_int(i) cast(int, (i)) 365 | #define s2v(o) (&(o)->val) 366 | #define val_(o) ((o)->value_) 367 | #define cast_u(o) cast(union GCUnion *, (o)) 368 | #define gco2lcl(o) (&((cast_u(o))->cl.l)) 369 | #define clLvalue(o) gco2lcl(val_(o).gc) 370 | #define pcRel(pc, p) (cast_int((pc) - (p)->code) - 1) 371 | #define ci_func(ci) (clLvalue(s2v((ci)->func.p))) 372 | #define getstr(ts) ((ts)->contents) 373 | #define LUA_VSHRSTR makevariant(LUA_TSTRING, 0) 374 | #define tsslen(s) ((s)->tt == LUA_VSHRSTR ? (s)->shrlen : (s)->u.lnglen) 375 | #define ctb(t) ((t) | BIT_ISCOLLECTABLE) 376 | #define rawtt(o) ((o)->tt_) 377 | #define checktag(o,t) (rawtt(o) == (t)) 378 | 379 | #define LUA_VLCL makevariant(LUA_TFUNCTION, 0) /* Lua closure */ 380 | #define LUA_VLCF makevariant(LUA_TFUNCTION, 1) /* light C function */ 381 | #define LUA_VCCL makevariant(LUA_TFUNCTION, 2) /* C closure */ 382 | 383 | 384 | #define withvariant(t) ((t) & 0x3F) 385 | #define ttisLclosure(o) checktag((o), ctb(LUA_VLCL)) 386 | #define ttislcf(o) checktag((o), LUA_VLCF) 387 | #define ttisCclosure(o) checktag((o), ctb(LUA_VCCL)) 388 | 389 | #define LUA_FILE_LEN (64) 390 | 391 | static __always_inline int currentpc (CallInfo *ci) { 392 | return pcRel(ci->u.l.savedpc, ci_func(ci)->p); 393 | } 394 | 395 | #define LINE_BUF_SIZE (8) 396 | 397 | struct line_ctx { 398 | //input 399 | int pc; 400 | void *ptr; 401 | unsigned int count; 402 | union { 403 | AbsLineInfo abslineinfo[LINE_BUF_SIZE]; 404 | lu_byte lineinfo[LINE_BUF_SIZE]; 405 | }; 406 | //output 407 | int basepc; 408 | int baseline; 409 | }; 410 | 411 | static __always_inline void *lua_get_closure(StkIdRel stk_base, CallInfo *ci, int *ctype) 412 | { 413 | void *ptr; 414 | int err; 415 | StackValue stk; 416 | ptr = (u8 *)ci->func.p; 417 | if ((uintptr_t)ptr < 1024*1024) { //it's a offset 418 | ptr += (uintptr_t)(stk_base.p); 419 | } 420 | DEBUG("lua_get_closure read stk:%lx %lx", ptr, ci->func.p); 421 | err = bpf_probe_read_user(&stk, sizeof(stk), (void *)ptr); 422 | if (err != 0) { 423 | ERROR("lua_get_closure read stk failed"); 424 | return NULL; 425 | } 426 | *ctype = 0; 427 | if (ttisLclosure(&stk.val)) { 428 | *ctype = LUA_VLCL; 429 | } else if (ttislcf(&stk.val)) { 430 | *ctype = LUA_VLCF; 431 | } else if (ttisCclosure(&stk.val)) { 432 | *ctype = LUA_VCCL; 433 | } 434 | DEBUG("lua_get_closure ci type:%d %lx", *ctype, stk.val.value_.f); 435 | return (void *)stk.val.value_.gc; 436 | } 437 | 438 | static __always_inline void *lua_get_proto(void *ptr, int ctype) 439 | { 440 | int err; 441 | Closure cl; 442 | err = bpf_probe_read_user(&cl, sizeof(cl.l), ptr); 443 | if (err != 0) { 444 | ERROR("lua_get_proto read %d failed ptr:%lx", ctype, ptr); 445 | return NULL; 446 | } 447 | switch (ctype) { 448 | case LUA_VLCL: //lua closure 449 | return cl.l.p; 450 | case LUA_VCCL: //c closure 451 | return cl.c.f; 452 | default: 453 | ERROR("lua_get_proto unknow ctype:%d", ctype); 454 | return NULL; 455 | } 456 | } 457 | 458 | struct lua_proto_source { 459 | TString *source; /* used for debug information */ 460 | int linedefined; /* debug information */ 461 | }; 462 | 463 | static __always_inline uint32_t lua_get_source(void *ptr, struct lua_proto_source *source) 464 | { 465 | int err; 466 | Proto p; 467 | err = bpf_probe_read_user(&p, sizeof(p), ptr); 468 | if (err != 0) { 469 | ERROR("lua_get_source read proto failed:%lx", ptr); 470 | return err; 471 | } 472 | source->source = p.source; /* used for debug information */ 473 | source->linedefined = p.linedefined; /* debug information */ 474 | return 0; 475 | } 476 | 477 | static __always_inline void *lua_get_file(struct lua_proto_source *proto, size_t *sz) 478 | { 479 | size_t n; 480 | TString source; 481 | void *ptr = proto->source; 482 | int err = bpf_probe_read_user(&source, offsetof(TString, contents), ptr); 483 | if (err != 0) { 484 | ERROR("lua_get_file read source failed:%lx", ptr); 485 | return NULL; 486 | } 487 | ptr += offsetof(TString, contents); 488 | *sz = tsslen(&source); 489 | return ptr; 490 | } 491 | 492 | static __always_inline void *lua_func_addr(StkIdRel stk, CallInfo *ci) { 493 | int err; 494 | void *ptr; 495 | int ctype; 496 | size_t n; 497 | uint32_t file_id; 498 | struct lua_proto_source source; 499 | //read stk 500 | ptr = lua_get_closure(stk, ci, &ctype); 501 | if (ptr == NULL || ctype == LUA_VLCF) { 502 | return ptr; 503 | } 504 | ptr = lua_get_proto(ptr, ctype); 505 | if (ptr == NULL) { 506 | return NULL; 507 | } 508 | if (ctype != LUA_VLCL) { //not a Lua closure, direct return c function addr 509 | return ptr; 510 | } 511 | if (lua_get_source(ptr, &source) != 0) { 512 | return NULL; 513 | } 514 | ptr = lua_get_file(&source, &n); 515 | if (ptr == NULL) { 516 | return NULL; 517 | } 518 | file_id = string_to_id(ptr, n); 519 | if (file_id == 0) { 520 | return NULL; 521 | } 522 | DEBUG("lua_func_addr lua:%x %x", file_id, source.linedefined); 523 | return MARK_LUA_ADDR((uintptr_t)source.linedefined << 32 | file_id); 524 | } 525 | 526 | #define lua_ci_is_fresh(ci) ((ci->callstatus & CIST_FRESH) == CIST_FRESH) 527 | 528 | #endif 529 | 530 | -------------------------------------------------------------------------------- /src/bpf/profile.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include "profile.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "lua.h" 8 | 9 | char LICENSE[] SEC("license") = "GPL"; 10 | 11 | static __always_inline struct eh_ctx *search(u64 eip) 12 | { 13 | u32 i = 0; 14 | s32 key = -1; 15 | u32 left = 0; 16 | struct eh_ctx *ctx; 17 | u32 right = EH_FRAME_COUNT; 18 | while (i++ < 20 && left < right) { 19 | u64 *mid_eip; 20 | u32 mid = (left + right) / 2; 21 | mid_eip = bpf_map_lookup_elem(&eh_frame_header, &mid); 22 | if (mid_eip == NULL) { 23 | break; 24 | } 25 | if (*mid_eip > eip) { 26 | right = mid; 27 | } else { 28 | key = (s32)mid; 29 | left = mid + 1; 30 | } 31 | } 32 | if (key == -1) { 33 | ERROR("search eh_frame_header fail eip:%lx", eip); 34 | return NULL; 35 | } 36 | ctx = bpf_map_lookup_elem(&eh_frame, &key); 37 | if (ctx == NULL) { 38 | ERROR("search eh_frame fail key:%d", key); 39 | return NULL; 40 | } 41 | return ctx; 42 | } 43 | 44 | struct lua_stack { 45 | u32 count; 46 | lua_State *L; 47 | lua_State *buf[MAX_STACK_DEPTH]; 48 | u8 L_cnt; 49 | u8 cnt[MAX_STACK_DEPTH]; 50 | }; 51 | 52 | struct c_stk_ctx { 53 | u64 cfa; 54 | u64 rip; 55 | u64 rsp; 56 | u64 rbp; 57 | u64 rbx; 58 | struct lua_stack *lua; 59 | struct stack_event *event; 60 | bool exist_lua; 61 | }; 62 | 63 | struct lua_stk_ctx { 64 | int calln; 65 | int lua_index; 66 | StkIdRel stack; 67 | CallInfo *ci; 68 | struct lua_stack *lua; 69 | struct stack_event *event; 70 | }; 71 | 72 | 73 | static __always_inline u64 unwind_get(struct c_stk_ctx *ctx, int reg) { 74 | switch (reg) { 75 | case REG_RBP: 76 | return ctx->rbp; 77 | case REG_RSP: 78 | return ctx->rsp; 79 | case REG_RBX: 80 | return ctx->rbx; 81 | case REG_RA: 82 | return ctx->rip; 83 | default: 84 | ERROR("unwind get reg:%d unspport", reg); 85 | return 0; 86 | } 87 | } 88 | 89 | static __always_inline u64 unwind_reg(struct c_stk_ctx *ctx, int reg, struct eh_reg *reg_conf) { 90 | long ret; 91 | u64 ptr, val; 92 | switch (reg_conf->rule) { 93 | case Undefined: 94 | if (reg == REG_RA) { 95 | return 0; 96 | } else { 97 | return unwind_get(ctx, reg); 98 | } 99 | case SameValue: 100 | return unwind_get(ctx, reg); 101 | case Offset: 102 | ptr = ctx->cfa + reg_conf->data; 103 | ret = bpf_probe_read_user(&val, 8, (void *)ptr); 104 | return (ret == 0) ? val : 0; 105 | case ValOffset: 106 | return ctx->cfa + reg_conf->data; 107 | case Register: 108 | return unwind_get(ctx, reg_conf->data); 109 | case Expression: 110 | case ValExpression: 111 | ERROR("unsupport rule:%d", reg_conf->rule); 112 | return 0; 113 | } 114 | return 0; 115 | } 116 | 117 | 118 | 119 | static int 120 | unwind_c(u32 i, void *ud) 121 | { 122 | struct c_stk_ctx *ctx = (struct c_stk_ctx *)ud; 123 | struct stack_event *event = ctx->event; 124 | struct lua_stack *lua = ctx->lua; 125 | if (i >= ARRAY_SIZE(event->ustack)) { 126 | return LOOP_BREAK; 127 | } 128 | if (ctx->rip >= ctrl.lua_eip_begin && ctx->rip < ctrl.lua_eip_end) { 129 | ctx->exist_lua = true; 130 | } 131 | if (ctx->exist_lua) { 132 | bool find = false; 133 | struct fn_var_pos pos; 134 | for (int i = 0; i < ARRAY_SIZE(ctrl.lua_var_pos); i++) { 135 | pos = ctrl.lua_var_pos[i]; 136 | if (ctx->rip >= pos.eip_begin && ctx->rip < pos.eip_end) { 137 | find = true; 138 | break; 139 | } 140 | } 141 | if (find) { 142 | lua_State *L; 143 | ctx->exist_lua = false; 144 | if (pos.is_mem) { 145 | u64 addr = unwind_get(ctx, pos.reg); 146 | addr += pos.disp; 147 | DEBUG("unwind_c L mem, reg:%d, mem:%d", pos.reg, pos.disp); 148 | int err = bpf_probe_read_user(&L, sizeof(L), (void *)addr); 149 | if (err != 0) { 150 | ERROR("unwind_c read L error:%d addr:%x", err, addr); 151 | return LOOP_BREAK; 152 | } 153 | } else { 154 | L = (lua_State *)unwind_get(ctx, pos.reg); 155 | DEBUG("unwind_c L reg:%d", pos.reg); 156 | } 157 | if (lua->L == NULL) { 158 | ctx->lua->L = L; 159 | ctx->lua->L_cnt = 1; 160 | } else if (lua->L != L) { 161 | size_t x = (size_t)lua->count & 0xffff; 162 | if (x < MAX_STACK_DEPTH) { 163 | lua->buf[x] = lua->L; 164 | lua->cnt[x] = lua->L_cnt; 165 | lua->L = L; 166 | lua->L_cnt = 1; 167 | lua->count++; 168 | } 169 | DEBUG("unwind_c lua stack change:%lx", L); 170 | } else { 171 | ctx->lua->L_cnt++; 172 | } 173 | } 174 | } 175 | event->ustack[i] = ctx->rip; 176 | event->ustack_sz++; 177 | struct eh_ctx *eh_ctx = search(ctx->rip); 178 | if (eh_ctx == NULL) { 179 | return LOOP_BREAK; 180 | } 181 | //DEBUG("rip1:%lx rule:%d, %d", ctx->rip, eh_ctx->cfa_reg, eh_ctx->cfa_off); 182 | if (eh_ctx->cfa_rule == CFA_Register) { 183 | switch (eh_ctx->cfa_reg) { 184 | case REG_RBP: //rbp 185 | ctx->cfa = ctx->rbp + eh_ctx->cfa_off; 186 | break; 187 | case REG_RSP: //rsp 188 | ctx->cfa = ctx->rsp + eh_ctx->cfa_off; 189 | break; 190 | default: 191 | ERROR("unsupport cfa_reg:%d", eh_ctx->cfa_reg); 192 | break; 193 | } 194 | } else { 195 | ERROR("unsupport cfa_rule:%d", eh_ctx->cfa_rule); 196 | return LOOP_BREAK; 197 | } 198 | ctx->rbp = unwind_reg(ctx, REG_RBP, &eh_ctx->regs[REG_RBP]); 199 | ctx->rbx = unwind_reg(ctx, REG_RBX, &eh_ctx->regs[REG_RBX]); 200 | ctx->rip = unwind_reg(ctx, REG_RA, &eh_ctx->regs[REG_RA]); 201 | ctx->rsp = ctx->cfa; 202 | if (ctx->rip == 0) 203 | return LOOP_BREAK; 204 | return LOOP_CONTINUE; 205 | } 206 | 207 | struct foo_iter { 208 | struct stack_event *event; 209 | }; 210 | 211 | static int foo_memcpy_iter(int i, void *ud) 212 | { 213 | char n; 214 | struct foo_iter *iter = (struct foo_iter *)ud; 215 | struct stack_event *event = iter->event; 216 | if (i >= ARRAY_SIZE(event->ustack)) { 217 | return LOOP_BREAK; 218 | } 219 | event->ustack[i] = (uintptr_t)ud; 220 | event->ustack_sz++; 221 | return LOOP_CONTINUE; 222 | } 223 | 224 | struct { 225 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 226 | __type(key, u32); 227 | __type(value, CallInfo); 228 | __uint(max_entries, 1); 229 | } tmp_call_info SEC(".maps"); 230 | 231 | DECLARE_TMP_VAR(CallInfo, ci); 232 | 233 | static __always_inline int lua_ci_stk(void *ptr, CallInfo **ci, StkIdRel *stk) { 234 | lua_State L; 235 | int err = bpf_probe_read_user(&L, sizeof(L), ptr); 236 | if (err == 0) { 237 | *ci = L.ci; 238 | *stk = L.stack; 239 | } else { 240 | ERROR("lua_ci_stk read L error:%d", err); 241 | } 242 | return err; 243 | } 244 | 245 | static int 246 | unwind_lua(u32 i, void *ud) 247 | { 248 | int err; 249 | void *addr; 250 | u32 zero = 0; 251 | FETCH_TMP_VAR(CallInfo, ci, LOOP_BREAK); 252 | struct lua_stk_ctx *ctx = (struct lua_stk_ctx *)ud; 253 | DEBUG("----unwind_lua ci:%lx", ctx->ci); 254 | if ((ctx->event->lstack_sz & 0xffff) >= ARRAY_SIZE(ctx->event->lstack)) { 255 | return LOOP_BREAK; 256 | } 257 | if (ctx->ci == NULL) { 258 | DEBUG("unwind_lua read ci:%d", ctx->lua_index); 259 | size_t i = (size_t)ctx->lua_index & 0xffff; 260 | if (i >= ctx->lua->count || i > MAX_STACK_DEPTH) { 261 | return LOOP_BREAK; 262 | } 263 | lua_State *L = ctx->lua->buf[i]; 264 | ctx->calln = ctx->lua->cnt[i]; 265 | DEBUG("unwind_lua begin ci:%d calln:%d", ctx->lua_index, ctx->calln); 266 | int err = lua_ci_stk(L, &ctx->ci, &ctx->stack); 267 | if (err != 0) { 268 | DEBUG("unwind_lua read L error:%d", err); 269 | return LOOP_BREAK; 270 | } 271 | //try extend more one lua frame for race condition 272 | if (ctx->lua_index == 0 && ctx->ci != NULL) { 273 | err = bpf_probe_read_user(ci, sizeof(*ci), (void *)ctx->ci); 274 | if (err != 0) { 275 | ERROR("unwind_lua read ci error:%d", err); 276 | return LOOP_BREAK; 277 | } 278 | if (ci->next != NULL) { 279 | ctx->ci = ci->next; 280 | ctx->calln++; 281 | } 282 | } 283 | ctx->lua_index++; 284 | } 285 | err = bpf_probe_read_user(ci, sizeof(*ci), (void *)ctx->ci); 286 | if (err != 0) { 287 | ERROR("unwind_lua read ci error:%d", err); 288 | return LOOP_BREAK; 289 | } 290 | addr = lua_func_addr(ctx->stack, ci); 291 | if (addr == NULL) { 292 | ctx->ci = NULL; 293 | DEBUG("unwind_lua lua_func_addr fail:%lx", ctx->ci); 294 | return LOOP_CONTINUE; 295 | } 296 | DEBUG("unwind_lua i:%d luaV_execute :%lx calln:%d prev:%lx", i, addr, ctx->calln, ci->previous); 297 | size_t j = (size_t)ctx->event->lstack_sz & 0xffff; 298 | if (j < ARRAY_SIZE(ctx->event->lstack)) { 299 | ctx->event->lstack[j] = (u64)addr; 300 | ctx->event->lstack_sz++; 301 | } 302 | j = (size_t)ctx->event->lstack_sz & 0xffff; 303 | if (j < ARRAY_SIZE(ctx->event->lstack) && lua_ci_is_fresh(ci)) { 304 | ctx->event->lstack[j] = 0; 305 | ctx->event->lstack_sz++; 306 | if (--ctx->calln <= 0) { 307 | ci->previous = NULL; 308 | } 309 | } 310 | ctx->ci = ci->previous; 311 | return LOOP_CONTINUE; 312 | } 313 | 314 | DECLARE_TMP_VAR(struct stack_event, stack_event); 315 | DECLARE_TMP_VAR(struct lua_stack, lua_stack); 316 | 317 | SEC("perf_event") 318 | int profile(struct bpf_perf_event_data *perf_ctx) 319 | { 320 | FETCH_TMP_VAR(struct stack_event, stack_event, 1) 321 | FETCH_TMP_VAR(struct lua_stack, lua_stack, 1) 322 | union { 323 | struct bpf_pidns_info ns; 324 | struct c_stk_ctx c; 325 | struct lua_stk_ctx l; 326 | } ctx; 327 | bpf_get_ns_current_pid_tgid(ctrl.dev, ctrl.ino, &ctx.ns, sizeof(ctx.ns)); 328 | if (ctx.ns.tgid != (u32)ctrl.target_pid) 329 | return 0; 330 | u32 zero = 0; 331 | u32 pid = ctx.ns.pid; 332 | int cpu_id = bpf_get_smp_processor_id(); 333 | stack_event->pid = pid; 334 | stack_event->cpu_id = cpu_id; 335 | if (bpf_get_current_comm(stack_event->comm, sizeof(stack_event->comm))) 336 | stack_event->comm[0] = 0; 337 | stack_event->kstack_sz = bpf_get_stack(perf_ctx, stack_event->kstack, sizeof(stack_event->kstack), 0); 338 | bpf_user_pt_regs_t *regs = &perf_ctx->regs; 339 | if (in_kernel(PT_REGS_IP(regs))) { 340 | if (!retrieve_task_registers(&ctx.c.rip, &ctx.c.rsp, &ctx.c.rbp, &ctx.c.rbx)) { 341 | return 1; 342 | } 343 | } else { 344 | ctx.c.rip = PT_REGS_IP(regs); 345 | ctx.c.rsp = PT_REGS_SP(regs); 346 | ctx.c.rbp = PT_REGS_FP(regs); 347 | //TODO: portable 348 | ctx.c.rbx = regs->bx; 349 | } 350 | ctx.c.cfa = ctx.c.rsp; 351 | ctx.c.lua = lua_stack; 352 | ctx.c.event = stack_event; 353 | ctx.c.exist_lua = false; 354 | stack_event->ustack_sz = 0; 355 | lua_stack->L = NULL; 356 | lua_stack->L_cnt = 0; 357 | lua_stack->count = 0; 358 | //unwind user space 359 | DEBUG("---------profile unwind start:%lx", ctx.c.rip); 360 | long n = bpf_loop(MAX_STACK_DEPTH, unwind_c, &ctx, 0); 361 | DEBUG("---------profile unwind end:%lx", ctx.c.rip); 362 | if (n > 0) { 363 | stack_event->ustack_sz *= sizeof(u64); 364 | } else { 365 | ERROR("profile unwind loop fail:%d", n); 366 | } 367 | //unwind lua_State 368 | if (ctx.c.lua->L != NULL && (ctx.c.lua->count & 0xffff) < MAX_STACK_DEPTH) { 369 | int i = ctx.c.lua->count & 0xffff; 370 | ctx.c.lua->buf[i] = ctx.c.lua->L; 371 | ctx.c.lua->cnt[i] = ctx.c.lua->L_cnt; 372 | ctx.c.lua->count++; 373 | } 374 | stack_event->lstack_sz = 0; 375 | DEBUG("profile unwind lua count:%d", ctx.c.lua->count); 376 | if (ctx.c.lua->count > 0) { 377 | ctx.l.lua_index = 0; 378 | ctx.l.lua = lua_stack; 379 | ctx.l.ci = NULL; 380 | ctx.l.event = stack_event; 381 | n = bpf_loop(MAX_STACK_DEPTH, unwind_lua, &ctx.l, 0); 382 | stack_event->lstack_sz *= sizeof(u64); 383 | } 384 | stack_event->stk_id = get_stack_id(stack_event); 385 | #if LOG_LEVEL <= LOG_DEBUG 386 | struct stack_event *new_event = bpf_ringbuf_reserve(&events, sizeof(*stack_event), 0); 387 | if (!new_event) 388 | return 1; 389 | new_event->event_type = EVENT_TRACE; 390 | new_event->pid = stack_event->pid; 391 | new_event->cpu_id = stack_event->cpu_id; 392 | new_event->comm[0] = stack_event->comm[0]; 393 | new_event->kstack_sz = stack_event->kstack_sz; 394 | new_event->ustack_sz = stack_event->ustack_sz; 395 | new_event->lstack_sz = stack_event->lstack_sz; 396 | new_event->stk_id = stack_event->stk_id; 397 | for (int i = 0; i < MAX_STACK_DEPTH; i++) { 398 | new_event->kstack[i] = stack_event->kstack[i]; 399 | new_event->ustack[i] = stack_event->ustack[i]; 400 | new_event->lstack[i] = stack_event->lstack[i]; 401 | } 402 | bpf_ringbuf_submit(new_event, 0); 403 | #endif 404 | return 0; 405 | } -------------------------------------------------------------------------------- /src/bpf/profile.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROFILE_H 2 | #define _PROFILE_H 3 | 4 | #include "vmlinux.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "hash.h" 10 | 11 | #define LOG_DEBUG 1 12 | #define LOG_INFO 2 13 | #define LOG_ERROR 3 14 | #define LOG_LEVEL LOG_ERROR 15 | 16 | #define LOOP_CONTINUE (0) 17 | #define LOOP_BREAK (1) 18 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 19 | u32 ZERO = 0; 20 | u32 STRINGS_MAP_SIZE; 21 | u32 STACKS_MAP_SIZE; 22 | u32 EH_FRAME_COUNT; 23 | #define STR_MAX_SIZE (64) 24 | 25 | #ifndef TASK_COMM_LEN 26 | #define TASK_COMM_LEN 16 27 | #endif 28 | 29 | #ifndef MAX_STACK_DEPTH 30 | #define MAX_STACK_DEPTH 128 31 | #endif 32 | 33 | enum { 34 | REG_RBX = 3, 35 | REG_RDI = 5, 36 | REG_RBP = 6, 37 | REG_RSP = 7, 38 | REG_RA = 16, 39 | REG_COUNT, 40 | }; 41 | 42 | #define LUA_ADDR (1ull << 63) 43 | 44 | #define MARK_LUA_ADDR(a) ((void *)((uintptr_t)(a) | LUA_ADDR)) 45 | 46 | #ifndef memcmp 47 | #define memcmp(s1, s2, n) __builtin_memcmp((s1), (s2), (n)) 48 | #endif 49 | 50 | #ifndef memset 51 | #define memset(dest, chr, n) __builtin_memset((dest), (chr), (n)) 52 | #endif 53 | 54 | #ifndef memcpy 55 | #define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n)) 56 | #endif 57 | 58 | #ifndef memmove 59 | #define memmove(dest, src, n) __builtin_memmove((dest), (src), (n)) 60 | #endif 61 | 62 | #if LOG_LEVEL <= LOG_DEBUG 63 | #define DEBUG(...) bpf_printk("D "__VA_ARGS__) 64 | #else 65 | #define DEBUG(...) (void)0 66 | #endif 67 | 68 | #if LOG_LEVEL <= LOG_INFO 69 | #define INFO(...) bpf_printk("I "__VA_ARGS__) 70 | #else 71 | #define INFO(...) (void)0 72 | #endif 73 | 74 | #if LOG_LEVEL <= LOG_ERROR 75 | #define ERROR(...) bpf_printk("E "__VA_ARGS__) 76 | #else 77 | #define ERROR(...) (void)0 78 | #endif 79 | 80 | #define DECLARE_TMP_VAR(typ, name) \ 81 | struct {\ 82 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); \ 83 | __type(key, u32); \ 84 | __type(value, typ); \ 85 | __uint(max_entries, 1); \ 86 | } tmp_##name SEC(".maps") 87 | 88 | #define FETCH_TMP_VAR(typ, name, ret) \ 89 | typ *name = bpf_map_lookup_elem(&tmp_##name, &ZERO); \ 90 | if (name == NULL) \ 91 | return ret; 92 | 93 | enum event_type { 94 | EVENT_NONE = 0, 95 | EVENT_TRACE = 1, 96 | EVENT_STACK = 2, 97 | EVENT_STRING = 3, 98 | }; 99 | 100 | enum reg_rule { 101 | Undefined = 0, 102 | SameValue = 1, 103 | Offset = 2, 104 | ValOffset = 3, 105 | Register = 4, 106 | Expression = 5, 107 | ValExpression = 6, 108 | }; 109 | 110 | enum cfa_rule { 111 | CFA_Undefined = 0, 112 | CFA_Register = 2, 113 | CFA_Expression = 3, 114 | }; 115 | 116 | typedef __u64 call_stack_t[MAX_STACK_DEPTH]; 117 | 118 | struct stack_event { 119 | u8 event_type; 120 | u32 pid; 121 | u32 cpu_id; 122 | u32 stk_id; 123 | char comm[TASK_COMM_LEN]; 124 | s32 kstack_sz; 125 | s32 ustack_sz; 126 | s32 lstack_sz; 127 | call_stack_t kstack; 128 | call_stack_t ustack; 129 | call_stack_t lstack; 130 | }; 131 | 132 | struct stack_count { 133 | u8 event_type; 134 | u32 id; 135 | u32 ver; 136 | u32 count; 137 | u32 hash; 138 | s32 kstack_sz; 139 | s32 ustack_sz; 140 | s32 lstack_sz; 141 | call_stack_t kstack; 142 | call_stack_t ustack; 143 | call_stack_t lstack; 144 | }; 145 | 146 | struct string { 147 | u8 event_type; 148 | u8 len; 149 | u32 id; 150 | u32 ver; 151 | u32 hash; 152 | char data[STR_MAX_SIZE]; 153 | }; 154 | 155 | struct eh_reg { 156 | enum reg_rule rule; 157 | s32 data; 158 | }; 159 | 160 | typedef struct eh_ctx { 161 | u64 eip; 162 | u32 size; 163 | enum cfa_rule cfa_rule; 164 | u32 cfa_reg; 165 | s64 cfa_off; 166 | struct eh_reg regs[REG_COUNT]; 167 | } eh_ctx; 168 | 169 | struct { 170 | __uint(type, BPF_MAP_TYPE_ARRAY); 171 | __type(key, u32); 172 | __type(value, u64); 173 | } eh_frame_header SEC(".maps"); 174 | 175 | struct { 176 | __uint(type, BPF_MAP_TYPE_ARRAY); 177 | __type(key, u32); 178 | __type(value, eh_ctx); 179 | } eh_frame SEC(".maps"); 180 | 181 | 182 | struct { 183 | __uint(type, BPF_MAP_TYPE_RINGBUF); 184 | __uint(max_entries, 256 * 1024); 185 | } events SEC(".maps"); 186 | 187 | struct { 188 | __uint(type, BPF_MAP_TYPE_ARRAY); 189 | __type(key, u32); 190 | __type(value, struct string); 191 | } strings SEC(".maps"); 192 | 193 | struct { 194 | __uint(type, BPF_MAP_TYPE_ARRAY); 195 | __type(key, u32); 196 | __type(value, struct stack_count); 197 | } stacks SEC(".maps"); 198 | 199 | struct fn_var_pos { 200 | u64 eip_begin; 201 | u64 eip_end; 202 | bool is_mem; 203 | u8 reg; //reg id 204 | s32 disp; //disp value 205 | }; 206 | 207 | struct ctrl { 208 | u64 lua_eip_begin; 209 | u64 lua_eip_end; 210 | struct fn_var_pos lua_var_pos[3]; 211 | pid_t target_pid; 212 | unsigned long long dev; 213 | unsigned long long ino; 214 | }; 215 | 216 | struct ctrl ctrl; 217 | 218 | // Dummy instance to get skeleton to generate definition for `struct event` 219 | union { 220 | enum event_type _x1; 221 | struct eh_ctx _x2; 222 | struct stack_event _x3; 223 | struct stack_count _x4; 224 | struct string _x5; 225 | } dummy; 226 | 227 | struct str_cache_ctx { 228 | const struct string *src; 229 | struct string *dst; 230 | }; 231 | 232 | static int str_cache_cpy_iter(int i, void *ud) 233 | { 234 | struct str_cache_ctx *ctx = (struct str_cache_ctx *)ud; 235 | const struct string *src = ctx->src; 236 | struct string *dst = ctx->dst; 237 | size_t n = (size_t)i & 0xffff; 238 | if (n >= ARRAY_SIZE(dst->data)) { 239 | return LOOP_BREAK; 240 | } 241 | if (n >= ARRAY_SIZE(src->data)) { 242 | return LOOP_BREAK; 243 | } 244 | dst->data[n] = src->data[n]; 245 | return LOOP_CONTINUE; 246 | } 247 | 248 | static int str_cache_cmp_iter(int i, void *ud) 249 | { 250 | char a, b; 251 | struct str_cache_ctx *ctx = (struct str_cache_ctx *)ud; 252 | const struct string *src = ctx->src; 253 | struct string *dst = ctx->dst; 254 | size_t n = (size_t)i & 0xffff; 255 | if (n >= ARRAY_SIZE(dst->data)) { 256 | return LOOP_BREAK; 257 | } 258 | if (n >= ARRAY_SIZE(src->data)) { 259 | return LOOP_BREAK; 260 | } 261 | a = src->data[n]; 262 | b = dst->data[n]; 263 | return (a != b) ? LOOP_BREAK : LOOP_CONTINUE; 264 | } 265 | 266 | static __always_inline bool cache_str_cmp_eq(struct string *a, struct string *b) 267 | { 268 | u32 zero = 0; 269 | if (a->len != b->len || a->hash != b->hash) { 270 | return false; 271 | } 272 | struct str_cache_ctx ctx = { 273 | .src = a, 274 | .dst = b, 275 | }; 276 | size_t cmp_count = a->len + 1; 277 | return (bpf_loop(a->len + 1, str_cache_cmp_iter, &ctx, 0) == (a->len + 1)); 278 | } 279 | 280 | static __always_inline void cache_str_cpy(struct string *dst, const struct string *src) 281 | { 282 | dst->len = src->len; 283 | dst->hash = src->hash; 284 | struct str_cache_ctx ctx = { 285 | .src = src, 286 | .dst = dst, 287 | }; 288 | bpf_loop(dst->len, str_cache_cpy_iter, &ctx, 0); 289 | } 290 | 291 | DECLARE_TMP_VAR(struct string, str_buf); 292 | static __always_inline u32 string_to_id(char *str, u32 len) 293 | { 294 | int err; 295 | u32 hash, i; 296 | struct string *cache; 297 | FETCH_TMP_VAR(struct string, str_buf, 0) 298 | if (len > sizeof(str_buf->data)) { 299 | str += len - sizeof(str_buf->data); 300 | len = sizeof(str_buf->data); 301 | } 302 | len &= 0xfffff; 303 | str_buf->len = len; 304 | err = bpf_probe_read_user(str_buf->data, str_buf->len, str); 305 | if (err != 0) { 306 | ERROR("string_to_id read string fail"); 307 | return 0; 308 | } 309 | str_buf->hash = (u32)((uintptr_t)str); 310 | i = str_buf->hash % STRINGS_MAP_SIZE; 311 | cache = (struct string *)bpf_map_lookup_elem(&strings, &i); 312 | if (cache == NULL) { 313 | ERROR("string_to_id cache:%d is NULL", i); 314 | return 0; 315 | } 316 | if (cache_str_cmp_eq(cache, str_buf)) { 317 | DEBUG("string_to_id i:%d, len:%u id:%d", i, cache->len, cache->id); 318 | return cache->id; 319 | } 320 | if (cache->id != 0) { //skip the first empty cache 321 | struct string *event; 322 | event = (struct string *)bpf_ringbuf_reserve(&events, sizeof(*event), 0); 323 | if (event == NULL) { 324 | bpf_printk("string_to_id: alloc ringbuf for '%s' fail", str); 325 | return 0; 326 | } 327 | *event = *cache; 328 | event->event_type = EVENT_STRING; 329 | bpf_ringbuf_submit(event, 0); 330 | } 331 | cache_str_cpy(cache, str_buf); 332 | cache->ver++; 333 | cache->id = cache->ver * STRINGS_MAP_SIZE + i; 334 | DEBUG("string_to_id i:%d, len:%u id:%d", i, cache->len, cache->id); 335 | return cache->id; 336 | } 337 | 338 | struct stack_ctx { 339 | const struct stack_event *stk; 340 | struct stack_count *counter; 341 | }; 342 | 343 | static int stack_cmp_iter(int i, void *ud) 344 | { 345 | u64 a, b; 346 | struct stack_ctx *ctx = (struct stack_ctx *)ud; 347 | size_t n = (size_t)i & 0xffff; 348 | if (n >= MAX_STACK_DEPTH) { 349 | return LOOP_BREAK; 350 | } 351 | if (n < ctx->stk->kstack_sz && ctx->stk->kstack[n] != ctx->counter->kstack[n]) { 352 | return LOOP_BREAK; 353 | } 354 | if (n < ctx->stk->ustack_sz && ctx->stk->ustack[n] != ctx->counter->ustack[n]) { 355 | return LOOP_BREAK; 356 | } 357 | if (n < ctx->stk->lstack_sz && ctx->stk->lstack[n] != ctx->counter->lstack[n]) { 358 | return LOOP_BREAK; 359 | } 360 | return LOOP_CONTINUE; 361 | } 362 | 363 | static int stack_cpy_iter(int i, void *ud) 364 | { 365 | u64 a, b; 366 | struct stack_ctx *ctx = (struct stack_ctx *)ud; 367 | size_t n = (size_t)i & 0xffff; 368 | if (n >= MAX_STACK_DEPTH) { 369 | return LOOP_BREAK; 370 | } 371 | if (n < ctx->stk->kstack_sz) { 372 | ctx->counter->kstack[n] = ctx->stk->kstack[n]; 373 | } 374 | if (n < ctx->stk->ustack_sz) { 375 | ctx->counter->ustack[n] = ctx->stk->ustack[n]; 376 | } 377 | if (n < ctx->stk->lstack_sz) { 378 | ctx->counter->lstack[n] = ctx->stk->lstack[n]; 379 | } 380 | return LOOP_CONTINUE; 381 | } 382 | 383 | static __always_inline bool stack_cmp_eq(struct stack_count *counter, struct stack_event *stk, u32 stk_hash) 384 | { 385 | if ( 386 | counter->hash != stk_hash || 387 | counter->kstack_sz != stk->kstack_sz || 388 | counter->ustack_sz != stk->ustack_sz || 389 | counter->lstack_sz != stk->lstack_sz) { 390 | return false; 391 | } 392 | struct stack_ctx ctx = { 393 | .stk = stk, 394 | .counter = counter, 395 | }; 396 | u32 n = stk->kstack_sz; 397 | if (n < stk->ustack_sz) { 398 | n = stk->ustack_sz; 399 | } 400 | if (n < stk->lstack_sz) { 401 | n = stk->lstack_sz; 402 | } 403 | n = n / sizeof(u64) + 1; 404 | return bpf_loop(n, stack_cmp_iter, &ctx, 0) == n; 405 | } 406 | 407 | static __always_inline void stack_cpy(struct stack_count *counter, struct stack_event *stk) 408 | { 409 | counter->kstack_sz = stk->kstack_sz; 410 | counter->ustack_sz = stk->ustack_sz; 411 | counter->lstack_sz = stk->lstack_sz; 412 | struct stack_ctx ctx = { 413 | .stk = stk, 414 | .counter = counter, 415 | }; 416 | u32 n = stk->kstack_sz; 417 | if (n < stk->ustack_sz) { 418 | n = stk->ustack_sz; 419 | } 420 | if (n < stk->lstack_sz) { 421 | n = stk->lstack_sz; 422 | } 423 | bpf_loop(n, stack_cpy_iter, &ctx, 0); 424 | } 425 | 426 | static __always_inline u32 get_stack_id(struct stack_event *event) 427 | { 428 | u32 hash = 0; 429 | struct stack_count *counter; 430 | hash = murmur_hash2((u32 *)event->kstack, event->kstack_sz / sizeof(u32), hash); 431 | hash = murmur_hash2((u32 *)event->ustack, event->ustack_sz / sizeof(u32), hash); 432 | hash = murmur_hash2((u32 *)event->lstack, event->lstack_sz / sizeof(u32), hash); 433 | u32 i = hash % STACKS_MAP_SIZE; 434 | counter = bpf_map_lookup_elem(&stacks, &i); 435 | if (counter == NULL) { 436 | ERROR("get_stack_id counter:%d is NULL", hash); 437 | return 0; 438 | } 439 | DEBUG("get_stack_id %u, i:%d h:%u", hash, i, counter->hash); 440 | if (stack_cmp_eq(counter, event, hash)) { 441 | counter->count++; 442 | DEBUG("get_stack_id id:%d count:%d", counter->id, counter->count); 443 | return counter->id; 444 | } 445 | if (counter->id != 0) { 446 | struct stack_count *event; 447 | event = (struct stack_count *)bpf_ringbuf_reserve(&events, sizeof(*event), 0); 448 | if (event == NULL) { 449 | ERROR("get_stack_id alloc ringbuf fail"); 450 | return 0; 451 | } 452 | event->event_type = EVENT_STACK; 453 | event->id = counter->id; 454 | event->count = counter->count; 455 | event->hash = counter->hash; 456 | event->kstack_sz = counter->kstack_sz; 457 | event->ustack_sz = counter->ustack_sz; 458 | event->lstack_sz = counter->lstack_sz; 459 | memcpy(event->kstack, counter->kstack, MAX_STACK_DEPTH * sizeof(u64)); 460 | memcpy(event->ustack, counter->ustack, MAX_STACK_DEPTH * sizeof(u64)); 461 | memcpy(event->lstack, counter->lstack, MAX_STACK_DEPTH * sizeof(u64)); 462 | bpf_ringbuf_submit(event, 0); 463 | } 464 | counter->ver++; 465 | counter->id = counter->ver * STACKS_MAP_SIZE + i; 466 | counter->count = 1; 467 | counter->hash = hash; 468 | stack_cpy(counter, event); 469 | DEBUG("get_stack_id stack i:%d id:%d count:%d", i, counter->id, counter->count); 470 | return counter->id; 471 | } 472 | 473 | // Values for x86_64 as of 6.0.18-200. 474 | #define TOP_OF_KERNEL_STACK_PADDING 0 475 | #define THREAD_SIZE_ORDER 2 476 | #define PAGE_SHIFT 12 477 | #define PAGE_SIZE (1UL << PAGE_SHIFT) 478 | #define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER) 479 | 480 | // Kernel addresses have the top bits set. 481 | static __always_inline bool in_kernel(u64 ip) { 482 | return ip & (1UL << 63); 483 | } 484 | 485 | // kthreads mm's is not set. 486 | // We don't check for the return value of `retrieve_task_registers`, it's 487 | // caller due the verifier not liking that code. 488 | static __always_inline bool is_kthread() { 489 | struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 490 | if (task == NULL) { 491 | return false; 492 | } 493 | void *mm; 494 | int err = bpf_probe_read_kernel(&mm, 8, &task->mm); 495 | if (err) { 496 | ERROR("is_kthread bpf_probe_read_kernel failed with %d", err); 497 | return false; 498 | } 499 | return mm == NULL; 500 | } 501 | 502 | // avoid R0 invalid mem access 'scalar' 503 | // Port of `task_pt_regs` in BPF. 504 | static __always_inline bool retrieve_task_registers(u64 *ip, u64 *sp, u64 *bp, u64 *rbx) { 505 | int err; 506 | void *stack; 507 | if (ip == NULL || sp == NULL || bp == NULL) { 508 | return false; 509 | } 510 | struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 511 | if (task == NULL) { 512 | return false; 513 | } 514 | if (is_kthread()) { 515 | return false; 516 | } 517 | 518 | err = bpf_probe_read_kernel(&stack, 8, &task->stack); 519 | if (err) { 520 | ERROR("retrieve_task_registers bpf_probe_read_kernel failed with %d", err); 521 | return false; 522 | } 523 | void *ptr = stack + THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING; 524 | struct pt_regs *regs = ((struct pt_regs *)ptr) - 1; 525 | 526 | *ip = PT_REGS_IP_CORE(regs); 527 | *sp = PT_REGS_SP_CORE(regs); 528 | *bp = PT_REGS_FP_CORE(regs); 529 | *rbx = BPF_CORE_READ(regs, bx); 530 | return true; 531 | } 532 | 533 | #endif 534 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | mod perf; 4 | mod args; 5 | 6 | fn main() { 7 | let args = args::Args::parse(); 8 | let mut perf = perf::Perf::new(args.pid); 9 | let _ = perf.exec(&args); 10 | } 11 | -------------------------------------------------------------------------------- /src/perf.rs: -------------------------------------------------------------------------------- 1 | mod maps; 2 | mod eh; 3 | mod var; 4 | mod syscall; 5 | 6 | use std::fs; 7 | use std::fs::File; 8 | use std::io::prelude::*; 9 | use std::sync::Arc; 10 | use core::time::Duration; 11 | use std::mem::size_of; 12 | use std::sync::atomic::AtomicBool; 13 | use std::sync::atomic::Ordering; 14 | use std::vec::Vec; 15 | use blazesym::symbolize; 16 | use gimli::X86_64; 17 | use goblin::elf::Elf; 18 | use libc::pid_t; 19 | use std::os::linux::fs::MetadataExt; 20 | use std::collections::HashMap; 21 | 22 | use anyhow::bail; 23 | use anyhow::Result; 24 | use plain::Plain; 25 | use libbpf_rs::MapFlags; 26 | use libbpf_rs::skel::OpenSkel; 27 | use libbpf_rs::skel::SkelBuilder; 28 | 29 | use nix::unistd::close; 30 | 31 | use tracing::subscriber::set_global_default as set_global_subscriber; 32 | use tracing_subscriber::filter::LevelFilter; 33 | use tracing_subscriber::fmt::format::FmtSpan; 34 | use tracing_subscriber::fmt::time::SystemTime; 35 | use tracing_subscriber::FmtSubscriber; 36 | 37 | use std::io::Error; 38 | use std::mem; 39 | use std::path::PathBuf; 40 | 41 | const STRINGS_MAP_SIZE: u32 = 2048; 42 | const STACKS_MAP_SIZE: u32 = 1024 * 10; 43 | const LUA_MASK: u64 = 1 << 63; 44 | 45 | mod bpf { 46 | include!(concat!(env!("OUT_DIR"), "/profile.skel.rs")); 47 | } 48 | 49 | use bpf::*; 50 | 51 | unsafe impl Plain for profile_bss_types::stack_event {} 52 | unsafe impl Plain for profile_bss_types::reg_rule {} 53 | unsafe impl Plain for profile_bss_types::eh_reg {} 54 | unsafe impl Plain for profile_bss_types::eh_ctx {} 55 | unsafe impl Plain for profile_bss_types::ctrl {} 56 | 57 | #[derive(Debug, Eq, PartialEq, Hash)] 58 | struct StackFrame { 59 | kstack: Vec, 60 | ustack: Vec, 61 | lstack: Vec, 62 | } 63 | 64 | #[derive(Debug)] 65 | enum LuaFrame { 66 | Lua(String), 67 | C(u64), 68 | } 69 | 70 | #[derive(Clone)] 71 | struct SymInfo { 72 | addr: u64, 73 | offset: u64, 74 | name: String, 75 | } 76 | 77 | impl StackFrame { 78 | fn convert_stack_vec(size: i32, kstack: &[u64; 128]) -> Vec { 79 | let mut stack = Vec::new(); 80 | let size = size / std::mem::size_of::() as i32; 81 | for i in 0..size { 82 | stack.push(kstack[i as usize]); 83 | } 84 | stack 85 | } 86 | fn from_event(event: &profile_bss_types::stack_event) -> Self { 87 | Self { 88 | kstack: Self::convert_stack_vec(event.kstack_sz, &event.kstack), 89 | ustack: Self::convert_stack_vec(event.ustack_sz, &event.ustack), 90 | lstack: Self::convert_stack_vec(event.lstack_sz, &event.lstack), 91 | } 92 | } 93 | fn from_count(stk: &profile_bss_types::stack_count) -> Self { 94 | Self { 95 | kstack: Self::convert_stack_vec(stk.kstack_sz, &stk.kstack), 96 | ustack: Self::convert_stack_vec(stk.ustack_sz, &stk.ustack), 97 | lstack: Self::convert_stack_vec(stk.lstack_sz, &stk.lstack), 98 | } 99 | } 100 | } 101 | 102 | pub struct LuaFn { 103 | pub addr: u64, 104 | pub size: u64, 105 | pub vars: Vec, 106 | } 107 | 108 | 109 | fn build_eh_ctx(pid: pid_t) ->(std::vec::Vec, LuaFn) { 110 | let mut lua_fn = LuaFn{ 111 | addr: 0, 112 | size: 0, 113 | vars: Vec::new(), 114 | }; 115 | let mut eh_all_ctx = Vec::new(); 116 | let maps = maps::Files::from_pid(pid as pid_t); 117 | for (path, maps) in maps.iter() { 118 | if path.contains("(deleted)") { 119 | continue; 120 | } 121 | let elf_data = std::fs::read(path).unwrap(); 122 | let elf = Elf::parse(&elf_data).unwrap(); 123 | let eh_ctx = eh::EhInstrContext::from_elf(&elf, &elf_data); 124 | if let Some(lua_fde) = eh_ctx.get_fde_desc(&"luaV_execute+0x0".to_string()) { 125 | assert!(lua_fde.size < std::u32::MAX as u64); 126 | lua_fn.addr = maps.translate(lua_fde.loc); 127 | lua_fn.size = lua_fde.size as u64; 128 | let fns_var_pos = var::collect_lua_fn_var_pos(&elf, &elf_data); 129 | for var_pos in fns_var_pos.iter() { 130 | let addr = maps.translate(var_pos.addr); 131 | let fn_var_pos = var::FnVarPos { 132 | addr: addr, 133 | size: var_pos.size as u64, 134 | pos: var_pos.pos.clone(), 135 | }; 136 | lua_fn.vars.push(fn_var_pos); 137 | } 138 | } 139 | for inst in eh_ctx.iter() { 140 | let eip = maps.translate(inst.loc); 141 | let mut bss_eh = profile_bss_types::eh_ctx { 142 | eip: eip as u64, 143 | size: inst.size as u32, 144 | cfa_rule: profile_bss_types::cfa_rule::CFA_Undefined, 145 | cfa_reg: 0, 146 | cfa_off: 0, 147 | regs: std::array::from_fn(|_| profile_bss_types::eh_reg { 148 | rule: profile_bss_types::reg_rule::Undefined, 149 | data: 0, 150 | }), 151 | __pad_20: [0;4], 152 | }; 153 | match &inst.cfa_reg { 154 | eh::CfaRule::Undefined => { 155 | bss_eh.cfa_rule = profile_bss_types::cfa_rule::CFA_Undefined; 156 | } 157 | eh::CfaRule::Register(r) => { 158 | bss_eh.cfa_rule = profile_bss_types::cfa_rule::CFA_Register; 159 | bss_eh.cfa_reg = r.0 as u32; 160 | bss_eh.cfa_off = inst.cfa_off; 161 | } 162 | eh::CfaRule::Expression(_e) => { 163 | bss_eh.cfa_rule = profile_bss_types::cfa_rule::CFA_Expression; 164 | bss_eh.cfa_reg = 0; 165 | bss_eh.cfa_off = 0; 166 | } 167 | } 168 | for (i, reg) in inst.regs.iter().enumerate() { 169 | match reg { 170 | eh::RegRule::Undefined => { 171 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::Undefined; 172 | } 173 | eh::RegRule::SameValue => { 174 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::SameValue; 175 | } 176 | eh::RegRule::Offset(off) => { 177 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::Offset; 178 | bss_eh.regs[i].data = *off as i32; 179 | } 180 | eh::RegRule::Register(r) => { 181 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::Register; 182 | bss_eh.regs[i].data = r.0 as i32; 183 | } 184 | eh::RegRule::ValOffset(val) => { 185 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::ValOffset; 186 | bss_eh.regs[i].data = *val as i32; 187 | } 188 | eh::RegRule::Expression(_e) => { //TODO: 189 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::Expression; 190 | bss_eh.regs[i].data = 0; 191 | } 192 | eh::RegRule::ValExpression(_e) => { //TODO: 193 | bss_eh.regs[i].rule = profile_bss_types::reg_rule::ValExpression; 194 | bss_eh.regs[i].data = 0; 195 | } 196 | } 197 | } 198 | eh_all_ctx.push(bss_eh); 199 | } 200 | } 201 | //对eh_all_ctx进行排序 202 | eh_all_ctx.sort_by_key(|a| a.eip); 203 | return (eh_all_ctx, lua_fn); 204 | } 205 | 206 | impl profile_bss_types::eh_ctx { 207 | fn to_ne_bytes(&self) ->&[u8;size_of::()] { 208 | unsafe { 209 | std::mem::transmute(self) 210 | } 211 | } 212 | } 213 | 214 | // Pid 0 means a kernel space stack. 215 | fn show_stack_trace(stack: &[u64], symbolizer: &symbolize::Symbolizer, pid: u32) { 216 | let converted_stack; 217 | // The kernel always reports `u64` addresses, whereas blazesym uses `usize`. 218 | // Convert the stack trace as necessary. 219 | let stack = if mem::size_of::() != mem::size_of::() { 220 | converted_stack = stack 221 | .iter() 222 | .copied() 223 | .map(|addr| addr as blazesym::Addr) 224 | .collect::>(); 225 | converted_stack.as_slice() 226 | } else { 227 | // SAFETY: `Addr` has the same size as `u64`, so it can be trivially and 228 | // safely converted. 229 | unsafe { mem::transmute::<_, &[blazesym::Addr]>(stack) } 230 | }; 231 | let src = if pid == 0 { 232 | symbolize::Source::from(symbolize::Kernel::default()) 233 | } else { 234 | symbolize::Source::from(symbolize::Process::new(pid.into())) 235 | }; 236 | let syms = match symbolizer.symbolize(&src, stack) { 237 | Ok(syms) => syms, 238 | Err(err) => { 239 | eprintln!(" failed to symbolize addresses: {err:#}"); 240 | return; 241 | } 242 | }; 243 | 244 | for (i, (addr, syms)) in stack.iter().zip(syms).enumerate() { 245 | let mut addr_fmt = format!(" {i:2} [<{addr:016x}>]"); 246 | if syms.is_empty() { 247 | println!("{addr_fmt}") 248 | } else { 249 | for (i, sym) in syms.into_iter().enumerate() { 250 | if i == 1 { 251 | addr_fmt = addr_fmt.replace(|_c| true, " "); 252 | } 253 | 254 | let path = match (sym.dir, sym.file) { 255 | (Some(dir), Some(file)) => Some(dir.join(file)), 256 | (dir, file) => dir.or_else(|| file.map(PathBuf::from)), 257 | }; 258 | 259 | let src_loc = if let (Some(path), Some(line)) = (path, sym.line) { 260 | if let Some(col) = sym.column { 261 | format!(" {}:{line}:{col}", path.display()) 262 | } else { 263 | format!(" {}:{line}", path.display()) 264 | } 265 | } else { 266 | String::new() 267 | }; 268 | 269 | let symbolize::Sym { 270 | name, addr, offset, .. 271 | } = sym; 272 | 273 | println!("{addr_fmt} {name} @ {addr:#x}+{offset:#x}{src_loc}"); 274 | } 275 | } 276 | } 277 | } 278 | 279 | pub struct Perf { 280 | pid: pid_t, 281 | symbolizer: symbolize::Symbolizer, 282 | strings: HashMap, 283 | stacks: Vec, 284 | } 285 | 286 | impl Perf { 287 | pub fn new(pid: pid_t) -> Self { 288 | Self { 289 | pid: pid, 290 | symbolizer: symbolize::Symbolizer::new(), 291 | strings: HashMap::new(), 292 | stacks: Vec::new(), 293 | } 294 | } 295 | fn bump_memlock_rlimit() -> Result<()> { 296 | let rlimit = libc::rlimit { 297 | rlim_cur: 128 << 20, 298 | rlim_max: 128 << 20, 299 | }; 300 | 301 | if unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlimit) } != 0 { 302 | bail!("Failed to increase rlimit"); 303 | } 304 | 305 | Ok(()) 306 | } 307 | fn build_eh_ctx(pid: pid_t) ->(std::vec::Vec, LuaFn) { 308 | build_eh_ctx(pid) 309 | } 310 | fn bpf_var_pos(var_pos: &var::FnVarPos) -> profile_bss_types::fn_var_pos { 311 | let mut pos = profile_bss_types::fn_var_pos::default(); 312 | pos.eip_begin = var_pos.addr; 313 | pos.eip_end = var_pos.addr + var_pos.size; 314 | match &var_pos.pos { 315 | var::VarPos::Reg(reg_name)=> { 316 | let reg = X86_64::name_to_register(reg_name).unwrap(); 317 | pos.is_mem = false; 318 | pos.reg = reg.0 as u8; 319 | }, 320 | var::VarPos::Mem(reg_name, disp) => { 321 | let reg = X86_64::name_to_register(reg_name).unwrap(); 322 | pos.is_mem = true; 323 | pos.reg = reg.0 as u8; 324 | pos.disp = *disp as i32; 325 | }, 326 | } 327 | pos 328 | } 329 | 330 | fn init<'a>(&self) ->Result> { 331 | //build eh_ctx 332 | let result = Self::build_eh_ctx(self.pid); 333 | let eh_list = result.0; 334 | let lua_fn = result.1; 335 | //tracing 336 | let level = LevelFilter::INFO; 337 | let subscriber = FmtSubscriber::builder() 338 | .with_max_level(level) 339 | .with_span_events(FmtSpan::FULL) 340 | .with_timer(SystemTime) 341 | .finish(); 342 | let () = set_global_subscriber(subscriber).expect("failed to set tracing subscriber"); 343 | let mut skel_builder = ProfileSkelBuilder::default(); 344 | //TODO:enable debug 345 | skel_builder.obj_builder.debug(false); 346 | let mut open_skel = skel_builder.open().unwrap(); 347 | //set max entries 348 | open_skel.bss().EH_FRAME_COUNT = eh_list.len() as u32; 349 | open_skel.bss().STRINGS_MAP_SIZE = STRINGS_MAP_SIZE; 350 | open_skel.bss().STACKS_MAP_SIZE = STACKS_MAP_SIZE; 351 | 352 | let _ = open_skel.maps_mut().eh_frame_header().set_max_entries(eh_list.len() as u32); 353 | let _ = open_skel.maps_mut().eh_frame().set_max_entries(eh_list.len() as u32); 354 | let _ = open_skel.maps_mut().strings().set_max_entries(STRINGS_MAP_SIZE as u32); 355 | let _ = open_skel.maps_mut().stacks().set_max_entries(STACKS_MAP_SIZE as u32); 356 | 357 | // Write arguments into prog 358 | let pid_path = format!("/proc/{}/ns/pid", self.pid); 359 | let stat = fs::metadata(&pid_path).unwrap(); 360 | open_skel.bss().ctrl.dev = stat.st_dev(); 361 | open_skel.bss().ctrl.ino = stat.st_ino(); 362 | open_skel.bss().ctrl.target_pid = self.pid; 363 | open_skel.bss().ctrl.lua_eip_begin = lua_fn.addr; 364 | open_skel.bss().ctrl.lua_eip_end = lua_fn.addr + lua_fn.size; 365 | for (i, var) in lua_fn.vars.iter().enumerate() { 366 | open_skel.bss().ctrl.lua_var_pos[i] = Self::bpf_var_pos(var); 367 | } 368 | 369 | //open_skel.bss().ctrl 370 | let mut skel = open_skel.load().unwrap(); 371 | for (i, ctx) in eh_list.iter().enumerate() { 372 | let key = (i as u32).to_ne_bytes(); 373 | let eip = ctx.eip.to_ne_bytes(); 374 | let val = ctx.to_ne_bytes(); 375 | match skel.maps_mut().eh_frame_header().update(&key, &eip, MapFlags::ANY) { 376 | Ok(()) => {} 377 | Err(e) => { 378 | panic!("Error: {}", e); 379 | } 380 | } 381 | match skel.maps_mut().eh_frame().update(&key, val, MapFlags::ANY) { 382 | Ok(()) => {} 383 | Err(e) => { 384 | panic!("Error: {}", e); 385 | } 386 | } 387 | } 388 | Ok(skel) 389 | } 390 | 391 | fn init_perf_monitor(&self, freq: u64) -> Vec { 392 | let nprocs = libbpf_rs::num_possible_cpus().unwrap(); 393 | let pid = -1; 394 | let buf: Vec = vec![0; mem::size_of::()]; 395 | let mut attr = unsafe { 396 | Box::::from_raw( 397 | buf.leak().as_mut_ptr() as *mut syscall::perf_event_attr 398 | ) 399 | }; 400 | attr._type = syscall::PERF_TYPE_SOFTWARE; 401 | attr.size = mem::size_of::() as u32; 402 | attr.config = syscall::PERF_COUNT_HW_CPU_CYCLES; 403 | attr.sample.sample_freq = freq; 404 | attr.flags = 1 << 10; // freq = 1 405 | println!("nprocs:{}", nprocs); 406 | (0..nprocs) 407 | .map(|cpu| { 408 | let fd = syscall::perf_event_open(attr.as_ref(), pid, cpu as i32, -1, 0); 409 | fd as i32 410 | }) 411 | .collect() 412 | } 413 | 414 | fn attach_perf_event( 415 | &mut self, 416 | skel: &mut ProfileSkel, 417 | pefds: &[i32], 418 | ) -> Vec> { 419 | let mut binding = skel.progs_mut(); 420 | let prog = binding.profile(); 421 | pefds 422 | .iter() 423 | .map(|pefd| prog.attach_perf_event(*pefd)) 424 | .collect() 425 | } 426 | fn event_counter(self: &mut Self, data: &[u8]) -> ::std::os::raw::c_int { 427 | if data.len() != mem::size_of::() { 428 | eprintln!( 429 | "Invalid size {} != {}", 430 | data.len(), 431 | mem::size_of::() 432 | ); 433 | return 1; 434 | } 435 | let event_ptr = unsafe { &*(data.as_ptr() as *const profile_bss_types::stack_count) }; 436 | let event: profile_bss_types::stack_count = *event_ptr; 437 | if (event.kstack_sz + event.ustack_sz + event.lstack_sz) <= 0 { 438 | return 1; 439 | } 440 | self.stacks.push(event); 441 | 0 442 | } 443 | 444 | fn event_string(self: &mut Self, data: &[u8]) -> ::std::os::raw::c_int { 445 | if data.len() != mem::size_of::() { 446 | eprintln!( 447 | "Invalid size {} != {}", 448 | data.len(), 449 | mem::size_of::() 450 | ); 451 | return 1; 452 | } 453 | let event_ptr = unsafe { &*(data.as_ptr() as *const profile_bss_types::string) }; 454 | let event: profile_bss_types::string = *event_ptr; 455 | let mut data = event.data.to_vec(); 456 | data.resize(event.len as usize, 0); 457 | let mut utf8_buffer: Vec = Vec::new(); 458 | for c in data { 459 | utf8_buffer.push(c as u8); 460 | } 461 | let s = std::string::String::from_utf8(utf8_buffer).unwrap(); 462 | self.strings.insert(event.id, s); 463 | 0 464 | } 465 | fn event_handler(self: &mut Self, skel: &ProfileSkel, data: &[u8]) -> ::std::os::raw::c_int { 466 | let event_type = data[0]; 467 | if event_type == profile_bss_types::event_type::EVENT_STACK as u8 { 468 | println!("EVENT_STACK"); 469 | self.event_counter(data) 470 | } else if event_type == profile_bss_types::event_type::EVENT_STRING as u8 { 471 | println!("EVENT_STRING"); 472 | self.event_string(data) 473 | } else if event_type == profile_bss_types::event_type::EVENT_TRACE as u8 { 474 | let maps = skel.maps(); 475 | let bindings = maps.strings(); 476 | self.event_trace(skel, &bindings, &self.symbolizer, &data) 477 | } else { 478 | println!("EVENT_UNKNOWN"); 479 | 0 480 | } 481 | } 482 | 483 | fn event_trace(self: &Self, skel: &ProfileSkel, bindings: &libbpf_rs::Map, symbolizer: &symbolize::Symbolizer, data: &[u8]) -> ::std::os::raw::c_int { 484 | if data.len() != mem::size_of::() { 485 | eprintln!( 486 | "Invalid size {} != {}", 487 | data.len(), 488 | mem::size_of::() 489 | ); 490 | return 1; 491 | } 492 | 493 | let event = unsafe { &*(data.as_ptr() as *const profile_bss_types::stack_event) }; 494 | 495 | if event.kstack_sz <= 0 && event.ustack_sz <= 0 { 496 | return 1; 497 | } 498 | 499 | let event_comm: Vec = event.comm.iter().map(|&x| x as u8).collect(); 500 | let comm = std::str::from_utf8(&event_comm) 501 | .or::(Ok("")) 502 | .unwrap(); 503 | println!("COMM: {} (pid={}) @ CPU {}", comm, event.pid, event.cpu_id); 504 | 505 | if event.kstack_sz > 0 { 506 | println!("Kernel:"); 507 | show_stack_trace( 508 | &event.kstack[0..(event.kstack_sz as usize / mem::size_of::())], 509 | symbolizer, 510 | 0, 511 | ); 512 | } else { 513 | println!("No Kernel Stack"); 514 | } 515 | 516 | if event.ustack_sz > 0 { 517 | println!("Userspace:"); 518 | show_stack_trace( 519 | &event.ustack[0..(event.ustack_sz as usize / mem::size_of::())], 520 | symbolizer, 521 | event.pid, 522 | ); 523 | } else { 524 | println!("No Userspace Stack"); 525 | } 526 | println!("LuaStack:"); 527 | if event.lstack_sz > 0 { 528 | for addr in event.lstack.iter().take(event.lstack_sz as usize / mem::size_of::()) { 529 | if (addr & LUA_MASK) != 0 { 530 | let addrv = *addr & !LUA_MASK; 531 | let file_id = addrv as u32; 532 | let line = (addrv >> 32) as u32; 533 | let i = file_id % 2048; 534 | let key = (i as u32).to_ne_bytes().to_vec(); 535 | match bindings.lookup(&key, MapFlags::ANY) { 536 | Ok(val) => { 537 | match val { 538 | Some(val) => { 539 | let cache = unsafe { &*(val.as_ptr() as *const profile_bss_types::string) }; 540 | let mut data = cache.data.to_vec(); 541 | data.resize(cache.len as usize, 0); 542 | let mut utf8_buffer: Vec = Vec::new(); 543 | for c in data { 544 | utf8_buffer.push(c as u8); 545 | } 546 | let s = std::string::String::from_utf8(utf8_buffer).unwrap(); 547 | println!("Lua Stack:{}:{} len:{}", s, line, cache.len); 548 | } 549 | None => { 550 | println!("Lua Stack:Unkonw {:X}", i); 551 | } 552 | } 553 | } 554 | Err(_) => { 555 | println!("Lua Stack:ERR {}", line); 556 | } 557 | } 558 | } else { 559 | println!("Lua Stack:{:X}", *addr); 560 | let mut stk = Vec::new(); 561 | stk.push(*addr); 562 | show_stack_trace( 563 | &stk[0..1], 564 | symbolizer, 565 | event.pid, 566 | ); 567 | } 568 | } 569 | } 570 | 571 | println!(); 572 | 573 | let stack = StackFrame::from_event(event); 574 | let mut stack_strs = self.combine_stack(skel, &stack); 575 | stack_strs.reverse(); 576 | println!("combined:"); 577 | println!("{}", stack_strs.join("\n")); 578 | println!("============="); 579 | 0 580 | } 581 | 582 | fn poll_events<'a>(self: &'a mut Self, skel: &'a mut ProfileSkel) -> Result<()> { 583 | let mut builder = libbpf_rs::RingBufferBuilder::new(); 584 | let binding = skel.maps(); 585 | builder.add(binding.events(), |data| { 586 | self.event_handler(skel, data) 587 | }).unwrap(); 588 | let ringbuf = builder.build()?; 589 | let running = Arc::new(AtomicBool::new(true)); 590 | let r = running.clone(); 591 | let _ = ctrlc::set_handler(move ||{ 592 | r.store(false, Ordering::SeqCst); 593 | }); 594 | while running.load(Ordering::SeqCst) { 595 | let ret = ringbuf.poll(Duration::from_millis(100)); 596 | match ret { 597 | Ok(_) => {} 598 | Err(_) => { 599 | break; 600 | } 601 | } 602 | } 603 | Ok(()) 604 | } 605 | 606 | fn syms_of_stack(&self, pid: pid_t, stack: &Vec) -> Vec { 607 | let mut stack_syms: Vec = Vec::new(); 608 | let converted_stack; 609 | let stack = if mem::size_of::() != mem::size_of::() { 610 | converted_stack = stack 611 | .iter() 612 | .copied() 613 | .map(|addr| addr as blazesym::Addr) 614 | .collect::>(); 615 | converted_stack.as_slice() 616 | } else { 617 | // SAFETY: `Addr` has the same size as `u64`, so it can be trivially and 618 | // safely converted. 619 | unsafe { mem::transmute::<_, &[blazesym::Addr]>(stack.as_slice()) } 620 | }; 621 | let src = if pid == 0 { 622 | symbolize::Source::from(symbolize::Kernel::default()) 623 | } else { 624 | symbolize::Source::from(symbolize::Process::new((pid as u32).into())) 625 | }; 626 | let syms = match self.symbolizer.symbolize(&src, stack) { 627 | Ok(syms) => syms, 628 | Err(err) => { 629 | eprintln!(" failed to symbolize addresses: {err:#}"); 630 | return stack_syms; 631 | } 632 | }; 633 | 634 | for (_i, (addr, syms)) in stack.iter().zip(syms).enumerate() { 635 | let addr_fmt = format!("[<{addr:016x}>]"); 636 | if syms.is_empty() { 637 | stack_syms.push(SymInfo{ 638 | addr: 0, 639 | offset: 0, 640 | name: addr_fmt.clone(), 641 | }) 642 | } else { 643 | for (_i, sym) in syms.into_iter().enumerate() { 644 | let symbolize::Sym { 645 | name, offset, .. 646 | } = sym; 647 | stack_syms.push(SymInfo{ 648 | addr: *addr as u64, 649 | offset: offset as u64, 650 | //name: format!("{} @ {}", name, src_loc), 651 | name, 652 | }) 653 | } 654 | } 655 | } 656 | stack_syms 657 | } 658 | 659 | fn id_to_str(&self, skel: &ProfileSkel, file_id: u32) -> String { 660 | let i = file_id % STRINGS_MAP_SIZE; 661 | let key = (i as u32).to_ne_bytes().to_vec(); 662 | let maps = skel.maps(); 663 | let bindings = maps.strings(); 664 | let str = match bindings.lookup(&key, MapFlags::ANY) { 665 | Ok(val) => { 666 | let v = match val { 667 | Some(val) => { 668 | let cache = unsafe { &*(val.as_ptr() as *const profile_bss_types::string) }; 669 | if cache.id != file_id { 670 | String::new() 671 | } else { 672 | let mut data = cache.data.to_vec(); 673 | data.resize(cache.len as usize, 0); 674 | let mut utf8_buffer: Vec = Vec::new(); 675 | for c in data { 676 | utf8_buffer.push(c as u8); 677 | } 678 | std::string::String::from_utf8(utf8_buffer).unwrap() 679 | } 680 | } 681 | None => String::new(), 682 | }; 683 | v 684 | }, 685 | Err(_) => String::from("ERR"), 686 | }; 687 | if !str.is_empty() { 688 | return str; 689 | } 690 | match self.strings.get(&file_id) { 691 | Some(v) => v.clone(), 692 | None => String::from("None"), 693 | } 694 | } 695 | fn split_lua_chunk(&self, skel: &ProfileSkel, frame: &StackFrame) -> Vec> { 696 | let mut chunks: Vec> = Vec::new(); 697 | let mut chunk: Vec = Vec::new(); 698 | for addr in frame.lstack.iter() { 699 | if *addr == 0 { //CIST_FRESH 700 | chunk.reverse(); 701 | chunks.push(chunk); 702 | chunk = Vec::new(); 703 | continue 704 | } 705 | if *addr & LUA_MASK == 0 { 706 | chunk.push(LuaFrame::C(*addr)) 707 | } else { 708 | let addrv = *addr & !LUA_MASK; 709 | let file_id = addrv as u32; 710 | let line = (addrv >> 32) as u32; 711 | let str = format!("{}:{}", self.id_to_str(skel, file_id), line); 712 | chunk.push(LuaFrame::Lua(str)); 713 | } 714 | } 715 | if chunk.len() > 0 { 716 | chunk.reverse(); 717 | chunks.push(chunk); 718 | } 719 | chunks.reverse(); 720 | return chunks 721 | } 722 | 723 | fn split_c_chunk(&self, usym: &Vec, l_chunks: &Vec>) -> Vec> { 724 | let mut chunks: Vec> = Vec::new(); 725 | let mut chunk: Vec = Vec::new(); 726 | for sym in usym.iter().rev() { 727 | //TODO process recursive call 728 | let start_addr = sym.addr - sym.offset; 729 | let c_start = l_chunks.iter().find(|&x| 730 | match x.first() { 731 | Some(LuaFrame::C(addr)) => *addr == start_addr, 732 | _ => false, 733 | } 734 | ).is_some(); 735 | if c_start || sym.name.contains("luaV_execute") { 736 | if !c_start { 737 | chunk.push(sym.clone()); 738 | } 739 | chunks.push(chunk); 740 | chunk = Vec::new(); 741 | } else { 742 | chunk.push(sym.clone()); 743 | } 744 | } 745 | if chunk.len() > 0 { 746 | chunks.push(chunk); 747 | } 748 | return chunks 749 | } 750 | 751 | fn combine_stack(&self, skel: &ProfileSkel, frame: &StackFrame) -> Vec { 752 | let ksyms = self.syms_of_stack(0, &frame.kstack); 753 | let usyms = self.syms_of_stack(self.pid, &frame.ustack); 754 | //split lua call chunk 755 | let mut lua_chunks = self.split_lua_chunk(skel, frame); 756 | let mut c_chunks = self.split_c_chunk(&usyms, &lua_chunks); 757 | let mut frame_strs:Vec = Vec::new(); 758 | for c_chunk in c_chunks.iter_mut() { 759 | for i in 0..(c_chunk.len()) { 760 | frame_strs.push(c_chunk[i].name.clone()); 761 | } 762 | if lua_chunks.len() > 0 { //has lua stack left 763 | let lua_chunk = lua_chunks.remove(0); 764 | let lua_c_func: Vec = lua_chunk.iter().map( 765 | |f| match f { 766 | LuaFrame::Lua(_) => 0, 767 | LuaFrame::C(addr) => *addr, 768 | } 769 | ).collect(); 770 | let lua_c_syms = self.syms_of_stack(self.pid, &lua_c_func); 771 | for (frame, sym) in lua_chunk.iter().zip(lua_c_syms) { 772 | match frame { 773 | LuaFrame::Lua(str) => { 774 | frame_strs.push(str.clone()) 775 | }, 776 | LuaFrame::C(addr) => 777 | if c_chunk.iter().find( 778 | |&x| return x.addr - x.offset == *addr 779 | ).is_none() { 780 | frame_strs.push(sym.name.clone()) 781 | } 782 | } 783 | }; 784 | } 785 | } 786 | let mut kstack = ksyms.iter().rev().map(|s| s.name.clone()).collect(); 787 | frame_strs.append(&mut kstack); 788 | return frame_strs; 789 | } 790 | 791 | fn flame_entry(&self, skel: &ProfileSkel, frame: &StackFrame) -> String { 792 | let strs = self.combine_stack(skel, frame); 793 | strs.join(";") 794 | } 795 | fn collect_flame(&self, skel: &ProfileSkel) { 796 | let maps = &skel.maps(); 797 | let stack_map = maps.stacks(); 798 | let mut stack_count: HashMap = HashMap::new(); 799 | let mut frame_list:Vec<(String, u32)> = Vec::new(); 800 | for i in 0..STACKS_MAP_SIZE { 801 | let key = (i as u32).to_ne_bytes().to_vec(); 802 | match stack_map.lookup(&key, MapFlags::ANY) { 803 | Ok(val) => { 804 | match val { 805 | Some(val) => { 806 | let counter = unsafe { &*(val.as_ptr() as *const profile_bss_types::stack_count) }; 807 | if (counter.kstack_sz + counter.ustack_sz + counter.lstack_sz) > 0 { 808 | let stack = StackFrame::from_count(counter); 809 | let stack_str = self.flame_entry(skel, &stack); 810 | *stack_count.entry(stack_str).or_insert(0) += counter.count; 811 | } 812 | } 813 | None => { 814 | eprintln!("Flame:Unkonw {:X}", i); 815 | } 816 | } 817 | } 818 | Err(_) => { 819 | eprintln!("Flame:ERR {}", i); 820 | } 821 | } 822 | } 823 | for stack in self.stacks.iter() { 824 | let count = stack.count; 825 | let stack = StackFrame::from_count(stack); 826 | let stack_str = self.flame_entry(skel, &stack); 827 | *stack_count.entry(stack_str).or_insert(0) += count; 828 | } 829 | for (stack, count) in stack_count.iter() { 830 | frame_list.push((stack.clone(), *count)); 831 | } 832 | frame_list.sort_by_key(|a| a.1); 833 | //write to file 834 | let mut file = File::create("perf.folded").unwrap(); 835 | for (stack, count) in frame_list.iter() { 836 | let line = format!("{} {}\n", stack, count); 837 | file.write(line.as_bytes()).unwrap(); 838 | } 839 | } 840 | 841 | pub fn exec<'a>(self: &'a mut Self, args: &crate::args::Args) ->Result<()> { 842 | Self::bump_memlock_rlimit()?; 843 | let mut skel = self.init()?; 844 | let pef_fds = self.init_perf_monitor(args.freq); 845 | let _links = self.attach_perf_event(&mut skel, &pef_fds); 846 | self.poll_events(&mut skel)?; 847 | self.collect_flame(&skel); 848 | for pefd in pef_fds { 849 | close(pefd).unwrap(); 850 | } 851 | Ok(()) 852 | } 853 | } 854 | 855 | 856 | 857 | #[cfg(test)] 858 | mod tests { 859 | use goblin::elf::Elf; 860 | #[test] 861 | fn test_find_func_code() { 862 | let paths = vec![ 863 | "lua.clang.o0", 864 | "lua.clang.o1", 865 | "lua.clang.o2", 866 | "lua.clang.o3", 867 | "lua.gcc.o0", 868 | "lua.gcc.o1", 869 | "lua.gcc.o2", 870 | "lua.gcc.o3", 871 | ]; 872 | for path in paths.iter() { 873 | let elf_data = std::fs::read(path).unwrap(); 874 | let data = &elf_data; 875 | let _elf = Elf::parse(data).unwrap(); 876 | //let fn_vars = var::parse_var(&elf, &elf_data); 877 | //println!("========{}======:{:?}", path, fn_vars); 878 | } 879 | } 880 | } 881 | -------------------------------------------------------------------------------- /src/perf/eh.rs: -------------------------------------------------------------------------------- 1 | use std::vec::Vec; 2 | use rustc_demangle::demangle; 3 | use std::collections::HashMap; 4 | use anyhow::{anyhow, Context}; 5 | 6 | use goblin::elf::Elf; 7 | use goblin::elf::Sym; 8 | use goblin::elf::SectionHeader; 9 | use gimli::BaseAddresses; 10 | use gimli::CallFrameInstruction; 11 | use gimli::CieOrFde; 12 | use gimli::EhFrame; 13 | use gimli::Reader; 14 | use gimli::Register; 15 | use gimli::SectionBaseAddresses; 16 | use gimli::UnwindSection; 17 | use gimli::X86_64; 18 | 19 | const MAX_REG: usize = X86_64::RA.0 as usize + 1; 20 | 21 | fn register_name(r: Register) -> &'static str { 22 | X86_64::register_name(r).unwrap_or("???") 23 | } 24 | 25 | #[derive(Debug, Clone, Eq, PartialEq)] 26 | pub enum RegRule { 27 | Undefined, 28 | SameValue, 29 | Offset(i64), 30 | ValOffset(i64), 31 | Register(Register), 32 | Expression(Vec), 33 | ValExpression(Vec), 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | pub enum CfaRule { 38 | Undefined, 39 | Register(Register), 40 | Expression(Vec), 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | pub struct EhInstr { 45 | pub sym: String, 46 | pub loc: u64, 47 | pub size: u64, 48 | pub cfa_reg: CfaRule, 49 | pub cfa_off: i64, 50 | pub regs: [RegRule; MAX_REG], 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct FdeDesc { 55 | pub loc: u64, 56 | pub size: u64, 57 | } 58 | 59 | pub struct EhInstrContext { 60 | code_align_factor: u64, 61 | data_align_factor: i64, 62 | fde_desc: HashMap, 63 | instr: EhInstr, 64 | init_regs: [RegRule; MAX_REG], 65 | instrs: Vec, 66 | stack: Vec, 67 | } 68 | 69 | impl EhInstr { 70 | fn new () -> Self { 71 | let instr = EhInstr { 72 | sym: String::new(), 73 | loc: 0, 74 | size: 0, 75 | cfa_reg: CfaRule::Undefined, 76 | cfa_off: 0, 77 | regs: std::array::from_fn(|_| RegRule::Undefined), 78 | }; 79 | return instr; 80 | } 81 | fn reset(&mut self) { 82 | *self = EhInstr::new(); 83 | } 84 | } 85 | 86 | impl std::fmt::Display for EhInstr { 87 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 88 | let cfa_reg = match &self.cfa_reg { 89 | CfaRule::Register(r) => String::from(register_name(*r)), 90 | CfaRule::Expression(e) => format!("exp:{:?}", e), 91 | CfaRule::Undefined => String::from("undefined"), 92 | }; 93 | write!(f, "{} {:#x} {:#x} CFA_reg:{:?} CFA_off:{} {}:{:?} {}:{:?} {}:{:?}", 94 | self.sym, self.loc, self.size, 95 | cfa_reg, self.cfa_off, 96 | register_name(X86_64::RA), self.regs[X86_64::RA.0 as usize], 97 | register_name(X86_64::RBP), self.regs[X86_64::RBP.0 as usize], 98 | register_name(X86_64::RSP), self.regs[X86_64::RSP.0 as usize], 99 | ) 100 | } 101 | } 102 | 103 | impl<'a> EhInstrContext { 104 | pub fn from_elf(elf: &'a Elf<'a>, elf_data: &'a Vec) -> Self { 105 | let mut instr_ctx = EhInstrContext { 106 | code_align_factor: 0, 107 | data_align_factor: 1, 108 | fde_desc: HashMap::new(), 109 | init_regs: std::array::from_fn(|_| RegRule::Undefined), 110 | stack: Vec::new(), 111 | instrs: Vec::new(), 112 | instr: EhInstr::new(), 113 | }; 114 | let sh = instr_ctx.find_section(&elf, ".eh_frame") 115 | .or_else(||instr_ctx.find_section(&elf, ".debug_frame")) 116 | .ok_or_else(|| anyhow!("couldn't find section `.eh_frame` or `.debug_frame`")).unwrap(); 117 | let range = sh.file_range() 118 | .ok_or_else(|| anyhow!("section has no content")).unwrap(); 119 | instr_ctx.parse_eh_frame(&elf, sh.sh_addr, &elf_data[range]); 120 | return instr_ctx; 121 | } 122 | fn find_section(&self, elf: &'a Elf, name: &str) -> Option<&'a SectionHeader> { 123 | elf.section_headers 124 | .iter() 125 | .find(|&s| { 126 | elf.shdr_strtab.get_at(s.sh_name) 127 | .map(|n| n == name) 128 | .unwrap_or(false) 129 | }) 130 | } 131 | pub fn parse_eh_frame(&mut self, elf: &Elf, vaddr: u64, content: &[u8]) { 132 | let eh = EhFrame::new(content, gimli::LittleEndian); // TODO: endianness 133 | let base_addrs = BaseAddresses { 134 | eh_frame_hdr: SectionBaseAddresses::default(), 135 | eh_frame: SectionBaseAddresses { 136 | section: Some(vaddr), 137 | text: None, 138 | data: None, 139 | }, 140 | }; 141 | let mut cies = HashMap::new(); 142 | let mut cfi_entries = eh.entries(&base_addrs); 143 | while let Some(entry) = cfi_entries.next().with_context(|| anyhow::anyhow!("failed to parse entry")).unwrap() { 144 | match entry { 145 | CieOrFde::Cie(cie) => { 146 | self.code_align_factor = cie.code_alignment_factor(); 147 | self.data_align_factor = cie.data_alignment_factor(); 148 | cies.insert(cie.offset(), cie); 149 | }, 150 | CieOrFde::Fde(fde_unparsed) => { 151 | let fde = fde_unparsed.parse(|_, _, offset| { 152 | Ok(cies[&offset.0].clone()) 153 | }).unwrap(); 154 | //cfi instruction 155 | let func_name = self.addr_to_sym_name(elf, fde.initial_address()); 156 | //println!("{} {:#x} {:#x}", func_name, fde.initial_address(), fde.len()); 157 | self.fde_desc.insert(func_name, FdeDesc{ 158 | loc: fde.initial_address(), 159 | size: fde.len(), 160 | }); 161 | self.eval_begin(elf, fde.initial_address()); 162 | let mut instr_iter = fde.cie().instructions(&eh, &base_addrs); 163 | while let Some(instr) = instr_iter.next().unwrap_or(None) { 164 | self.eval(elf, instr); 165 | } 166 | self.save_init(); 167 | //fde instruction 168 | let mut instr_iter = fde.instructions(&eh, &base_addrs); 169 | while let Some(instr) = instr_iter.next().unwrap_or(None) { 170 | self.eval(elf, instr); 171 | } 172 | self.eval_end(elf, fde.initial_address() + fde.len()); 173 | }, 174 | } 175 | } 176 | } 177 | fn addr_to_sym(&self, elf: &Elf, addr: u64) -> Option { 178 | let mut iter = elf.syms.iter(); 179 | let mut curr_sym = iter.next()?; 180 | for sym in iter { 181 | if !sym.is_function() { 182 | continue; 183 | } 184 | if sym.st_value <= addr && sym.st_value > curr_sym.st_value { 185 | curr_sym = sym; 186 | } 187 | } 188 | if curr_sym.st_value > addr { 189 | None 190 | } else { 191 | Some(curr_sym) 192 | } 193 | } 194 | fn addr_to_sym_name(&self, elf: &Elf, addr: u64) ->String { 195 | if let Some(sym) = self.addr_to_sym(elf, addr) { 196 | let name = elf.strtab.get_at(sym.st_name).unwrap_or("???"); 197 | let name = demangle(name).to_string(); 198 | return format!("{}+{:#x}", name, addr - sym.st_value); 199 | } 200 | return format!("{:#x}", addr); 201 | } 202 | fn update_instr_sym(&mut self, elf: &Elf) { 203 | if self.instr.loc == 0 { 204 | self.instr.sym = String::from("cfi"); 205 | return; 206 | } 207 | self.instr.sym = self.addr_to_sym_name(elf, self.instr.loc); 208 | } 209 | fn set_loc(&mut self, elf: &Elf, loc: u64) { 210 | if self.instr.loc != 0 { 211 | self.instr.size = loc - self.instr.loc; 212 | self.instrs.push(self.instr.clone()); 213 | } 214 | self.instr.loc = loc; 215 | self.update_instr_sym(&elf); 216 | } 217 | fn eval_begin(&mut self, elf: &Elf, loc: u64) { 218 | self.instr.loc = loc; 219 | self.instr.size = 0; 220 | self.update_instr_sym(elf); 221 | } 222 | fn eval_end(&mut self, elf: &Elf, loc: u64) { 223 | self.set_loc(elf, loc); 224 | self.instr.reset(); 225 | } 226 | fn save_init(&mut self) { 227 | for i in 0 .. self.instr.regs.len() { 228 | self.init_regs[i] = self.instr.regs[i].clone(); 229 | } 230 | } 231 | fn eval(&mut self, elf: &Elf, instr: CallFrameInstruction) { 232 | use CallFrameInstruction::*; 233 | match instr { 234 | SetLoc { address } => { 235 | self.set_loc(elf, address); 236 | }, 237 | AdvanceLoc { delta } => { 238 | self.set_loc(elf, self.instr.loc + delta as u64 * self.code_align_factor); 239 | }, 240 | DefCfa { register, offset } => { 241 | self.instr.cfa_reg = CfaRule::Register(register); 242 | self.instr.cfa_off = offset as i64; 243 | }, 244 | DefCfaSf { register, factored_offset } => { 245 | self.instr.cfa_reg = CfaRule::Register(register); 246 | self.instr.cfa_off = factored_offset * self.data_align_factor; 247 | }, 248 | DefCfaRegister { register } => { 249 | self.instr.cfa_reg = CfaRule::Register(register); 250 | }, 251 | DefCfaOffset { offset } => { 252 | self.instr.cfa_off = offset as i64; 253 | }, 254 | DefCfaOffsetSf { factored_offset } => { 255 | self.instr.cfa_off = factored_offset * self.data_align_factor; 256 | }, 257 | DefCfaExpression { expression } => { 258 | self.instr.cfa_off = 0; 259 | self.instr.cfa_reg = CfaRule::Expression(expression.0.to_slice().unwrap().to_vec()); 260 | }, 261 | Undefined { register } => { 262 | let register = register.0 as usize; 263 | if register < MAX_REG { 264 | self.instr.regs[register] = RegRule::Undefined; 265 | } 266 | }, 267 | SameValue { register } => { 268 | let register = register.0 as usize; 269 | if register < MAX_REG { 270 | self.instr.regs[register] = RegRule::SameValue; 271 | } 272 | }, 273 | Offset { register, factored_offset } => { 274 | let register = register.0 as usize; 275 | let off = factored_offset as i64 * self.data_align_factor; 276 | if register < MAX_REG { 277 | self.instr.regs[register] = RegRule::Offset(off); 278 | } 279 | }, 280 | OffsetExtendedSf { register, factored_offset } => { 281 | let register = register.0 as usize; 282 | let off = factored_offset as i64 * self.data_align_factor; 283 | if register < MAX_REG { 284 | self.instr.regs[register] = RegRule::Offset(off); 285 | } 286 | }, 287 | ValOffset { register, factored_offset } => { 288 | let register = register.0 as usize; 289 | let off = factored_offset as i64 * self.data_align_factor; 290 | if register < MAX_REG { 291 | self.instr.regs[register] = RegRule::ValOffset(off); 292 | } 293 | }, 294 | ValOffsetSf { register, factored_offset } => { 295 | let register = register.0 as usize; 296 | let off = factored_offset as i64 * self.data_align_factor; 297 | if register < MAX_REG { 298 | self.instr.regs[register] = RegRule::ValOffset(off); 299 | } 300 | }, 301 | Register { dest_register, src_register } => { 302 | let dest_register = dest_register.0 as usize; 303 | if dest_register < MAX_REG { 304 | if src_register.0 as usize >= MAX_REG { 305 | panic!("Unsupport Register instruction"); 306 | } 307 | self.instr.regs[dest_register] = RegRule::Register(src_register); 308 | } 309 | }, 310 | Expression { register, expression } => { 311 | let register = register.0 as usize; 312 | if register < MAX_REG { 313 | self.instr.regs[register] = RegRule::Expression(expression.0.to_slice().unwrap().to_vec()); 314 | } 315 | }, 316 | ValExpression { register, expression } => { 317 | let register = register.0 as usize; 318 | if register < MAX_REG { 319 | self.instr.regs[register] = RegRule::ValExpression(expression.0.to_slice().unwrap().to_vec()); 320 | } 321 | }, 322 | Restore { register } => { 323 | let register = register.0 as usize; 324 | if register < MAX_REG { 325 | self.instr.regs[register] = self.init_regs[register].clone(); 326 | } 327 | }, 328 | RememberState => { 329 | self.stack.push(self.instr.clone()); 330 | }, 331 | RestoreState => { 332 | let loc = self.instr.loc; 333 | self.instr = self.stack.pop().unwrap(); 334 | self.instr.loc = loc; 335 | }, 336 | ArgsSize{ size: _ } => (), 337 | Nop => (), 338 | _ => panic!("unhandled instruction {:?}", instr), 339 | } 340 | } 341 | pub fn iter(&'a self) -> std::slice::Iter<'a, EhInstr> { 342 | self.instrs.iter() 343 | } 344 | pub fn get_fde_desc(&self, name: &String) -> Option<&FdeDesc> { 345 | self.fde_desc.get(name) 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/perf/maps.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use procmaps::Mappings; 3 | use procmaps::Path; 4 | use libc::pid_t; 5 | 6 | use goblin::elf::Elf; 7 | use goblin::elf::program_header::*; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct Map { 11 | pub offset: u64, 12 | pub base: u64, 13 | pub vaddr: u64, 14 | pub size : u64, 15 | } 16 | 17 | pub struct FileMaps { 18 | path: String, 19 | maps: Vec 20 | } 21 | 22 | pub struct Files { 23 | files: HashMap, 24 | } 25 | 26 | impl FileMaps { 27 | fn parse(&mut self) { 28 | println!("parse {}", self.path); 29 | let elf_data = std::fs::read(&self.path).unwrap(); 30 | let elf = Elf::parse(&elf_data).unwrap(); 31 | let headers = &elf.program_headers; // 获取程序头列表 32 | for map in &mut self.maps { 33 | let mut find = false; 34 | for ph in headers { 35 | if !ph.is_executable() { 36 | continue; 37 | } 38 | if ph.p_type != PT_LOAD { 39 | continue; 40 | } 41 | if map.offset == ph.p_offset { 42 | map.vaddr = ph.p_vaddr; 43 | if map.size < ph.p_memsz { 44 | panic!("base:{:X} va:{:X} offset:{:X} size:{:X} != {:X} {:X}", 45 | map.base, ph.p_vaddr, ph.p_offset, ph.p_memsz, map.size, ph.p_filesz); 46 | } 47 | find = true; 48 | break 49 | } 50 | } 51 | if !find { 52 | panic!("offset:{} can't find map zone", map.offset); 53 | } 54 | 55 | } 56 | } 57 | pub fn translate(&self, vaddr: u64) ->u64 { 58 | for map in &self.maps { 59 | if vaddr >= map.vaddr && vaddr < (map.vaddr + map.size) { 60 | return vaddr - map.vaddr + map.base; 61 | } 62 | } 63 | panic!("{}: invalid {}", self.path, vaddr); 64 | } 65 | } 66 | 67 | impl Files { 68 | fn parse_files(pid: pid_t) ->HashMap { 69 | let mut files = HashMap::new(); 70 | let mappings = Mappings::from_pid(pid).unwrap(); 71 | for map in mappings.iter() { 72 | if !map.perms.executable { 73 | continue; 74 | } 75 | if let Path::MappedFile(path) = &map.pathname { 76 | if !path.starts_with("/") { 77 | continue 78 | } 79 | files.entry(String::from(path)) 80 | .or_insert(FileMaps{path: path.to_string(), maps:Vec::new()}).maps 81 | .push(Map { 82 | vaddr: 0, 83 | base : map.base as u64, 84 | offset: map.offset as u64, 85 | size: (map.ceiling - map.base) as u64, 86 | }); 87 | } 88 | } 89 | for (_, f) in &mut files { 90 | if f.path.contains("(deleted)") { 91 | continue 92 | } 93 | f.parse(); 94 | } 95 | return files; 96 | } 97 | pub fn from_pid(pid: pid_t) -> Self { 98 | Files { 99 | files: Files::parse_files(pid), 100 | } 101 | } 102 | pub fn iter(&self) -> std::collections::hash_map::Iter:: { 103 | return self.files.iter(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/perf/syscall.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | #[repr(C)] 4 | pub union sample_un { 5 | pub sample_period: u64, 6 | pub sample_freq: u64, 7 | } 8 | 9 | #[repr(C)] 10 | pub union wakeup_un { 11 | pub wakeup_events: u32, 12 | pub wakeup_atermark: u32, 13 | } 14 | 15 | #[repr(C)] 16 | pub union bp_1_un { 17 | pub bp_addr: u64, 18 | pub kprobe_func: u64, 19 | pub uprobe_path: u64, 20 | pub config1: u64, 21 | } 22 | 23 | #[repr(C)] 24 | pub union bp_2_un { 25 | pub bp_len: u64, 26 | pub kprobe_addr: u64, 27 | pub probe_offset: u64, 28 | pub config2: u64, 29 | } 30 | 31 | #[repr(C)] 32 | pub struct perf_event_attr { 33 | pub _type: u32, 34 | pub size: u32, 35 | pub config: u64, 36 | pub sample: sample_un, 37 | pub sample_type: u64, 38 | pub read_format: u64, 39 | pub flags: u64, 40 | pub wakeup: wakeup_un, 41 | pub bp_type: u32, 42 | pub bp_1: bp_1_un, 43 | pub bp_2: bp_2_un, 44 | pub branch_sample_type: u64, 45 | pub sample_regs_user: u64, 46 | pub sample_stack_user: u32, 47 | pub clockid: i32, 48 | pub sample_regs_intr: u64, 49 | pub aux_watermark: u32, 50 | pub sample_max_stack: u16, 51 | pub __reserved_2: u16, 52 | pub aux_sample_size: u32, 53 | pub __reserved_3: u32, 54 | } 55 | 56 | #[allow(dead_code)] 57 | pub const PERF_TYPE_HARDWARE: u32 = 0; 58 | pub const PERF_TYPE_SOFTWARE: u32 = 1; 59 | pub const PERF_COUNT_HW_CPU_CYCLES: u64 = 0; 60 | 61 | extern "C" { 62 | fn syscall(number: libc::c_long, ...) -> libc::c_long; 63 | } 64 | 65 | pub fn perf_event_open( 66 | hw_event: &perf_event_attr, 67 | pid: libc::pid_t, 68 | cpu: libc::c_int, 69 | group_fd: libc::c_int, 70 | flags: libc::c_ulong, 71 | ) -> libc::c_long { 72 | unsafe { 73 | syscall( 74 | libc::SYS_perf_event_open, 75 | hw_event as *const perf_event_attr, 76 | pid, 77 | cpu, 78 | group_fd, 79 | flags, 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/perf/var.rs: -------------------------------------------------------------------------------- 1 | use capstone::arch::x86::X86OperandType; 2 | use capstone::{prelude::*, Insn, arch::x86::X86Operand}; 3 | use goblin::elf::Elf; 4 | use goblin::elf::section_header::SHT_SYMTAB; 5 | 6 | #[derive(Debug, Clone)] 7 | pub enum VarPos { 8 | Reg(String), 9 | Mem(String, i64), 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct FnVarPos { 14 | pub addr: u64, 15 | pub size: u64, 16 | pub pos: VarPos, 17 | } 18 | 19 | struct FnCode<'a> { 20 | name: String, 21 | addr: u64, 22 | size: u64, 23 | code: &'a [u8], 24 | } 25 | 26 | fn collect_callee_addr(elf: &Elf) -> Vec { 27 | let mut fns: Vec = Vec::new(); 28 | for section_header in &elf.section_headers { 29 | if section_header.sh_type != SHT_SYMTAB { 30 | continue; 31 | } 32 | let symtab = &elf.syms; 33 | let strtab = &elf.strtab; 34 | for symbol in symtab.iter() { 35 | if let Some(name) = strtab.get_at(symbol.st_name) { 36 | if name == "luaV_execute" || name == "ccall" { 37 | fns.push(symbol.st_value); 38 | } 39 | } 40 | } 41 | } 42 | fns 43 | } 44 | 45 | fn collect_caller_code<'a>(elf: &'a Elf<'a>, elf_data: &'a Vec) -> Vec> { 46 | let mut fns: Vec> = Vec::new(); 47 | for section_header in &elf.section_headers { 48 | if section_header.sh_type != SHT_SYMTAB { 49 | continue; 50 | } 51 | let symtab = &elf.syms; 52 | let strtab = &elf.strtab; 53 | for symbol in symtab.iter() { 54 | if let Some(name) = strtab.get_at(symbol.st_name) { 55 | if name == "luaD_call" || name == "luaD_callnoyield" || name == "luaD_rawrunprotected" { 56 | let start = symbol.st_value as usize; 57 | let end = start + symbol.st_size as usize; 58 | fns.push(FnCode { 59 | name: String::from(name), 60 | addr: symbol.st_value, 61 | size: symbol.st_size, 62 | code: &elf_data[start..end], 63 | }); 64 | } 65 | } 66 | } 67 | } 68 | fns 69 | } 70 | 71 | fn instr_ops(cs: &Capstone, instr: &Insn) -> (String, Vec) { 72 | let detail = cs.insn_detail(instr).unwrap(); 73 | let arc_detail = detail.arch_detail(); 74 | let arc_detail = arc_detail.x86().unwrap(); 75 | let instr_name = cs.insn_name(instr.id()).unwrap(); 76 | let ops: Vec<_> = arc_detail.operands().map(|op| op.clone()).collect(); 77 | (instr_name, ops) 78 | } 79 | 80 | fn trace_reg(mov_ops: &mut X86Operand, mut mov_idx: usize, callee_save_reg: &Vec, instructions: capstone::Instructions<'_>, cs: &Capstone) { 81 | if let X86OperandType::Reg(reg) = mov_ops.op_type { 82 | let reg_id: u32 = reg.0 as u32; 83 | while mov_idx > 0 && !callee_save_reg.contains(®_id) { 84 | mov_idx -= 1; 85 | let instr = &instructions[mov_idx]; 86 | let (inst_name, ops) = instr_ops(cs, &instr); 87 | if inst_name == "mov" && ops.len() == 2 && ops[1].op_type == X86OperandType::Reg(reg) { 88 | *mov_ops = ops[0].clone(); 89 | if let X86OperandType::Reg(reg) = mov_ops.op_type { 90 | let reg_id: u32 = reg.0 as u32; 91 | if callee_save_reg.contains(®_id) { 92 | break 93 | } 94 | } else { 95 | break 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | pub fn collect_lua_fn_var_pos<'a>(elf: &'a Elf<'a>, elf_data: &'a Vec) -> Vec { 103 | let cs = Capstone::new() 104 | .x86() 105 | .mode(arch::x86::ArchMode::Mode64) 106 | .syntax(arch::x86::ArchSyntax::Att) 107 | .detail(true) 108 | .build().unwrap(); 109 | let callee_save_reg = vec!{ 110 | arch::x86::X86Reg::X86_REG_RBX, 111 | arch::x86::X86Reg::X86_REG_RBP, 112 | arch::x86::X86Reg::X86_REG_RSP, 113 | arch::x86::X86Reg::X86_REG_R12, 114 | arch::x86::X86Reg::X86_REG_R13, 115 | arch::x86::X86Reg::X86_REG_R14, 116 | arch::x86::X86Reg::X86_REG_R15, 117 | }; 118 | let mut fn_var_pos: Vec = Vec::new(); 119 | let callee = collect_callee_addr(elf); 120 | let callers = collect_caller_code(elf, elf_data); 121 | for caller in callers.iter() { 122 | let mut mov_idx: usize = 0; 123 | let mut mov_ops: X86Operand = Default::default(); 124 | let instructions = cs.disasm_all(caller.code, caller.addr).unwrap(); 125 | for (i, instr) in instructions.iter().enumerate() { 126 | let (inst_name, ops) = instr_ops(&cs, &instr); 127 | match inst_name.as_str() { 128 | "mov" => { 129 | if ops.len() == 2 && ops[1].op_type == X86OperandType::Reg(arch::x86::X86Reg::X86_REG_RDI.into()) { 130 | mov_idx = i; 131 | mov_ops = ops[0].clone(); 132 | } 133 | continue 134 | }, 135 | "call" => {}, 136 | _ => continue 137 | } 138 | match caller.name.as_str() { 139 | "luaD_call" | "luaD_callnoyield" => { 140 | let hit = match ops[0].op_type { 141 | X86OperandType::Imm(addr) => { 142 | let addr = addr as u64; 143 | callee.contains(&addr) 144 | } 145 | _ => false 146 | }; 147 | if hit { 148 | break 149 | } 150 | }, 151 | "luaD_rawrunprotected" => { 152 | let hit = ops[0].op_type == X86OperandType::Imm(arch::x86::X86Reg::X86_REG_RDI.into()); 153 | if hit { 154 | break 155 | } 156 | }, 157 | _ => {} 158 | } 159 | } 160 | trace_reg(&mut mov_ops, mov_idx, &callee_save_reg, instructions, &cs); 161 | let pos = match mov_ops.op_type { 162 | X86OperandType::Reg(reg_id) => { 163 | VarPos::Reg(cs.reg_name(reg_id).unwrap()) 164 | }, 165 | X86OperandType::Mem(mem) => { 166 | let base_reg = mem.base().0 as u32; 167 | if !callee_save_reg.contains(&base_reg) { 168 | panic!("unsupported mem address:{}", cs.reg_name(mem.base()).unwrap()); 169 | } 170 | VarPos::Mem(cs.reg_name(mem.base()).unwrap(), mem.disp()) 171 | } 172 | _ => panic!("unsupported mov ops:{:?}", mov_ops.op_type), 173 | }; 174 | fn_var_pos.push(FnVarPos { 175 | addr: caller.addr, 176 | size: caller.size, 177 | pos: pos, 178 | }); 179 | } 180 | fn_var_pos 181 | } 182 | 183 | --------------------------------------------------------------------------------