Commit ffd94110 authored by Brendan Gregg's avatar Brendan Gregg

add async functions: print(), clear(), time(), exit()

parent cf4a4c26
......@@ -205,7 +205,11 @@ Functions:
- `count()` - Count the number of times this function is called
- `delete(@x)` - Delete the map element passed in as an argument
- `str(char *s)` - Returns the string pointed to by `s`
- `printf(char *fmt, ...)` - Write to stdout
- `printf(char *fmt, ...)` - Print formatted to stdout
- `print(@x[, int top [, int div]])` - Print a map, with optional top entry count and divisor
- `clear(@x)` - Delet all key/values from a map
- `sym(void *p)` - Resolve kernel address
- `usym(void *p)` - Resolve user space address (incomplete)
- `reg(char *name)` - Returns the value stored in the named register
- `time(char *fmt)` - Print the current time
- `exit()` - Quit bpftrace
......@@ -3,6 +3,7 @@
#include "ast.h"
#include "parser.tab.hh"
#include "arch/arch.h"
#include "types.h"
#include <llvm/Support/raw_os_ostream.h>
#include <llvm/Support/TargetRegistry.h>
......@@ -173,6 +174,12 @@ void CodegenLLVM::visit(Call &call)
}
else if (call.func == "printf")
{
/*
* perf event output has: uint64_t printf_id, vargs
* The printf_id maps to bpftrace_.printf_args_, and is a way to define the
* types and offsets of each of the arguments, and share that between BPF and
* user-space for printing.
*/
ArrayType *string_type = ArrayType::get(b_.getInt8Ty(), STRING_SIZE);
StructType *printf_struct = StructType::create(module_->getContext(), "printf_t");
std::vector<llvm::Type *> elements = { b_.getInt64Ty() }; // printf ID
......@@ -207,6 +214,105 @@ void CodegenLLVM::visit(Call &call)
b_.CreateLifetimeEnd(printf_args);
expr_ = nullptr;
}
else if (call.func == "exit")
{
/*
* perf event output has: uint64_t asyncaction_id
* The asyncaction_id informs user-space that this is not a printf(), but is a
* special asynchronous action. The ID maps to exit().
*/
ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t));
AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata");
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::exit)), perfdata);
b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t));
b_.CreateLifetimeEnd(perfdata);
expr_ = nullptr;
}
else if (call.func == "print")
{
/*
* perf event output has: uint64_t asyncaction_id, uint64_t top, uint64_t div, string map_ident
* The asyncaction_id informs user-space that this is not a printf(), but is a
* special asynchronous action. The ID maps to print(). The top argument is either
* a value for truncation, or 0 for everything. The div argument divides the output values
* by this (eg: for use in nanosecond -> millisecond conversions).
* TODO: consider stashing top & div in a printf_args_ like struct, so we don't need to pass
* them here via the perfdata output (which is a little more wasteful than need be: I'm using
* uint64_t's to avoid "misaligned stack access off" errors when juggling uint32_t's).
*/
auto &arg = *call.vargs->at(0);
auto &map = static_cast<Map&>(arg);
Constant *const_str = ConstantDataArray::getString(module_->getContext(), map.ident, true);
AllocaInst *str_buf = b_.CreateAllocaBPF(ArrayType::get(b_.getInt8Ty(), map.ident.length()), "str");
b_.CreateStore(b_.CreateGEP(const_str, b_.getInt64(0)), str_buf);
ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) + 2 * sizeof(uint64_t) + map.ident.length());
AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata");
// store asyncactionid:
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::print)), perfdata);
// store top:
if (call.vargs->size() > 1)
{
Integer &top_arg = static_cast<Integer&>(*call.vargs->at(1));
Value *top;
top_arg.accept(*this);
top = expr_;
b_.CreateStore(top, b_.CreateGEP(perfdata, {b_.getInt32(0), b_.getInt32(sizeof(uint64_t))}));
}
else
b_.CreateStore(b_.getInt64(0), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))}));
// store top:
if (call.vargs->size() > 2)
{
Integer &div_arg = static_cast<Integer&>(*call.vargs->at(2));
Value *div;
div_arg.accept(*this);
div = expr_;
b_.CreateStore(div, b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t) + sizeof(uint64_t))}));
}
else
b_.CreateStore(b_.getInt64(0), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t) + sizeof(uint64_t))}));
// store map ident:
b_.CreateMemCpy(b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t) + 2 * sizeof(uint64_t))}), str_buf, map.ident.length(), 1);
b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) + 2 * sizeof(uint64_t) + map.ident.length());
b_.CreateLifetimeEnd(perfdata);
expr_ = nullptr;
}
else if (call.func == "clear" || call.func == "zero")
{
auto &arg = *call.vargs->at(0);
auto &map = static_cast<Map&>(arg);
Constant *const_str = ConstantDataArray::getString(module_->getContext(), map.ident, true);
AllocaInst *str_buf = b_.CreateAllocaBPF(ArrayType::get(b_.getInt8Ty(), map.ident.length()), "str");
b_.CreateStore(b_.CreateGEP(const_str, b_.getInt64(0)), str_buf);
ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) + map.ident.length());
AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata");
if (call.func == "clear")
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::clear)), perfdata);
else
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::zero)), perfdata);
b_.CreateMemCpy(b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))}), str_buf, map.ident.length(), 1);
b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) + map.ident.length());
b_.CreateLifetimeEnd(perfdata);
expr_ = nullptr;
}
else if (call.func == "time")
{
ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) * 2);
AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata");
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::time)), perfdata);
static int time_id = 0;
b_.CreateStore(b_.getInt64(time_id), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))}));
time_id++;
b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) * 2);
b_.CreateLifetimeEnd(perfdata);
expr_ = nullptr;
}
else
{
abort();
......
......@@ -149,6 +149,57 @@ void SemanticAnalyser::visit(Call &call)
call.type = SizedType(Type::none, 0);
}
else if (call.func == "exit") {
check_nargs(call, 0);
}
else if (call.func == "print") {
check_assignment(call, false, false);
if (check_varargs(call, 1, 3)) {
if (is_final_pass()) {
auto &arg = *call.vargs->at(0);
if (!arg.is_map)
err_ << "print() expects a map to be provided" << std::endl;
if (call.vargs->size() > 1)
check_arg(call, Type::integer, 1, true);
if (call.vargs->size() > 2)
check_arg(call, Type::integer, 2, true);
}
}
}
else if (call.func == "clear") {
check_assignment(call, false, false);
check_nargs(call, 1);
if (check_nargs(call, 1)) {
auto &arg = *call.vargs->at(0);
if (!arg.is_map)
err_ << "clear() expects a map to be provided" << std::endl;
}
}
else if (call.func == "zero") {
check_assignment(call, false, false);
check_nargs(call, 1);
if (check_nargs(call, 1)) {
auto &arg = *call.vargs->at(0);
if (!arg.is_map)
err_ << "zero() expects a map to be provided" << std::endl;
}
}
else if (call.func == "time") {
check_assignment(call, false, false);
if (check_varargs(call, 0, 1)) {
if (is_final_pass()) {
if (call.vargs && call.vargs->size() > 0) {
check_arg(call, Type::string, 0, true);
auto &fmt_arg = *call.vargs->at(0);
String &fmt = static_cast<String&>(fmt_arg);
bpftrace_.time_args_.push_back(fmt.str);
} else {
std::string fmt_default = "%H:%M:%S\n";
bpftrace_.time_args_.push_back(fmt_default.c_str());
}
}
}
}
else {
err_ << "Unknown function: '" << call.func << "'" << std::endl;
call.type = SizedType(Type::none, 0);
......@@ -167,6 +218,14 @@ void SemanticAnalyser::visit(Map &map)
auto search = map_key_.find(map.ident);
if (search != map_key_.end()) {
/*
* TODO: this code ensures that map keys are consistent, but
* currently prevents print() and clear() being used, since
* for example "@x[pid] = count(); ... print(@x)" is detected
* as having inconsistent keys. We need a way to do this check
* differently for print() and clear() calls. I've commented it
* out for now - Brendan.
*
if (search->second != key) {
err_ << "Argument mismatch for " << map.ident << ": ";
err_ << "trying to access with arguments: ";
......@@ -175,6 +234,7 @@ void SemanticAnalyser::visit(Map &map)
err_ << search->second.argument_type_list();
err_ << "\n" << std::endl;
}
*/
}
else {
map_key_.insert({map.ident, key});
......
......@@ -5,6 +5,7 @@
#include <regex>
#include <sstream>
#include <sys/epoll.h>
#include <time.h>
#include "bcc_syms.h"
#include "perf_reader.h"
......@@ -132,7 +133,56 @@ void perf_event_printer(void *cb_cookie, void *data, int size)
auto bpftrace = static_cast<BPFtrace*>(cb_cookie);
auto printf_id = *static_cast<uint64_t*>(data);
auto arg_data = static_cast<uint8_t*>(data) + sizeof(uint64_t);
int err;
// async actions
if (printf_id == asyncactionint(AsyncAction::exit))
{
err = bpftrace->print_maps();
exit(err);
}
else if (printf_id == asyncactionint(AsyncAction::print))
{
std::string arg = (const char *)(static_cast<uint8_t*>(data) + sizeof(uint64_t) + 2 * sizeof(uint64_t));
uint64_t top = (uint64_t)*(static_cast<uint64_t*>(data) + sizeof(uint64_t) / sizeof(uint64_t));
uint64_t div = (uint64_t)*(static_cast<uint64_t*>(data) + (sizeof(uint64_t) + sizeof(uint64_t)) / sizeof(uint64_t));
bpftrace->print_map_ident(arg, top, div);
return;
}
else if (printf_id == asyncactionint(AsyncAction::clear))
{
std::string arg = (const char *)arg_data;
bpftrace->clear_map_ident(arg);
return;
}
else if (printf_id == asyncactionint(AsyncAction::zero))
{
std::string arg = (const char *)arg_data;
bpftrace->zero_map_ident(arg);
return;
}
else if (printf_id == asyncactionint(AsyncAction::time))
{
char timestr[STRING_SIZE];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
if (tmp == NULL) {
perror("localtime");
return;
}
uint64_t time_id = (uint64_t)*(static_cast<uint64_t*>(data) + sizeof(uint64_t) / sizeof(uint64_t));
auto fmt = bpftrace->time_args_[time_id].c_str();
if (strftime(timestr, sizeof(timestr), fmt, tmp) == 0) {
fprintf(stderr, "strftime returned 0");
return;
}
printf("%s", timestr);
return;
}
// printf
auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]).c_str();
auto args = std::get<1>(bpftrace->printf_args_[printf_id]);
std::vector<uint64_t> arg_values;
......@@ -314,9 +364,9 @@ int BPFtrace::print_maps()
IMap &map = *mapmap.second.get();
int err;
if (map.type_.type == Type::quantize)
err = print_map_quantize(map);
err = print_map_quantize(map, 0, 0);
else
err = print_map(map);
err = print_map(map, 0, 0);
if (err)
return err;
......@@ -325,7 +375,142 @@ int BPFtrace::print_maps()
return 0;
}
int BPFtrace::print_map(IMap &map)
// print a map given an ident string
int BPFtrace::print_map_ident(const std::string &ident, uint32_t top, uint32_t div)
{
int err = 0;
for(auto &mapmap : maps_)
{
IMap &map = *mapmap.second.get();
if (map.name_ == ident) {
if (map.type_.type == Type::quantize)
err = print_map_quantize(map, top, div);
else
err = print_map(map, top, div);
return err;
}
}
return -2;
}
// clear a map (delete all keys) given an ident string
int BPFtrace::clear_map_ident(const std::string &ident)
{
int err = 0;
for(auto &mapmap : maps_)
{
IMap &map = *mapmap.second.get();
if (map.name_ == ident) {
err = clear_map(map);
return err;
}
}
return -2;
}
// zero a map (set all keys to zero) given an ident string
int BPFtrace::zero_map_ident(const std::string &ident)
{
int err = 0;
for(auto &mapmap : maps_)
{
IMap &map = *mapmap.second.get();
if (map.name_ == ident) {
err = zero_map(map);
return err;
}
}
return -2;
}
// clear a map
int BPFtrace::clear_map(IMap &map)
{
std::vector<uint8_t> old_key;
try
{
if (map.type_.type == Type::quantize)
// quantize maps have 8 extra bytes for the bucket number
old_key = find_empty_key(map, map.key_.size() + 8);
else
old_key = find_empty_key(map, map.key_.size());
}
catch (std::runtime_error &e)
{
std::cerr << "Error getting key for map '" << map.name_ << "': "
<< e.what() << std::endl;
return -2;
}
auto key(old_key);
// snapshot keys, then operate on them
std::vector<std::vector<uint8_t>> keys;
while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0)
{
keys.push_back(key);
old_key = key;
}
for (auto &key : keys)
{
int err = bpf_delete_elem(map.mapfd_, key.data());
if (err)
{
std::cerr << "Error looking up elem: " << err << std::endl;
return -1;
}
}
return 0;
}
// zero a map
int BPFtrace::zero_map(IMap &map)
{
std::vector<uint8_t> old_key;
try
{
if (map.type_.type == Type::quantize)
// quantize maps have 8 extra bytes for the bucket number
old_key = find_empty_key(map, map.key_.size() + 8);
else
old_key = find_empty_key(map, map.key_.size());
}
catch (std::runtime_error &e)
{
std::cerr << "Error getting key for map '" << map.name_ << "': "
<< e.what() << std::endl;
return -2;
}
auto key(old_key);
// snapshot keys, then operate on them
std::vector<std::vector<uint8_t>> keys;
while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0)
{
keys.push_back(key);
old_key = key;
}
uint64_t zero = 0;
for (auto &key : keys)
{
int err = bpf_update_elem(map.mapfd_, key.data(), &zero, BPF_EXIST);
if (err)
{
std::cerr << "Error looking up elem: " << err << std::endl;
return -1;
}
}
return 0;
}
int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div)
{
std::vector<uint8_t> old_key;
try
......@@ -372,11 +557,20 @@ int BPFtrace::print_map(IMap &map)
sort_by_key(map.key_.args_, values_by_key);
};
if (div == 0)
div = 1;
uint32_t i = 0;
for (auto &pair : values_by_key)
{
auto key = pair.first;
auto value = pair.second;
if (top)
{
if (i++ < (values_by_key.size() - top))
continue;
}
std::cout << map.name_ << map.key_.argument_value_list(*this, key) << ": ";
if (map.type_.type == Type::stack)
......@@ -390,9 +584,9 @@ int BPFtrace::print_map(IMap &map)
else if (map.type_.type == Type::string)
std::cout << value.data() << std::endl;
else if (map.type_.type == Type::count)
std::cout << reduce_value(value, ncpus_) << std::endl;
std::cout << reduce_value(value, ncpus_) / div << std::endl;
else
std::cout << *(int64_t*)value.data() << std::endl;
std::cout << *(int64_t*)value.data() / div << std::endl;
}
std::cout << std::endl;
......@@ -400,7 +594,7 @@ int BPFtrace::print_map(IMap &map)
return 0;
}
int BPFtrace::print_map_quantize(IMap &map)
int BPFtrace::print_map_quantize(IMap &map, uint32_t top, uint32_t div)
{
// A quantize-map adds an extra 8 bytes onto the end of its key for storing
// the bucket number.
......@@ -465,13 +659,23 @@ int BPFtrace::print_map_quantize(IMap &map)
return a.second < b.second;
});
if (div == 0)
div = 1;
uint32_t i = 0;
for (auto &key_count : total_counts_by_key)
{
auto &key = key_count.first;
auto &value = values_by_key[key];
if (top)
{
if (i++ < (values_by_key.size() - top))
continue;
}
std::cout << map.name_ << map.key_.argument_value_list(*this, key) << ": " << std::endl;
print_quantize(value);
print_quantize(value, div);
std::cout << std::endl;
}
......@@ -479,14 +683,14 @@ int BPFtrace::print_map_quantize(IMap &map)
return 0;
}
int BPFtrace::print_quantize(const std::vector<uint64_t> &values) const
int BPFtrace::print_quantize(const std::vector<uint64_t> &values, uint32_t div) const
{
int max_index = -1;
int max_value = 0;
for (size_t i = 0; i < values.size(); i++)
{
int v = values.at(i);
int v = values.at(i) / div;
if (v != 0)
max_index = i;
if (v > max_value)
......@@ -510,11 +714,11 @@ int BPFtrace::print_quantize(const std::vector<uint64_t> &values) const
}
int max_width = 52;
int bar_width = values.at(i)/(float)max_value*max_width;
int bar_width = values.at(i)/((float)max_value*max_width * div);
std::string bar(bar_width, '@');
std::cout << std::setw(16) << std::left << header.str()
<< std::setw(8) << std::right << values.at(i)
<< std::setw(8) << std::right << (values.at(i) / div)
<< " |" << std::setw(max_width) << std::left << bar << "|"
<< std::endl;
}
......
......@@ -31,6 +31,9 @@ public:
int num_probes() const;
int run(std::unique_ptr<BpfOrc> bpforc);
int print_maps();
int print_map_ident(const std::string &ident, uint32_t top, uint32_t div);
int clear_map_ident(const std::string &ident);
int zero_map_ident(const std::string &ident);
std::string get_stack(uint32_t stackid, bool ustack, int indent=0);
std::string resolve_sym(uintptr_t addr, bool show_offset=false);
std::string resolve_usym(uintptr_t addr) const;
......@@ -38,6 +41,7 @@ public:
std::map<std::string, std::unique_ptr<IMap>> maps_;
std::map<std::string, Struct> structs_;
std::vector<std::tuple<std::string, std::vector<SizedType>>> printf_args_;
std::vector<std::string> time_args_;
std::unique_ptr<IMap> stackid_map_;
std::unique_ptr<IMap> perf_event_map_;
......@@ -59,9 +63,11 @@ private:
std::unique_ptr<AttachedProbe> attach_probe(Probe &probe, const BpfOrc &bpforc);
int setup_perf_events();
void poll_perf_events(int epollfd, int timeout=-1);
int print_map(IMap &map);
int print_map_quantize(IMap &map);
int print_quantize(const std::vector<uint64_t> &values) const;
int clear_map(IMap &map);
int zero_map(IMap &map);
int print_map(IMap &map, uint32_t top, uint32_t div);
int print_map_quantize(IMap &map, uint32_t top, uint32_t div);
int print_quantize(const std::vector<uint64_t> &values, uint32_t div) const;
static uint64_t reduce_value(const std::vector<uint8_t> &value, int ncpus);
static std::string quantize_index_label(int power);
std::vector<uint8_t> find_empty_key(IMap &map, size_t size) const;
......
......@@ -60,4 +60,9 @@ ProbeType probetype(const std::string &type)
abort();
}
uint64_t asyncactionint(AsyncAction a)
{
return (uint64_t)a;
}
} // namespace bpftrace
......@@ -67,4 +67,16 @@ public:
int freq;
};
enum class AsyncAction
{
// printf reserves 0-9999 for printf_ids
exit = 10000,
print,
clear,
zero,
time,
};
uint64_t asyncactionint(AsyncAction a);
} // namespace bpftrace
......@@ -948,6 +948,228 @@ attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, call_exit)
{
test("kprobe:f { exit() }",
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" {
entry:
%perfdata = alloca [8 x i8], align 8
%1 = getelementptr inbounds [8 x i8], [8 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10000, [8 x i8]* %perfdata, 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, [8 x i8]* nonnull %perfdata, i64 8)
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");
}
TEST(codegen, call_print)
{
test("BEGIN { @x = 1; } kprobe:f { print(@x); }",
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 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [26 x i8], align 8
%1 = getelementptr inbounds [26 x i8], [26 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10001, [26 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [26 x i8], [26 x i8]* %perfdata, i64 0, i64 8
%3 = getelementptr inbounds [26 x i8], [26 x i8]* %perfdata, i64 0, i64 24
%4 = bitcast i8* %3 to i16*
call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 8, i1 false)
store i16 30784, i16* %4, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%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, [26 x i8]* nonnull %perfdata, i64 26)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, call_clear)
{
test("BEGIN { @x = 1; } kprobe:f { clear(@x); }",
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 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [10 x i8], align 8
%1 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10002, [10 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 8
%3 = bitcast i8* %2 to i16*
store i16 30784, i16* %3, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%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, [10 x i8]* nonnull %perfdata, i64 10)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, call_zero)
{
test("BEGIN { @x = 1; } kprobe:f { zero(@x); }",
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 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [10 x i8], align 8
%1 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10003, [10 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 8
%3 = bitcast i8* %2 to i16*
store i16 30784, i16* %3, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%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, [10 x i8]* nonnull %perfdata, i64 10)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, call_time)
{
test("kprobe:f { time(); }",
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" {
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 10004, [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");
}
TEST(codegen, int_propagation)
{
test("kprobe:f { @x = 1234; @y = @x }",
......
......@@ -81,6 +81,11 @@ TEST(semantic_analyser, builtin_functions)
test("kprobe:f { @x = quantize(123) }", 0);
test("kprobe:f { @x = count() }", 0);
test("kprobe:f { @x = 1; delete(@x) }", 0);
test("kprobe:f { @x = 1; print(@x) }", 0);
test("kprobe:f { @x = 1; clear(@x) }", 0);
test("kprobe:f { @x = 1; zero(@x) }", 0);
test("kprobe:f { time() }", 0);
test("kprobe:f { exit() }", 0);
test("kprobe:f { str(0xffff) }", 0);
test("kprobe:f { printf(\"hello\\n\") }", 0);
test("kprobe:f { sym(0xffff) }", 0);
......@@ -141,6 +146,43 @@ TEST(semantic_analyser, call_delete)
test("kprobe:f { $y = delete(@x); }", 1);
}
TEST(semantic_analyser, call_exit)
{
test("kprobe:f { exit(); }", 0);
test("kprobe:f { exit(1); }", 1);
}
TEST(semantic_analyser, call_print)
{
test("kprobe:f { @x = count(); print(@x); }", 0);
test("kprobe:f { @x = count(); print(@x, 5); }", 0);
test("kprobe:f { @x = count(); print(@x, 5, 10); }", 0);
test("kprobe:f { @x = count(); print(@x, 5, 10, 1); }", 1);
test("kprobe:f { @x = count(); @x = print(); }", 1);
}
TEST(semantic_analyser, call_clear)
{
test("kprobe:f { @x = count(); clear(@x); }", 0);
test("kprobe:f { @x = count(); clear(@x, 1); }", 1);
test("kprobe:f { @x = count(); @x = clear(); }", 1);
}
TEST(semantic_analyser, call_zero)
{
test("kprobe:f { @x = count(); zero(@x); }", 0);
test("kprobe:f { @x = count(); zero(@x, 1); }", 1);
test("kprobe:f { @x = count(); @x = zero(); }", 1);
}
TEST(semantic_analyser, call_time)
{
test("kprobe:f { time(); }", 0);
test("kprobe:f { time(\"%M:%S\"); }", 0);
test("kprobe:f { time(\"%M:%S\", 1); }", 1);
test("kprobe:f { @x = time(); }", 1);
}
TEST(semantic_analyser, call_str)
{
test("kprobe:f { str(arg0); }", 0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment