mirror of https://github.com/yshui/picom.git
add interceptor to send stack traces
This commit is contained in:
parent
3fd06f2dd0
commit
40be723870
|
@ -33,6 +33,7 @@
|
||||||
buildInputs = defaultPackage.buildInputs ++ (with pkgs; [
|
buildInputs = defaultPackage.buildInputs ++ (with pkgs; [
|
||||||
clang-tools_17
|
clang-tools_17
|
||||||
llvmPackages_17.clang-unwrapped.python
|
llvmPackages_17.clang-unwrapped.python
|
||||||
|
libunwind
|
||||||
]);
|
]);
|
||||||
hardeningDisable = [ "fortify" ];
|
hardeningDisable = [ "fortify" ];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <libunwind-x86_64.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <threads.h>
|
||||||
|
|
||||||
|
static ssize_t (*orig_recvmsg)(int, struct msghdr *, int) = NULL;
|
||||||
|
thread_local char buffer[4096];
|
||||||
|
|
||||||
|
__attribute__((noinline)) void recvmsg_stack_trace_probe(const char *ptr, uint64_t size) {
|
||||||
|
__asm__ volatile("" : : "r"(ptr), "r"(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void backtrace() {
|
||||||
|
unw_cursor_t cursor;
|
||||||
|
unw_context_t context;
|
||||||
|
|
||||||
|
unw_getcontext(&context);
|
||||||
|
unw_init_local(&cursor, &context);
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
size_t buffer_offset = 0;
|
||||||
|
while (unw_step(&cursor)) {
|
||||||
|
unw_word_t ip, sp, off;
|
||||||
|
|
||||||
|
unw_get_reg(&cursor, UNW_REG_IP, &ip);
|
||||||
|
unw_get_reg(&cursor, UNW_REG_SP, &sp);
|
||||||
|
|
||||||
|
char symbol[256] = {"<unknown>"};
|
||||||
|
char *name = symbol;
|
||||||
|
|
||||||
|
unw_get_proc_name(&cursor, symbol, sizeof(symbol), &off);
|
||||||
|
|
||||||
|
size_t written = (size_t)snprintf(
|
||||||
|
buffer + buffer_offset, sizeof(buffer) - buffer_offset,
|
||||||
|
"#%-2d 0x%016" PRIxPTR " sp=0x%016" PRIxPTR " %s + 0x%" PRIxPTR "\n",
|
||||||
|
++n, ip, sp, name, off);
|
||||||
|
if (written >= sizeof(buffer) - buffer_offset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buffer_offset += written;
|
||||||
|
}
|
||||||
|
recvmsg_stack_trace_probe(buffer, buffer_offset);
|
||||||
|
}
|
||||||
|
ssize_t recvmsg(int socket, struct msghdr *message, int flags) {
|
||||||
|
if (!orig_recvmsg) {
|
||||||
|
orig_recvmsg = dlsym((void *)RTLD_NEXT, "recvmsg");
|
||||||
|
}
|
||||||
|
backtrace();
|
||||||
|
return orig_recvmsg(socket, message, flags);
|
||||||
|
}
|
|
@ -41,6 +41,26 @@ version = "2.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck_derive"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -202,6 +222,12 @@ version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
|
checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
|
@ -384,6 +410,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
name = "picom-tracer"
|
name = "picom-tracer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"hex",
|
||||||
"libbpf-cargo",
|
"libbpf-cargo",
|
||||||
"libbpf-rs",
|
"libbpf-rs",
|
||||||
"object",
|
"object",
|
||||||
|
|
|
@ -6,6 +6,9 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytemuck = { version = "1.14.3", features = ["derive"] }
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
hex = "0.4.3"
|
||||||
libbpf-rs = { version = "0.22.1", default-features = false, features = ["novendor"] }
|
libbpf-rs = { version = "0.22.1", default-features = false, features = ["novendor"] }
|
||||||
object = "0.32.2"
|
object = "0.32.2"
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,12 @@ struct xcb_connection_t {
|
||||||
void *setup;
|
void *setup;
|
||||||
int fd;
|
int fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||||
__type(key, u32);
|
__uint(key_size, sizeof(int));
|
||||||
__type(value, u64);
|
__uint(value_size, sizeof(u32));
|
||||||
__uint(max_entries, 256);
|
__uint(max_entries, 2);
|
||||||
} my_map SEC(".maps");
|
} events SEC(".maps");
|
||||||
|
|
||||||
struct event {
|
struct event {
|
||||||
u8 task[TASK_COMM_LEN];
|
u8 task[TASK_COMM_LEN];
|
||||||
|
@ -31,39 +30,58 @@ struct event {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct event _event = {0};
|
struct event _event = {0};
|
||||||
|
u64 pid = 0;
|
||||||
|
void *conn_ptr;
|
||||||
|
char last_stack[4096];
|
||||||
|
u64 last_recv_stack_size;
|
||||||
|
|
||||||
|
SEC("uprobe")
|
||||||
|
int BPF_KPROBE(uprobe_recvmsg, const char *trace, u64 size) {
|
||||||
|
if (pid != bpf_get_current_pid_tgid() >> 32) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
last_recv_stack_size = 0;
|
||||||
|
if (size > 4096) {
|
||||||
|
bpf_printk("invalid stack size %u", size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (bpf_probe_read_user(&last_stack[0], size, trace)) {
|
||||||
|
bpf_printk("cannot read");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
last_recv_stack_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
SEC("uprobe")
|
SEC("uprobe")
|
||||||
int BPF_KPROBE(uprobe_epoll_wait) {
|
int BPF_KPROBE(uprobe_epoll_wait) {
|
||||||
u64 *curr_pid = bpf_map_lookup_elem(&my_map, (u32[]){0});
|
if (pid != bpf_get_current_pid_tgid() >> 32) {
|
||||||
u32 pid = bpf_get_current_pid_tgid() >> 32;
|
|
||||||
if (!curr_pid || pid != *curr_pid) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
void **conn_ptr = bpf_map_lookup_elem(&my_map, (u32[]){1});
|
|
||||||
if (!conn_ptr) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
struct xcb_connection_t conn;
|
struct xcb_connection_t conn;
|
||||||
if (bpf_probe_read_user(&conn, sizeof(conn), *conn_ptr)) {
|
if (bpf_probe_read_user(&conn, sizeof(conn), conn_ptr)) {
|
||||||
bpf_printk("cannot read");
|
bpf_printk("cannot read");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#if 0
|
u32 queue_len;
|
||||||
u64 request_read;
|
if (bpf_probe_read_user(&queue_len, sizeof(queue_len), conn_ptr + 4212)) {
|
||||||
if (bpf_probe_read_user(&request_read, sizeof(request_read), (*conn_ptr) + 4216)) {
|
|
||||||
bpf_printk("cannot read");
|
bpf_printk("cannot read");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
bpf_printk("request read %x", request_read);
|
|
||||||
#endif
|
|
||||||
u64 event_head;
|
u64 event_head;
|
||||||
if (bpf_probe_read_user(&event_head, sizeof(event_head), (*conn_ptr) + 4272)) {
|
if (bpf_probe_read_user(&event_head, sizeof(event_head), conn_ptr + 4272)) {
|
||||||
bpf_printk("cannot read");
|
bpf_printk("cannot read");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event_head != 0) {
|
if (event_head != 0 || queue_len != 0) {
|
||||||
bpf_printk("epoll_wait %d %p", conn.fd, event_head);
|
bpf_printk("epoll_wait %d %p %d", conn.fd, event_head, queue_len);
|
||||||
|
char data[16];
|
||||||
|
*(u64 *)data = event_head;
|
||||||
|
*(u64 *)(data + 8) = (u64)queue_len;
|
||||||
|
bpf_perf_event_output(ctx, &events, 1, data, 16);
|
||||||
|
if (last_recv_stack_size <= 4096) {
|
||||||
|
bpf_perf_event_output(ctx, &events, 0, last_stack, last_recv_stack_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -71,14 +89,13 @@ int BPF_KPROBE(uprobe_epoll_wait) {
|
||||||
SEC("uprobe")
|
SEC("uprobe")
|
||||||
int BPF_KPROBE(uprobe_xcb_conn, void *ptr) {
|
int BPF_KPROBE(uprobe_xcb_conn, void *ptr) {
|
||||||
struct xcb_connection_t conn;
|
struct xcb_connection_t conn;
|
||||||
u64 pid = bpf_get_current_pid_tgid() >> 32;
|
pid = bpf_get_current_pid_tgid() >> 32;
|
||||||
bpf_map_update_elem(&my_map, (u32[]){0}, &pid, 0);
|
|
||||||
bpf_printk("xcb connection is %p", ptr);
|
bpf_printk("xcb connection is %p", ptr);
|
||||||
if (bpf_probe_read_user(&conn, sizeof(conn), ptr)) {
|
if (bpf_probe_read_user(&conn, sizeof(conn), ptr)) {
|
||||||
bpf_printk("cannot read");
|
bpf_printk("cannot read");
|
||||||
} else {
|
} else {
|
||||||
bpf_map_update_elem(&my_map, (u32[]){1}, &ptr, 0);
|
|
||||||
bpf_printk("fd is %d", conn.fd);
|
bpf_printk("fd is %d", conn.fd);
|
||||||
|
conn_ptr = ptr;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use libbpf_rs::skel::{OpenSkel, Skel, SkelBuilder};
|
use libbpf_rs::skel::{OpenSkel, Skel, SkelBuilder};
|
||||||
use libbpf_rs::{PerfBufferBuilder, UprobeOpts};
|
use libbpf_rs::{MapFlags, PerfBufferBuilder, UprobeOpts};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use byteorder::ByteOrder;
|
||||||
use object::read::elf::{Dyn, FileHeader};
|
use object::read::elf::{Dyn, FileHeader};
|
||||||
|
|
||||||
mod uprobe {
|
mod uprobe {
|
||||||
|
@ -8,15 +10,30 @@ mod uprobe {
|
||||||
}
|
}
|
||||||
use object::Object;
|
use object::Object;
|
||||||
use uprobe::*;
|
use uprobe::*;
|
||||||
|
thread_local! {
|
||||||
|
static SKEL: RefCell<Option<UprobeSkel<'static>>> = RefCell::new(None);
|
||||||
|
}
|
||||||
fn handle_lost_events(cpu: i32, count: u64) {
|
fn handle_lost_events(cpu: i32, count: u64) {
|
||||||
eprintln!("Lost {count} events on CPU {cpu}");
|
eprintln!("Lost {count} events on CPU {cpu}");
|
||||||
}
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(bytemuck::Pod, bytemuck::Zeroable, Copy, Clone)]
|
||||||
|
struct Event {
|
||||||
|
event_head: u64,
|
||||||
|
queue_len: u64,
|
||||||
|
}
|
||||||
fn handle_event(cpu: i32, data: &[u8]) {
|
fn handle_event(cpu: i32, data: &[u8]) {
|
||||||
eprintln!("Got {} bytes of data on {cpu}", data.len());
|
if cpu == 0 {
|
||||||
|
eprintln!("{}", String::from_utf8_lossy(data));
|
||||||
|
} else if cpu == 1 {
|
||||||
|
let event: Event = bytemuck::pod_read_unaligned(&data[..16]);
|
||||||
|
eprintln!("queue_len: {}, event_head: {:#x}", event.queue_len, event.event_head);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut builder = UprobeSkelBuilder::default();
|
let mut builder = UprobeSkelBuilder::default();
|
||||||
let picom_path = std::env::args().nth(1).unwrap();
|
let picom_path = std::env::args().nth(1).unwrap();
|
||||||
|
let interceptor_path = std::env::args().nth(2).unwrap();
|
||||||
let data = std::fs::read(&picom_path).unwrap();
|
let data = std::fs::read(&picom_path).unwrap();
|
||||||
let file = object::read::elf::ElfFile64::<'_, object::NativeEndian, _>::parse(&*data).unwrap();
|
let file = object::read::elf::ElfFile64::<'_, object::NativeEndian, _>::parse(&*data).unwrap();
|
||||||
let header = file.raw_header();
|
let header = file.raw_header();
|
||||||
|
@ -57,6 +74,12 @@ fn main() {
|
||||||
let open_skel = builder.open().unwrap();
|
let open_skel = builder.open().unwrap();
|
||||||
let mut skel = open_skel.load().unwrap();
|
let mut skel = open_skel.load().unwrap();
|
||||||
let obj = skel.object_mut();
|
let obj = skel.object_mut();
|
||||||
|
let recv_probe = obj.prog_mut("uprobe_recvmsg").unwrap();
|
||||||
|
let _link0 = recv_probe.attach_uprobe_with_opts(-1, &interceptor_path, 0, UprobeOpts {
|
||||||
|
retprobe: false,
|
||||||
|
func_name: "recvmsg_stack_trace_probe".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}).unwrap();
|
||||||
let xcb_probe = obj.prog_mut("uprobe_xcb_conn").unwrap();
|
let xcb_probe = obj.prog_mut("uprobe_xcb_conn").unwrap();
|
||||||
let _link = xcb_probe.attach_uprobe_with_opts(-1, &picom_path, 0, UprobeOpts {
|
let _link = xcb_probe.attach_uprobe_with_opts(-1, &picom_path, 0, UprobeOpts {
|
||||||
retprobe: false,
|
retprobe: false,
|
||||||
|
@ -64,10 +87,20 @@ fn main() {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
let epoll_probe = obj.prog_mut("uprobe_epoll_wait").unwrap();
|
let epoll_probe = obj.prog_mut("uprobe_epoll_wait").unwrap();
|
||||||
let _link2 = epoll_probe.attach_uprobe_with_opts(-1, libc_path, 0, UprobeOpts {
|
let _link2 = epoll_probe.attach_uprobe_with_opts(-1, &libc_path, 0, UprobeOpts {
|
||||||
retprobe: false,
|
retprobe: false,
|
||||||
func_name: "epoll_wait".to_string(),
|
func_name: "epoll_wait".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
std::thread::park();
|
|
||||||
|
let perf = PerfBufferBuilder::new(skel.maps_mut().events())
|
||||||
|
.sample_cb(handle_event)
|
||||||
|
.lost_cb(handle_lost_events)
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
SKEL.with_borrow_mut(|s| *s = Some(skel));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
perf.poll(std::time::Duration::from_millis(100)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue