Skip to content

Commit

Permalink
Add 'cat' builtin (#543)
Browse files Browse the repository at this point in the history
  • Loading branch information
caringi committed Apr 25, 2019
1 parent 480bb22 commit ae1cfc9
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -282,6 +282,7 @@ Functions:
- `reg(char *name)` - Returns the value stored in the named register
- `join(char *arr[] [, char *delim])` - Prints the string array
- `time(char *fmt)` - Print the current time
- `cat(char *filename)` - Print file content
- `system(char *fmt)` - Execute shell command
- `exit()` - Quit bpftrace

Expand Down
22 changes: 22 additions & 0 deletions docs/reference_guide.md
Expand Up @@ -68,6 +68,7 @@ This is a work in progress. If something is missing, check the bpftrace source t
- [14. `ntop()`: Convert IP address data to text](#14-ntop-convert-ip-address-data-to-text)
- [15. `kstack()`: Stack Traces, Kernel](#15-kstack-stack-traces-kernel)
- [16. `ustack()`: Stack Traces, User](#16-ustack-stack-traces-user)
- [17. `cat()`: Print file content](#17-cat-print-file-content)
- [Map Functions](#map-functions)
- [1. Builtins](#1-builtins-2)
- [2. `count()`: Count](#2-count-count)
Expand Down Expand Up @@ -1391,6 +1392,7 @@ Tracing block I/O sizes > 0 bytes
- `kstack([StackMode mode, ][int level])` - Kernel stack trace
- `ustack([StackMode mode, ][int level])` - User stack trace
- `ntop(int af, int addr)` - Convert IP address data to text
- `cat(char *filename)` - Print file content

Some of these are asynchronous: the kernel queues the event, but some time
later (milliseconds) it is processed in user-space. The asynchronous actions
Expand Down Expand Up @@ -1913,6 +1915,26 @@ Attaching 1 probe...
Note that for these examples to work, bash had to be recompiled with frame
pointers.

## 17. `cat()`: Print file content

Syntax: `cat(filename)`

This prints the file content. For example:

```
# bpftrace -e 't:syscalls:sys_enter_execve { printf("%s ", str(args->filename)); cat("/proc/loadavg"); }'
Attaching 1 probe...
/usr/libexec/grepconf.sh 3.18 2.90 2.94 2/977 30138
/usr/bin/grep 3.18 2.90 2.94 4/978 30139
/usr/bin/flatpak 3.18 2.90 2.94 2/980 30143
/usr/bin/grep 3.18 2.90 2.94 3/977 30144
/usr/bin/sed 3.18 2.90 2.94 7/978 30146
/usr/bin/tclsh 3.18 2.90 2.94 5/978 30150
/usr/bin/manpath 3.18 2.90 2.94 2/978 30152
/bin/ps 3.18 2.90 2.94 2/979 30155
^C
```

# Map Functions

Maps are special BPF data types that can be used to store counts, statistics, and histograms. They are also used for some variable types as discussed in the previous section, whenever `@` is used: [globals](#21-global), [per thread variables](#22-per-thread), and [associative arrays](#3--associative-arrays).
Expand Down
4 changes: 4 additions & 0 deletions man/man8/bpftrace.8
Expand Up @@ -376,6 +376,10 @@ Prints the string array
Print the current time
.
.TP
\fBcat(char *filename)\fR
Print file content
.
.TP
\fBsystem(char *fmt)\fR
Execute shell command
.
Expand Down
12 changes: 12 additions & 0 deletions src/ast/codegen_llvm.cpp
Expand Up @@ -703,6 +703,18 @@ void CodegenLLVM::visit(Call &call)
b_.CreateLifetimeEnd(perfdata);
expr_ = nullptr;
}
else if (call.func == "cat")
{
ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) * 2);
AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata");
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::cat)), perfdata);
b_.CreateStore(b_.getInt64(cat_id_), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))}));

cat_id_++;
b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) * 2);
b_.CreateLifetimeEnd(perfdata);
expr_ = nullptr;
}
else if (call.func == "kstack" || call.func == "ustack")
{
Value *stackid = b_.CreateGetStackId(ctx_, call.func == "ustack", call.type.stack_type);
Expand Down
1 change: 1 addition & 0 deletions src/ast/codegen_llvm.h
Expand Up @@ -81,6 +81,7 @@ class CodegenLLVM : public Visitor {
std::map<std::string, AllocaInst *> variables_;
int printf_id_ = 0;
int time_id_ = 0;
int cat_id_ = 0;
uint64_t join_id_ = 0;
int system_id_ = 0;
};
Expand Down
11 changes: 11 additions & 0 deletions src/ast/semantic_analyser.cpp
Expand Up @@ -456,6 +456,17 @@ void SemanticAnalyser::visit(Call &call)
}
}
}
else if (call.func == "cat") {
check_assignment(call, false, false);
if (check_nargs(call, 1)) {
if (check_arg(call, Type::string, 0, true)) {
if (is_final_pass()) {
auto &arg = *call.vargs->at(0);
bpftrace_.cat_args_.push_back(static_cast<String&>(arg).str);
}
}
}
}
else if (call.func == "kstack") {
check_stack_call(call, Type::kstack);
}
Expand Down
7 changes: 7 additions & 0 deletions src/bpftrace.cpp
Expand Up @@ -262,6 +262,13 @@ void perf_event_printer(void *cb_cookie, void *data, int size __attribute__((unu
printf("%s", timestr);
return;
}
else if (printf_id == asyncactionint(AsyncAction::cat))
{
uint64_t cat_id = (uint64_t)*(static_cast<uint64_t*>(data) + sizeof(uint64_t) / sizeof(uint64_t));
auto filename = bpftrace->cat_args_[cat_id].c_str();
cat_file(filename);
return;
}
else if (printf_id == asyncactionint(AsyncAction::join))
{
uint64_t join_id = (uint64_t)*(static_cast<uint64_t*>(data) + sizeof(uint64_t) / sizeof(uint64_t));
Expand Down
1 change: 1 addition & 0 deletions src/bpftrace.h
Expand Up @@ -88,6 +88,7 @@ class BPFtrace
std::vector<std::tuple<std::string, std::vector<Field>>> system_args_;
std::vector<std::string> join_args_;
std::vector<std::string> time_args_;
std::vector<std::string> cat_args_;
std::unordered_map<StackType, std::unique_ptr<IMap>> stackid_maps_;
std::unique_ptr<IMap> join_map_;
std::unique_ptr<IMap> perf_event_map_;
Expand Down
1 change: 1 addition & 0 deletions src/types.h
Expand Up @@ -153,6 +153,7 @@ enum class AsyncAction
zero,
time,
join,
cat
};

uint64_t asyncactionint(AsyncAction a);
Expand Down
16 changes: 16 additions & 0 deletions src/utils.cpp
Expand Up @@ -209,4 +209,20 @@ std::string resolve_binary_path(const std::string& cmd)
}
}

void cat_file(const char *filename)
{
std::ifstream file(filename);
std::string line;

if (file.fail())
{
std::cerr << "ERROR: file not found: " << filename << std::endl;
return;
}
while (getline(file, line))
{
std::cout << line << std::endl;
}
}

} // namespace bpftrace
1 change: 1 addition & 0 deletions src/utils.h
Expand Up @@ -39,6 +39,7 @@ std::vector<std::string> get_kernel_cflags(
std::string is_deprecated(std::string &str);
std::string exec_system(const char* cmd);
std::string resolve_binary_path(const std::string& cmd);
void cat_file(const char *filename);

// trim from end of string (right)
inline std::string& rtrim(std::string& s)
Expand Down
1 change: 1 addition & 0 deletions tests/codegen.cpp
Expand Up @@ -43,6 +43,7 @@
#include "codegen/call_sum.cpp"
#include "codegen/call_system.cpp"
#include "codegen/call_time.cpp"
#include "codegen/call_cat.cpp"
#include "codegen/call_usym_key.cpp"
#include "codegen/call_ustack.cpp"
#include "codegen/call_zero.cpp"
Expand Down
42 changes: 42 additions & 0 deletions tests/codegen/call_cat.cpp
@@ -0,0 +1,42 @@
#include "common.h"

namespace bpftrace {
namespace test {
namespace codegen {

TEST(codegen, call_cat)
{
test("kprobe:f { cat(\"/proc/loadavg\"); }",

R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" {
entry:
%perfdata = alloca [16 x i8], align 8
%1 = getelementptr inbounds [16 x i8], [16 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 20006, [16 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [16 x i8], [16 x i8]* %perfdata, i64 0, i64 8
store i64 0, i8* %2, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [16 x i8]* nonnull %perfdata, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
}

} // namespace codegen
} // namespace test
} // namespace bpftrace
5 changes: 5 additions & 0 deletions tests/runtime/call
Expand Up @@ -103,3 +103,8 @@ RUN bpftrace -e 'k:do_nanosleep { printf("SUCCESS '$test' %s\n%s\n", ustack(), u
EXPECT SUCCESS ustack
TIMEOUT 5
BEFORE sleep 0.1 && sleep 0.1 &

NAME cat
RUN bpftrace -e 'i:ms:1 { cat("/proc/uptime"); exit();}'
EXPECT [0-9]*.[0-9]* [0-9]*.[0-9]*
TIMEOUT 5
9 changes: 9 additions & 0 deletions tests/semantic_analyser.cpp
Expand Up @@ -100,6 +100,7 @@ TEST(semantic_analyser, builtin_functions)
test("kprobe:f { @x = count(pid) }", 1);
test("kprobe:f { @x = sum(pid, 123) }", 1);
test("kprobe:f { fake() }", 1);
test("kprobe:f { cat(\"/proc/uptime\") }", 0);
}

TEST(semantic_analyser, undefined_map)
Expand Down Expand Up @@ -313,6 +314,14 @@ TEST(semantic_analyser, call_probe)
test("kprobe:f { probe(123); }", 1);
}

TEST(semantic_analyser, call_cat)
{
test("kprobe:f { cat(\"/proc/loadavg\"); }", 0);
test("kprobe:f { cat(); }", 1);
test("kprobe:f { cat(123); }", 1);
test("kprobe:f { @x = cat(\"/proc/loadavg\"); }", 1);
}

TEST(semantic_analyser, map_reassignment)
{
test("kprobe:f { @x = 1; @x = 2; }", 0);
Expand Down

0 comments on commit ae1cfc9

Please sign in to comment.