Skip to content

Commit

Permalink
Add support to attach kprobe to offset
Browse files Browse the repository at this point in the history
Example: `bpftrace -e 'kprobe:do_sys_open+9 { ... }'`.

This is based on the support of attaching uprobe to offset (bpftrace#803).  The
offset is checked whether it is an instruction boundary using vmlinux
(with debug symbols).  The environment variable `BPFTRACE_VMLINUX` can
be used to specify vmlinux file.  ALLOW_UNSAFE_UPROBE option is renamed
to ALLOW_UNSASFE_PROBE so that it supports both uprobe and kprobe.

If bpftrace is compoiled with ALLOW_UNSASFE_PROBE option and vmlinux
file is not found, and --unsafe option is given, the userspace check is
skipped.  Note that linux kernel checks the address alignment, but it
does not check whether it is within the function.

Closes bpftrace#42
  • Loading branch information
mmisono committed Jan 12, 2020
1 parent add4117 commit 4bc9951
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 48 deletions.
43 changes: 41 additions & 2 deletions docs/reference_guide.md
Expand Up @@ -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*'
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
```

Expand All @@ -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:
Expand Down Expand Up @@ -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'
Expand Down
6 changes: 3 additions & 3 deletions src/CMakeLists.txt
Expand Up @@ -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)

Expand Down
17 changes: 14 additions & 3 deletions src/ast/ast.h
Expand Up @@ -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,
Expand Down
200 changes: 163 additions & 37 deletions src/attached_probe.cpp
Expand Up @@ -4,8 +4,9 @@
#include <fstream>
#include <iostream>
#include <link.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <linux/limits.h>
#include <linux/perf_event.h>
#include <regex>
#include <sys/auxv.h>
#include <sys/utsname.h>
Expand Down Expand Up @@ -89,11 +90,11 @@ AttachedProbe::AttachedProbe(Probe &probe, std::tuple<uint8_t *, uintptr_t> 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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = { };
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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<attach_probe_wrapper_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<attach_probe_wrapper_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) {
Expand Down
3 changes: 2 additions & 1 deletion src/attached_probe.h
Expand Up @@ -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();
Expand Down

0 comments on commit 4bc9951

Please sign in to comment.