Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add path builtin function #1492

Merged
merged 8 commits into from
Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to
## Unreleased

#### Added
- Add path builtin
- [#1492](https://github.com/iovisor/bpftrace/pull/1492)
- Allow wildcards for tracepoint categories
- [#1445](https://github.com/iovisor/bpftrace/pull/1445)
- Add wildcard support for kfunc probe types
Expand Down
29 changes: 29 additions & 0 deletions docs/reference_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ discussion to other files in /docs, the /tools/\*\_examples.txt files, or blog p
- [22. `sizeof()`: Size of type or expression](#22-sizeof-size-of-type-or-expression)
- [23. `print()`: Print Value](#23-print-print-value)
- [24. `strftime()`: Formatted timestamp](#24-strftime-formatted-timestamp)
- [25. `path()`: Return full path](#25-path-return-full-path)
- [Map Functions](#map-functions)
- [1. Builtins](#1-builtins-2)
- [2. `count()`: Count](#2-count-count)
Expand Down Expand Up @@ -1958,6 +1959,7 @@ Tracing block I/O sizes > 0 bytes
- `signal(char[] signal | u32 signal)` - Send a signal to the current task
- `strncmp(char *s1, char *s2, int length)` - Compare first n characters of two strings
- `override(u64 rc)` - Override return value
- `path(struct path *path)` - Return full path

Some of these are asynchronous: the kernel queues the event, but some time later (milliseconds) it is
processed in user-space. The asynchronous actions are: `printf()`, `time()`, and `join()`. Both `ksym()`
Expand Down Expand Up @@ -2752,6 +2754,33 @@ Attaching 1 probe...
^C
```

## 25. `path()`: Return full path

Syntax:
- `path(struct path *path)`

Return full path referenced by struct path pointer in argument.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing: maybe it'd be good to document there's a whilelist of kernel functions you can use this on. Could be confusing for users who get load errors when they have a correct handle to struct path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, I added a sentence about that.. I think we need some way to get the whitelist info from kernel, I'll try to add something

There's list of allowed kernel functions, that can use this
helper in probe.

Examples:
```
# bpftrace -e 'kfunc:filp_close { printf("%s\n", path(args->filp->f_path)); }'
Attaching 1 probe...
/proc/sys/net/ipv6/conf/eno2/disable_ipv6
/proc/sys/net/ipv6/conf/eno2/use_tempaddr
socket:[23276]
/proc/sys/net/ipv6/conf/eno2/disable_ipv6
socket:[17655]
/sys/devices/pci0000:00/0000:00:1c.5/0000:04:00.1/net/eno2/type
socket:[38745]
/proc/sys/net/ipv6/conf/eno2/disable_ipv6

# bpftrace -e 'kretfunc:dentry_open { printf("%s\n", path(retval->f_path)); }'
Attaching 1 probe...
/dev/pts/1 -> /dev/pts/1
```

# Map Functions

Maps are special BPF data types that can be used to store counts, statistics, and histograms. They are
Expand Down
9 changes: 9 additions & 0 deletions src/ast/codegen_llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,15 @@ void CodegenLLVM::visit(Call &call)
expr_ = buf;
expr_deleter_ = [this, buf]() { b_.CreateLifetimeEnd(buf); };
}
else if (call.func == "path")
{
AllocaInst *buf = b_.CreateAllocaBPF(bpftrace_.strlen_, "path");
b_.CREATE_MEMSET(buf, b_.getInt8(0), bpftrace_.strlen_, 1);
call.vargs->front()->accept(*this);
b_.CreatePath(ctx_, buf, expr_, call.loc);
expr_ = buf;
expr_deleter_ = [this, buf]() { b_.CreateLifetimeEnd(buf); };
}
else if (call.func == "kaddr")
{
uint64_t addr;
Expand Down
3 changes: 3 additions & 0 deletions src/ast/field_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ void FieldAnalyser::visit(AttachPoint &ap)
{
auto stype = arg.second;

if (stype.IsPtrTy())
stype = *stype.GetPointeeTy();

if (stype.IsRecordTy())
bpftrace_.btf_set_.insert(stype.GetName());
}
Expand Down
20 changes: 20 additions & 0 deletions src/ast/irbuilderbpf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1147,5 +1147,25 @@ void IRBuilderBPF::CreateHelperErrorCond(Value *ctx,
SetInsertPoint(helper_merge_block);
}

void IRBuilderBPF::CreatePath(Value *ctx,
AllocaInst *buf,
Value *path,
const location &loc)
{
// int bpf_d_path(struct path *path, char *buf, u32 sz)
// Return: 0 or error
FunctionType *d_path_func_type = FunctionType::get(
getInt64Ty(), { getInt8PtrTy(), buf->getType(), getInt32Ty() }, false);
PointerType *d_path_func_ptr_type = PointerType::get(d_path_func_type, 0);
Constant *d_path_func = ConstantExpr::getCast(Instruction::IntToPtr,
getInt64(
libbpf::BPF_FUNC_d_path),
d_path_func_ptr_type);
CallInst *call = createCall(d_path_func,
{ path, buf, getInt32(bpftrace_.strlen_) },
"d_path");
CreateHelperErrorCond(ctx, call, libbpf::BPF_FUNC_d_path, loc);
}

} // namespace ast
} // namespace bpftrace
4 changes: 4 additions & 0 deletions src/ast/irbuilderbpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ class IRBuilderBPF : public IRBuilder<>
StructType *GetStructType(std::string name, const std::vector<llvm::Type *> & elements, bool packed = false);
AllocaInst *CreateUSym(llvm::Value *val);
Value *CreatKFuncArg(Value *ctx, SizedType& type, std::string& name);
void CreatePath(Value *ctx,
AllocaInst *buf,
Value *path,
const location &loc);
int helper_error_id_ = 0;

private:
Expand Down
72 changes: 59 additions & 13 deletions src/ast/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,42 @@ void SemanticAnalyser::visit(Call &call)

call.type = CreateUInt64();
}
else if (call.func == "path")
{
if (!bpftrace_.feature_->has_d_path())
{
LOG(ERROR, call.loc, err_)
<< "BPF_FUNC_d_path not available for your kernel version";
}

if (check_varargs(call, 1, 1))
{
// Argument for path can be both record and pointer.
// It's pointer when it's passed directly from the probe
// argument, like: path(args->path))
// It's record when it's referenced as object pointer
// member, like: path(args->filp->f_path))
if (!check_arg(call, Type::record, 0, false, false) &&
!check_arg(call, Type::pointer, 0, false, false))
{
auto &arg = *call.vargs->at(0);

LOG(ERROR, call.loc, err_)
<< "path() only supports pointer or record argument ("
<< arg.type.type << " provided)";
}

call.type = SizedType(Type::string, bpftrace_.strlen_);
}

for (auto &attach_point : *probe_->attach_points)
{
ProbeType type = probetype(attach_point->provider);
if (type != ProbeType::kfunc && type != ProbeType::kretfunc)
LOG(ERROR, call.loc, err_) << "The path function can only be used with "
<< "'kfunc', 'kretfunc' probes";
}
}
else if (call.func == "strncmp") {
if (check_nargs(call, 3)) {
check_arg(call, Type::string, 0);
Expand Down Expand Up @@ -2652,31 +2688,41 @@ bool SemanticAnalyser::check_varargs(const Call &call, size_t min_nargs, size_t
return true;
}

bool SemanticAnalyser::check_arg(const Call &call, Type type, int arg_num, bool want_literal)
bool SemanticAnalyser::check_arg(const Call &call,
Type type,
int arg_num,
bool want_literal,
bool fail)
{
if (!call.vargs)
return false;

auto &arg = *call.vargs->at(arg_num);
if (want_literal && (!arg.is_literal || arg.type.type != type))
{
LOG(ERROR, call.loc, err_) << call.func << "() expects a " << type
<< " literal (" << arg.type.type << " provided)";
if (type == Type::string)
if (fail)
{
// If the call requires a string literal and a positional parameter is
// given, tell user to use str()
auto *pos_param = dynamic_cast<PositionalParameter *>(&arg);
if (pos_param)
LOG(ERROR) << "Use str($" << pos_param->n << ") to treat $"
<< pos_param->n << " as a string";
LOG(ERROR, call.loc, err_) << call.func << "() expects a " << type
<< " literal (" << arg.type.type << " provided)";
if (type == Type::string)
{
// If the call requires a string literal and a positional parameter is
// given, tell user to use str()
auto *pos_param = dynamic_cast<PositionalParameter *>(&arg);
if (pos_param)
LOG(ERROR) << "Use str($" << pos_param->n << ") to treat $"
<< pos_param->n << " as a string";
}
}
return false;
}
else if (is_final_pass() && arg.type.type != type) {
LOG(ERROR, call.loc, err_)
<< call.func << "() only supports " << type << " arguments ("
<< arg.type.type << " provided)";
if (fail)
{
LOG(ERROR, call.loc, err_)
<< call.func << "() only supports " << type << " arguments ("
<< arg.type.type << " provided)";
}
return false;
}
return true;
Expand Down
6 changes: 5 additions & 1 deletion src/ast/semantic_analyser.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class SemanticAnalyser : public Visitor {
bool check_assignment(const Call &call, bool want_map, bool want_var, bool want_map_key);
bool check_nargs(const Call &call, size_t expected_nargs);
bool check_varargs(const Call &call, size_t min_nargs, size_t max_nargs);
bool check_arg(const Call &call, Type type, int arg_num, bool want_literal=false);
bool check_arg(const Call &call,
Type type,
int arg_num,
bool want_literal = false,
bool fail = true);
bool check_symbol(const Call &call, int arg_num);

void check_stack_call(Call &call, bool kernel);
Expand Down
33 changes: 30 additions & 3 deletions src/bpffeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ static bool try_load(const char* name,

static bool try_load(enum libbpf::bpf_prog_type prog_type,
struct bpf_insn* insns,
size_t len)
size_t len,
const char* name = nullptr)
{
constexpr int log_size = 4096;
char logbuf[log_size] = {};

// kfunc / kretfunc only for now. We can refactor if more attach types
// get added to BPF_PROG_TYPE_TRACING
if (prog_type == libbpf::BPF_PROG_TYPE_TRACING)
if (prog_type == libbpf::BPF_PROG_TYPE_TRACING && !name)
{
// List of available functions must be readable
std::ifstream traceable_funcs(kprobe_path);
Expand All @@ -88,7 +89,7 @@ static bool try_load(enum libbpf::bpf_prog_type prog_type,
"kretfunc__strlen", prog_type, insns, len, 0, logbuf, log_size);
}

return try_load(nullptr, prog_type, insns, len, 0, logbuf, log_size);
return try_load(name, prog_type, insns, len, 0, logbuf, log_size);
}

bool BPFfeature::detect_helper(enum libbpf::bpf_func_id func_id,
Expand Down Expand Up @@ -278,6 +279,31 @@ bool BPFfeature::has_map_batch()
#endif
}

bool BPFfeature::has_d_path(void)
{
if (has_d_path_.has_value())
return *has_d_path_;

struct bpf_insn insns[] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need a custom program? doesn't the helper detection work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aah, I try to load the working program and check that try_load succeeds,
but I did not noticed that the detection stuff checks for 'invalid' string,
so the load can fail for other reasons but still detect the helper.. that might
actually work.. I'll try it ;-)

thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, the tracing program fails earlier with:
Tracing programs must provide btf_id

so we need to have special function for that

BPF_LDX_MEM(BPF_W, BPF_REG_1, BPF_REG_1, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
BPF_MOV64_IMM(BPF_REG_6, 0),
BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6, 0),
BPF_LD_IMM64(BPF_REG_3, 8),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, libbpf::BPF_FUNC_d_path),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};

has_d_path_ = std::make_optional<bool>(try_load(libbpf::BPF_PROG_TYPE_TRACING,
insns,
ARRAY_SIZE(insns),
"kfunc__dentry_open"));

return *has_d_path_;
}

std::string BPFfeature::report(void)
{
std::stringstream buf;
Expand All @@ -299,6 +325,7 @@ std::string BPFfeature::report(void)
<< " send_signal: " << to_str(has_helper_send_signal())
<< " override_return: " << to_str(has_helper_override_return())
<< " get_boot_ns: " << to_str(has_helper_ktime_get_boot_ns())
<< " dpath: " << to_str(has_d_path())
<< std::endl;

buf << "Kernel features" << std::endl
Expand Down
2 changes: 2 additions & 0 deletions src/bpffeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class BPFfeature
bool has_loop();
bool has_btf();
bool has_map_batch();
bool has_d_path();

std::string report(void);

Expand All @@ -95,6 +96,7 @@ class BPFfeature

protected:
std::optional<bool> has_loop_;
std::optional<bool> has_d_path_;
std::optional<int> insns_limit_;
std::optional<bool> has_map_batch_;

Expand Down
2 changes: 1 addition & 1 deletion src/lexer.l
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ vspace [\n\r]
space {hspace}|{vspace}
path :(\\.|[_\-\./a-zA-Z0-9#\*])*:
builtin arg[0-9]|args|cgroup|comm|cpid|cpu|ctx|curtask|elapsed|func|gid|nsecs|pid|probe|rand|retval|sarg[0-9]|tid|uid|username
call avg|buf|cat|cgroupid|clear|count|delete|exit|hist|join|kaddr|kptr|ksym|lhist|max|min|ntop|override|print|printf|reg|signal|sizeof|stats|str|strftime|strncmp|sum|system|time|uaddr|uptr|usym|zero
call avg|buf|cat|cgroupid|clear|count|delete|exit|hist|join|kaddr|kptr|ksym|lhist|max|min|ntop|override|print|printf|reg|signal|sizeof|stats|str|strftime|strncmp|sum|system|time|uaddr|uptr|usym|zero|path

/* Don't add to this! Use builtin OR call not both */
call_and_builtin kstack|ustack
Expand Down
14 changes: 13 additions & 1 deletion src/libbpf/bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,19 @@ enum bpf_prog_type {
FN(ringbuf_submit), \
FN(ringbuf_discard), \
FN(ringbuf_query), \
FN(csum_level),
FN(csum_level), \
FN(skc_to_tcp6_sock), \
FN(skc_to_tcp_sock), \
FN(skc_to_tcp_timewait_sock), \
FN(skc_to_tcp_request_sock), \
FN(skc_to_udp6_sock), \
FN(get_task_stack), \
FN(load_hdr_opt), \
FN(store_hdr_opt), \
FN(reserve_hdr_opt), \
FN(inode_storage_get), \
FN(inode_storage_delete), \
FN(d_path),


/* integer value in 'imm' field of BPF_CALL instruction selects which helper
Expand Down
1 change: 1 addition & 0 deletions tests/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class MockBPFfeature : public BPFfeature
has_loop_ = std::make_optional<bool>(has_features);
has_probe_read_kernel_ = std::make_optional<bool>(has_features);
has_features_ = has_features;
has_d_path_ = std::make_optional<bool>(has_features);
};
bool has_features_;
};
Expand Down
7 changes: 7 additions & 0 deletions tests/runtime/call
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,10 @@ NAME print_hist_with_large_top_arg
RUN bpftrace -e 'BEGIN { print("BEGIN"); @[1] = hist(10); @[2] = hist(20); @[3] = hist(30); print(@, 10); print("END"); clear(@); exit(); } '
EXPECT BEGIN\n@\[1\]:(.*\n)+@\[2\]:(.*\n)+@\[3\]:(.*\n)+END
TIMEOUT 1

NAME path
RUN bpftrace -ve 'kfunc:filp_close { $f = path(args->filp->f_path); if (!strncmp($f, "/tmp/bpftrace_runtime_test_syscall_gen_read_temp", 49)) { printf("OK\n"); exit(); } }'
EXPECT OK
REQUIRES_FEATURE dpath
TIMEOUT 5
AFTER ./testprogs/syscall read
2 changes: 1 addition & 1 deletion tests/runtime/engine/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def __read_test_struct(test, test_suite):
arch = [x.strip() for x in line.split("|")]
elif item_name == 'REQUIRES_FEATURE':
feature_requirement = {x.strip() for x in line.split(" ")}
unknown = feature_requirement - {"loop", "btf", "probe_read_kernel"}
unknown = feature_requirement - {"loop", "btf", "probe_read_kernel", "dpath"}
if len(unknown) > 0:
raise UnknownFieldError('%s is invalid for REQUIRES_FEATURE. Suite: %s' % (','.join(unknown), test_suite))
else:
Expand Down
1 change: 1 addition & 0 deletions tests/runtime/engine/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __get_bpffeature():
bpffeature["loop"] = output.find("Loop support: yes") != -1
bpffeature["probe_read_kernel"] = output.find("probe_read_kernel: yes") != -1
bpffeature["btf"] = output.find("btf (depends on Build:libbpf): yes") != -1
bpffeature["dpath"] = output.find("dpath: yes") != -1
return bpffeature

@staticmethod
Expand Down
Loading