diff --git a/docs/reference_guide.md b/docs/reference_guide.md index 3baa318bd65d..6c885fea527a 100644 --- a/docs/reference_guide.md +++ b/docs/reference_guide.md @@ -136,6 +136,7 @@ ENVIRONMENT: BPFTRACE_NO_CPP_DEMANGLE [default: 0] disable C++ symbol demangling BPFTRACE_MAP_KEYS_MAX [default: 4096] max keys in a map BPFTRACE_MAX_PROBES [default: 512] max number of probes bpftrace can attach to + BPFTRACE_VMLINUX [default: none] vmlinux path used for kernel symbol resolution EXAMPLES: bpftrace -l '*sleep*' @@ -436,6 +437,14 @@ Default: 512 This is the maximum number of probes that bpftrace can attach to. Increasing the value will consume more memory, increase startup times and can incur high performance overhead or even freeze or crash the system. +### 9.5 `BPFTRACE_VMLINUX` + +Default: None + +This specifies the vmlinux path used for kernel symbol resolution when attaching kprobe to offset. +If this value is not given, bpftrace searches vmlinux from pre defined locations. +See src/attached_probe.cpp:find_vmlinux() for details. + ## 10. Clang Environment Variables bpftrace parses header files using libclang, the C interface to Clang. @@ -676,7 +685,7 @@ Some probe types allow wildcards to match multiple probes, eg, `kprobe:vfs_*`. Y Syntax: ``` -kprobe:function_name +kprobe:function_name[+offset] kretprobe:function_name ``` @@ -695,6 +704,36 @@ sleep by 3669 ^C ``` +It's also possible to specify offset within the probed function: + +``` +# gdb -q /usr/lib/debug/boot/vmlinux-`uname -r` --ex 'disassemble do_sys_open' +Reading symbols from /usr/lib/debug/boot/vmlinux-5.0.0-32-generic...done. +Dump of assembler code for function do_sys_open: + 0xffffffff812b2ed0 <+0>: callq 0xffffffff81c01820 <__fentry__> + 0xffffffff812b2ed5 <+5>: push %rbp + 0xffffffff812b2ed6 <+6>: mov %rsp,%rbp + 0xffffffff812b2ed9 <+9>: push %r15 +... +# bpftrace -e 'kprobe:do_sys_open+9 { printf("in here\n"); }' +Attaching 1 probe... +in here +... +``` + +The address is being checked using vmlinux (with debug symbols) if it's aligned with instruction +boundaries and within the function. If it's not, we fail to add it: +``` +# bpftrace -e 'kprobe:do_sys_open+1 { printf("in here\n"); }' +Attaching 1 probe... +Could not add kprobe into middle of instruction: /usr/lib/debug/boot/vmlinux-5.0.0-32-generic:do_sys_open+1 +``` + +If bpftrace is compiled with `ALLOW_UNSAFE_PROBE` option, you can use --unsafe option to skip the check. +In this case, linux kernel still checks instruction alignment. + +The default vmlinux path can be overridden using the environment variable `BPFTRACE_VMLINUX`. + ## 2. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level Arguments Syntax: @@ -835,7 +874,7 @@ Attaching 1 probe... Could not add uprobe into middle of instruction: /bin/bash:main+1 ``` -Unless you use --unsafe option: +If bpftrace is compiled with `ALLOW_UNSAFE_PROBE` option, you can use --unsafe option to skip the check: ``` # bpftrace -e 'uprobe:/bin/bash:main+1 { printf("in here\n"); } --unsafe' diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27ddb646a37f..5171a96ac189 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,9 +60,9 @@ if(HAVE_BFD_DISASM) endif(STATIC_LINKING) endif(HAVE_BFD_DISASM) -if (ALLOW_UNSAFE_UPROBE) - target_compile_definitions(bpftrace PRIVATE HAVE_UNSAFE_UPROBE) -endif(ALLOW_UNSAFE_UPROBE) +if (ALLOW_UNSAFE_PROBE) + target_compile_definitions(bpftrace PRIVATE HAVE_UNSAFE_PROBE) +endif(ALLOW_UNSAFE_PROBE) target_link_libraries(bpftrace arch ast parser resources) diff --git a/src/ast/ast.h b/src/ast/ast.h index aad41dd4785a..c22239f4a200 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -296,15 +296,26 @@ class AttachPoint : public Node { location loc=location()) : Node(loc), provider(probetypeName(provider)), target(target), ns(ns), func(func), need_expansion(need_expansion) { } AttachPoint(const std::string &provider, - const std::string &target, + const std::string &str, uint64_t val, - location loc=location()) - : Node(loc), provider(probetypeName(provider)), target(target), need_expansion(true) + location loc = location()) + : Node(loc), provider(probetypeName(provider)), need_expansion(true) { if (this->provider == "uprobe" || this->provider == "uretprobe") + { + target = str; address = val; + } + else if (this->provider == "kprobe") + { + func = str; + func_offset = val; + } else + { + target = str; freq = val; + } } AttachPoint(const std::string &provider, const std::string &target, diff --git a/src/attached_probe.cpp b/src/attached_probe.cpp index 130b1030047f..d86b3795d8c6 100644 --- a/src/attached_probe.cpp +++ b/src/attached_probe.cpp @@ -4,8 +4,9 @@ #include #include #include -#include #include +#include +#include #include #include #include @@ -89,11 +90,11 @@ AttachedProbe::AttachedProbe(Probe &probe, std::tuple func switch (probe_.type) { case ProbeType::kprobe: - attach_kprobe(); + attach_kprobe(safe_mode); break; case ProbeType::kretprobe: check_banned_kretprobes(probe_.attach_point); - attach_kprobe(); + attach_kprobe(safe_mode); break; case ProbeType::uprobe: case ProbeType::uretprobe: @@ -202,7 +203,9 @@ std::string AttachedProbe::eventname() const { case ProbeType::kprobe: case ProbeType::kretprobe: - return eventprefix() + sanitise(probe_.attach_point) + index_str; + offset_str << std::hex << offset_; + return eventprefix() + sanitise(probe_.attach_point) + "_" + + offset_str.str() + index_str; case ProbeType::uprobe: case ProbeType::uretprobe: case ProbeType::usdt: @@ -274,6 +277,63 @@ resolve_offset(const std::string &path, const std::string &symbol, uint64_t loc) return bcc_sym.offset; } +static void check_alignment(std::string &path, + std::string &symbol, + uint64_t sym_offset, + uint64_t func_offset, + bool safe_mode, + ProbeType type) +{ + Disasm dasm(path); + AlignState aligned = dasm.is_aligned(sym_offset, func_offset); + std::string probe_name = probetypeName(type); + + std::string tmp = path + ":" + symbol + "+" + std::to_string(func_offset); + + if (AlignState::Ok == aligned) + return; + + // If we did not allow unaligned uprobes in the + // compile time, force the safe mode now. +#ifndef HAVE_UNSAFE_PROBE + safe_mode = true; +#endif + + switch (aligned) + { + case AlignState::NotAlign: + if (safe_mode) + throw std::runtime_error("Could not add " + probe_name + + " into middle of instruction: " + tmp); + else + std::cerr << "Unsafe " + probe_name + + " in the middle of the instruction: " + << tmp << std::endl; + break; + + case AlignState::Fail: + if (safe_mode) + throw std::runtime_error("Failed to check if " + probe_name + + " is in proper place: " + tmp); + else + std::cerr << "Unchecked " + probe_name + ": " << tmp << std::endl; + break; + + case AlignState::NotSupp: + if (safe_mode) + throw std::runtime_error("Can't check if " + probe_name + + " is in proper place (compiled without " + "(k|u)probe offset support): " + + tmp); + else + std::cerr << "Unchecked " + probe_name + " : " << tmp << std::endl; + break; + + default: + throw std::runtime_error("Internal error: " + tmp); + } +} + void AttachedProbe::resolve_offset_uprobe(bool safe_mode) { struct bcc_symbol_option option = { }; @@ -329,46 +389,106 @@ void AttachedProbe::resolve_offset_uprobe(bool safe_mode) if (func_offset == 0) return; - Disasm dasm(probe_.path); - AlignState aligned = dasm.is_aligned(sym_offset, func_offset); + check_alignment( + probe_.path, symbol, sym_offset, func_offset, safe_mode, probe_.type); +} - std::string tmp = probe_.path + ":" + symbol + "+" + std::to_string(func_offset); +static std::string find_vmlinux() +{ + char *path = std::getenv("BPFTRACE_VMLINUX"); + if (path) + return path; + + struct utsname buf; + uname(&buf); + + // based on btf_location in btf.cpp + const char *vmlinux_locs[] = { + "/boot/vmlinux-%1$s", + "/lib/modules/%1$s/vmlinux-%1$s", + "/lib/modules/%1$s/build/vmlinux", + "/usr/lib/modules/%1$s/kernel/vmlinux", + "/usr/lib/debug/boot/vmlinux-%1$s", + "/usr/lib/debug/boot/vmlinux-%1$s.debug", + "/usr/lib/debug/lib/modules/%1$s/vmlinux", + nullptr, + }; + + for (int i = 0; vmlinux_locs[i]; i++) + { + char path[PATH_MAX + 1]; + snprintf(path, PATH_MAX, vmlinux_locs[i], buf.release); + if (access(path, R_OK)) + continue; + return path; + } - if (AlignState::Ok == aligned) - return; + return ""; +} - // If we did not allow unaligned uprobes in the - // compile time, force the safe mode now. -#ifndef HAVE_UNSAFE_UPROBE +void AttachedProbe::resolve_offset_kprobe(bool safe_mode) +{ + struct bcc_symbol_option option = {}; + struct symbol sym = {}; + std::string &symbol = probe_.attach_point; + uint64_t func_offset = probe_.func_offset; + offset_ = func_offset; + +#ifndef HAVE_UNSAFE_PROBE safe_mode = true; #endif - switch (aligned) - { - case AlignState::NotAlign: - if (safe_mode) - throw std::runtime_error("Could not add uprobe into middle of instruction: " + tmp); - else - std::cerr << "Unsafe uprobe in the middle of the instruction: " << tmp << std::endl; - break; + if (func_offset == 0) + return; - case AlignState::Fail: - if (safe_mode) - throw std::runtime_error("Failed to check if uprobe is in proper place: " + tmp); - else - std::cerr << "Unchecked uprobe: " << tmp << std::endl; - break; + sym.name = symbol; + option.use_debug_file = 0; + option.use_symbol_type = BCC_SYM_ALL_TYPES; - case AlignState::NotSupp: - if (safe_mode) - throw std::runtime_error("Can't check if uprobe is in proper place (compiled without uprobe offset support): " + tmp); - else - std::cerr << "Unchecked uprobe: " << tmp << std::endl; - break; + std::string path = find_vmlinux(); + if (path.empty()) + { + if (safe_mode) + { + std::stringstream buf; + buf << "Could not find vmlinux to check the offset."; + buf << " Use BPFTRACE_VMLINUX env variable to specify vmlinux path."; +#ifdef HAVE_UNSAFE_PROBE + buf << " Use --unsafe to skip the userspace check."; +#else + buf << " Compile bpftrace with ALLOW_UNAFE_PROBE option to force skip " + "the check."; +#endif + throw std::runtime_error(buf.str()); + } + else + { + // linux kernel checks alignment, but not the function bounds + if (bt_verbose) + std::cout << "No vmlinux found. Skip offset checking." << std::endl; + return; + } + } + if (bt_verbose) + std::cout << "vmlinux: using " << path << std::endl; + bcc_elf_foreach_sym(path.c_str(), sym_name_cb, &option, &sym); - default: - throw std::runtime_error("Internal error: " + tmp); + if (!sym.start) + throw std::runtime_error("Could not resolve symbol: " + path + ":" + + symbol); + + if (func_offset >= sym.size) + { + std::stringstream ss; + ss << sym.size; + throw std::runtime_error("Offset outside the function bounds ('" + symbol + + "' size is " + ss.str() + ")"); } + + uint64_t sym_offset = resolve_offset(path, probe_.attach_point, probe_.loc); + + check_alignment( + path, symbol, sym_offset, func_offset, safe_mode, probe_.type); } /** @@ -562,10 +682,16 @@ void AttachedProbe::load_prog() // although not ideal. typedef int (*attach_probe_wrapper_signature)(int, enum bpf_probe_attach_type, const char*, const char*, uint64_t, int); -void AttachedProbe::attach_kprobe() +void AttachedProbe::attach_kprobe(bool safe_mode) { - int perf_event_fd = cast_signature(&bpf_attach_kprobe)(progfd_, attachtype(probe_.type), - eventname().c_str(), probe_.attach_point.c_str(), 0, 0); + resolve_offset_kprobe(safe_mode); + int perf_event_fd = cast_signature( + &bpf_attach_kprobe)(progfd_, + attachtype(probe_.type), + eventname().c_str(), + probe_.attach_point.c_str(), + offset_, + 0); if (perf_event_fd < 0) { if (probe_.orig_name != probe_.name) { diff --git a/src/attached_probe.h b/src/attached_probe.h index 8404b178d111..411672e9230c 100644 --- a/src/attached_probe.h +++ b/src/attached_probe.h @@ -24,9 +24,10 @@ class AttachedProbe std::string eventprefix() const; std::string eventname() const; static std::string sanitise(const std::string &str); + void resolve_offset_kprobe(bool safe_mode); void resolve_offset_uprobe(bool safe_mode); void load_prog(); - void attach_kprobe(); + void attach_kprobe(bool safe_mode); void attach_uprobe(bool safe_mode); void attach_usdt(int pid); void attach_tracepoint(); diff --git a/src/main.cpp b/src/main.cpp index 62ea12174cce..1d1b3e992fa7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,6 +34,7 @@ enum class OutputBufferConfig { void usage() { + // clang-format off std::cerr << "USAGE:" << std::endl; std::cerr << " bpftrace [options] filename" << std::endl; std::cerr << " bpftrace [options] -e 'program'" << std::endl << std::endl; @@ -62,6 +63,7 @@ void usage() std::cerr << " BPFTRACE_MAX_PROBES [default: 512] max number of probes" << std::endl; std::cerr << " BPFTRACE_LOG_SIZE [default: 409600] log size in bytes" << std::endl; std::cerr << " BPFTRACE_NO_USER_SYMBOLS [default: 0] disable user symbol resolution" << std::endl; + std::cerr << " BPFTRACE_VMLINUX [default: none] vmlinux path " "used for kernel symbol resolution" << std::endl; std::cerr << std::endl; std::cerr << "EXAMPLES:" << std::endl; std::cerr << "bpftrace -l '*sleep*'" << std::endl; @@ -70,6 +72,7 @@ void usage() std::cerr << " trace processes calling sleep" << std::endl; std::cerr << "bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'" << std::endl; std::cerr << " count syscalls by process name" << std::endl; + // clang-format on } static void enforce_infinite_rlimit() { diff --git a/src/parser.yy b/src/parser.yy index 71466e3210ca..28f8e7aaf796 100644 --- a/src/parser.yy +++ b/src/parser.yy @@ -170,6 +170,7 @@ attach_points : attach_points "," attach_point { $$ = $1; $1->push_back($3); } attach_point : ident { $$ = new ast::AttachPoint($1, @$); } | ident ":" wildcard { $$ = new ast::AttachPoint($1, $3, @$); } + | ident ":" wildcard PLUS INT { $$ = new ast::AttachPoint($1, $3, $5, @$); } | ident PATH STRING { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, false, @$); } | ident PATH wildcard { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, true, @$); } | ident PATH wildcard PLUS INT { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, (uint64_t) $5, @$); } diff --git a/tests/ast.cpp b/tests/ast.cpp index 7fee8be01c3c..77db9877d835 100644 --- a/tests/ast.cpp +++ b/tests/ast.cpp @@ -33,6 +33,12 @@ TEST(ast, probe_name_kprobe) AttachPointList attach_points2 = { &ap1, &ap2 }; Probe kprobe2(&attach_points2, nullptr, nullptr); EXPECT_EQ(kprobe2.name(), "kprobe:sys_read,kprobe:sys_write"); + + AttachPoint ap3("kprobe", "sys_read", (uint64_t)10); + AttachPointList attach_points3 = { &ap1, &ap2, &ap3 }; + Probe kprobe3(&attach_points3, nullptr, nullptr); + EXPECT_EQ(kprobe3.name(), + "kprobe:sys_read,kprobe:sys_write,kprobe:sys_read+10"); } TEST(ast, probe_name_uprobe) diff --git a/tests/bpftrace.cpp b/tests/bpftrace.cpp index 4648658f5f47..146fdbfef595 100644 --- a/tests/bpftrace.cpp +++ b/tests/bpftrace.cpp @@ -10,12 +10,23 @@ namespace bpftrace { using ::testing::ContainerEq; using ::testing::StrictMock; -void check_kprobe(Probe &p, const std::string &attach_point, const std::string &orig_name) +static const std::string kprobe_name(const std::string &attach_point, + uint64_t func_offset) +{ + auto str = func_offset ? "+" + std::to_string(func_offset) : ""; + return "kprobe:" + attach_point + str; +} + +void check_kprobe(Probe &p, + const std::string &attach_point, + const std::string &orig_name, + uint64_t func_offset = 0) { EXPECT_EQ(ProbeType::kprobe, p.type); EXPECT_EQ(attach_point, p.attach_point); EXPECT_EQ(orig_name, p.orig_name); - EXPECT_EQ("kprobe:" + attach_point, p.name); + EXPECT_EQ(kprobe_name(attach_point, func_offset), p.name); + EXPECT_EQ(func_offset, p.func_offset); } static const std::string uprobe_name(const std::string &path, const std::string &attach_point, @@ -203,6 +214,23 @@ TEST(bpftrace, add_probes_wildcard_no_matches) check_kprobe(bpftrace->get_probes().at(1), "sys_write", probe_orig_name); } +TEST(bpftrace, add_probes_offset) +{ + uint64_t offset = 10; + ast::AttachPoint a("kprobe", "sys_read", offset); + ast::AttachPointList attach_points = { &a }; + ast::Probe probe(&attach_points, nullptr, nullptr); + + StrictMock bpftrace; + ASSERT_EQ(0, bpftrace.add_probe(probe)); + ASSERT_EQ(1U, bpftrace.get_probes().size()); + ASSERT_EQ(0U, bpftrace.get_special_probes().size()); + + std::string probe_orig_name = "kprobe:sys_read+" + std::to_string(offset); + check_kprobe( + bpftrace.get_probes().at(0), "sys_read", probe_orig_name, offset); +} + TEST(bpftrace, add_probes_uprobe) { ast::AttachPoint a("uprobe", "/bin/sh", "foo", true); diff --git a/tests/runtime/probe b/tests/runtime/probe index e45acd90256b..eaf9a59ea02c 100644 --- a/tests/runtime/probe +++ b/tests/runtime/probe @@ -19,6 +19,16 @@ EXPECT first second TIMEOUT 5 AFTER /bin/bash -c "sleep 1e-6"; /bin/bash -c "sleep 1e-6" +NAME kprobe_offset +RUN bpftrace -v -e 'kprobe:vfs_read+0 { printf("SUCCESS '$test' %d\n", pid); exit(); }' +EXPECT SUCCESS kprobe_offset [0-9][0-9]* +TIMEOUT 5 + +NAME kprobe_offset_fail_size +RUN bpftrace -v -e 'kprobe:vfs_read+1000000 { printf("SUCCESS '$test' %d\n", pid); exit(); }' +EXPECT Offset outside the function bounds \('vfs_read' size is* +TIMEOUT 5 + NAME kretprobe RUN bpftrace -v -e 'kretprobe:vfs_read { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS kretprobe [0-9][0-9]*