Commit 5c31a0a6 authored by Alastair Robertson's avatar Alastair Robertson

Codegen for C structs

parent 2f0c01df
......@@ -110,8 +110,10 @@ public:
class Cast : public Expression {
public:
Cast(const std::string &type, Expression *expr) : cast_type(type), expr(expr) { }
Cast(const std::string &type, bool is_pointer, Expression *expr)
: cast_type(type), is_pointer(is_pointer), expr(expr) { }
std::string cast_type;
bool is_pointer;
Expression *expr;
void accept(Visitor &v) override;
......
......@@ -180,7 +180,7 @@ void CodegenLLVM::visit(Call &call)
static int printf_id = 0;
auto args = std::get<1>(bpftrace_.printf_args_.at(printf_id));
for (SizedType t : args)
for (SizedType &t : args)
{
llvm::Type *ty = b_.GetType(t);
elements.push_back(ty);
......@@ -291,32 +291,112 @@ void CodegenLLVM::visit(Binop &binop)
void CodegenLLVM::visit(Unop &unop)
{
assert(unop.expr->type.type == Type::integer);
unop.expr->accept(*this);
switch (unop.op) {
case bpftrace::Parser::token::LNOT: expr_ = b_.CreateNot(expr_); break;
case bpftrace::Parser::token::BNOT: expr_ = b_.CreateNeg(expr_); break;
case bpftrace::Parser::token::MUL:
{
AllocaInst *dst = b_.CreateAllocaBPF(unop.expr->type, "deref");
b_.CreateProbeRead(dst, unop.expr->type.size, expr_);
expr_ = b_.CreateLoad(dst);
b_.CreateLifetimeEnd(dst);
break;
SizedType &type = unop.expr->type;
if (type.type == Type::integer)
{
switch (unop.op) {
case bpftrace::Parser::token::LNOT: expr_ = b_.CreateNot(expr_); break;
case bpftrace::Parser::token::BNOT: expr_ = b_.CreateNeg(expr_); break;
case bpftrace::Parser::token::MUL:
{
int size = type.size;
if (type.is_pointer)
{
// When dereferencing a 32-bit integer, only read in 32-bits, etc.
size = type.pointee_size;
}
AllocaInst *dst = b_.CreateAllocaBPF(SizedType(type.type, size), "deref");
b_.CreateProbeRead(dst, size, expr_);
expr_ = b_.CreateLoad(dst);
b_.CreateLifetimeEnd(dst);
break;
}
default: abort();
}
default: abort();
}
else if (type.type == Type::cast)
{
// Do nothing
}
else
{
abort();
}
}
void CodegenLLVM::visit(FieldAccess &acc)
{
// TODO
SizedType &type = acc.expr->type;
assert(type.type == Type::cast);
acc.expr->accept(*this);
auto &field = bpftrace_.structs_[type.cast_type].fields[acc.field];
if (type.is_internal)
{
// The struct we are reading from has already been pulled into
// BPF-memory, e.g. by being stored in a map.
// Just read from the correct offset of expr_
Value *src = b_.CreateGEP(expr_, {b_.getInt64(0), b_.getInt64(field.offset)});
if (field.type.type == Type::cast)
{
// TODO This should be do-able without allocating more memory here
AllocaInst *dst = b_.CreateAllocaBPF(field.type, "internal_" + type.cast_type + "." + acc.field);
b_.CreateMemCpy(dst, src, field.type.size, 1);
expr_ = dst;
// TODO clean up dst memory?
}
else if (field.type.type == Type::string)
{
expr_ = src;
}
else
{
expr_ = b_.CreateLoad(b_.GetType(field.type), src);
}
}
else
{
// The struct we are reading from has not been pulled into BPF-memory,
// so expr_ will contain an external pointer to the start of the struct
Value *src = b_.CreateAdd(expr_, b_.getInt64(field.offset));
if (field.type.type == Type::cast && !field.type.is_pointer)
{
// struct X
// {
// struct Y y;
// };
//
// We are trying to access an embedded struct, e.g. "x.y"
//
// Instead of copying the entire struct Y in, we'll just store it as a
// pointer internally and dereference later when necessary.
expr_ = src;
}
else if (field.type.type == Type::string)
{
AllocaInst *dst = b_.CreateAllocaBPF(field.type, type.cast_type + "." + acc.field);
b_.CreateProbeRead(dst, field.type.size, src);
expr_ = dst;
}
else
{
AllocaInst *dst = b_.CreateAllocaBPF(field.type, type.cast_type + "." + acc.field);
b_.CreateProbeRead(dst, field.type.size, src);
expr_ = b_.CreateLoad(dst);
b_.CreateLifetimeEnd(dst);
}
}
}
void CodegenLLVM::visit(Cast &cast)
{
// TODO
cast.expr->accept(*this);
}
void CodegenLLVM::visit(ExprStatement &expr)
......@@ -336,12 +416,32 @@ void CodegenLLVM::visit(AssignMapStatement &assignment)
Value *val, *expr;
expr = expr_;
AllocaInst *key = getMapKey(map);
if (assignment.expr->type.type == Type::string)
if (map.type.type == Type::string)
{
val = expr;
}
else if (map.type.type == Type::cast)
{
if (assignment.expr->type.is_internal)
{
val = expr;
}
else
{
// expr currently contains a pointer to the struct
// We now want to read the entire struct in so we can save it
AllocaInst *dst = b_.CreateAllocaBPF(map.type, map.ident + "_val");
b_.CreateProbeRead(dst, map.type.size, expr);
val = dst;
}
}
else
{
if (map.type.type == Type::integer)
{
// Integers are always stored as 64-bit in map values
expr = b_.CreateIntCast(expr, b_.getInt64Ty(), false);
}
val = b_.CreateAllocaBPF(map.type, map.ident + "_val");
b_.CreateStore(expr, val);
}
......@@ -425,7 +525,7 @@ AllocaInst *CodegenLLVM::getMapKey(Map &map)
{
size += expr->type.size;
}
key = b_.CreateAllocaMapKey(size, map.ident + "_key");
key = b_.CreateAllocaBPF(size, map.ident + "_key");
int offset = 0;
for (Expression *expr : *map.vargs) {
......@@ -455,7 +555,7 @@ AllocaInst *CodegenLLVM::getQuantizeMapKey(Map &map, Value *log2)
{
size += expr->type.size;
}
key = b_.CreateAllocaMapKey(size, map.ident + "_key");
key = b_.CreateAllocaBPF(size, map.ident + "_key");
int offset = 0;
for (Expression *expr : *map.vargs) {
......
......@@ -48,7 +48,7 @@ AllocaInst *IRBuilderBPF::CreateAllocaBPF(const SizedType &stype, const std::str
return CreateAllocaBPF(ty, name);
}
AllocaInst *IRBuilderBPF::CreateAllocaMapKey(int bytes, const std::string &name)
AllocaInst *IRBuilderBPF::CreateAllocaBPF(int bytes, const std::string &name)
{
llvm::Type *ty = ArrayType::get(getInt8Ty(), bytes);
return CreateAllocaBPF(ty, name);
......@@ -57,7 +57,7 @@ AllocaInst *IRBuilderBPF::CreateAllocaMapKey(int bytes, const std::string &name)
llvm::Type *IRBuilderBPF::GetType(const SizedType &stype)
{
llvm::Type *ty;
if (stype.type == Type::string || stype.type == Type::cast)
if (stype.type == Type::string || (stype.type == Type::cast && !stype.is_pointer))
{
ty = ArrayType::get(getInt8Ty(), stype.size);
}
......@@ -121,22 +121,23 @@ Value *IRBuilderBPF::CreateMapLookupElem(Map &map, AllocaInst *key)
CreateCondBr(condition, lookup_success_block, lookup_failure_block);
SetInsertPoint(lookup_success_block);
if (map.type.type == Type::string)
if (map.type.type == Type::string || map.type.type == Type::cast)
CreateMemCpy(value, call, map.type.size, 1);
else
CreateStore(CreateLoad(getInt64Ty(), call), value);
CreateBr(lookup_merge_block);
SetInsertPoint(lookup_failure_block);
if (map.type.type == Type::string)
if (map.type.type == Type::string || map.type.type == Type::cast)
CreateMemSet(value, getInt8(0), map.type.size, 1);
else
CreateStore(getInt64(0), value);
CreateBr(lookup_merge_block);
SetInsertPoint(lookup_merge_block);
if (map.type.type == Type::string)
if (map.type.type == Type::string || map.type.type == Type::cast)
return value;
return CreateLoad(value);
}
......
......@@ -20,7 +20,7 @@ public:
AllocaInst *CreateAllocaBPF(llvm::Type *ty, const std::string &name="");
AllocaInst *CreateAllocaBPF(const SizedType &stype, const std::string &name="");
AllocaInst *CreateAllocaMapKey(int bytes, const std::string &name="");
AllocaInst *CreateAllocaBPF(int bytes, const std::string &name="");
llvm::Type *GetType(const SizedType &stype);
CallInst *CreateBpfPseudoCall(int mapfd);
CallInst *CreateBpfPseudoCall(Map &map);
......
......@@ -101,7 +101,10 @@ void Printer::visit(FieldAccess &acc)
void Printer::visit(Cast &cast)
{
std::string indent(depth_, ' ');
out_ << indent << "(" << cast.cast_type << ")" << std::endl;
if (cast.is_pointer)
out_ << indent << "(" << cast.cast_type << "*)" << std::endl;
else
out_ << indent << "(" << cast.cast_type << ")" << std::endl;
++depth_;
cast.expr->accept(*this);
......
......@@ -231,22 +231,31 @@ void SemanticAnalyser::visit(Unop &unop)
{
unop.expr->accept(*this);
SizedType &type = unop.expr->type;
if (is_final_pass() &&
unop.expr->type.type != Type::integer &&
unop.expr->type.type != Type::cast) {
!(type.type == Type::integer) &&
!(type.type == Type::cast && unop.op == Parser::token::MUL)) {
err_ << "The " << opstr(unop) << " operator can not be used on expressions of type '"
<< unop.expr->type << "'" << std::endl;
<< type << "'" << std::endl;
}
if (unop.op == Parser::token::MUL && unop.expr->type.type == Type::cast) {
std::string cast_type = unop.expr->type.cast_type;
if (cast_type.back() == '*') {
cast_type.pop_back();
unop.type = SizedType(Type::cast, 8, cast_type);
if (unop.op == Parser::token::MUL) {
if (type.type == Type::cast) {
if (type.is_pointer) {
if (bpftrace_.structs_.count(type.cast_type) == 0) {
err_ << "Unknown struct/union: '" << type.cast_type << "'" << std::endl;
return;
}
int cast_size = bpftrace_.structs_[type.cast_type].size;
unop.type = SizedType(Type::cast, cast_size, type.cast_type);
}
else {
err_ << "Can not dereference struct/union of type '" << type.cast_type << "'. "
<< "It is not a pointer." << std::endl;
}
}
else {
err_ << "Can not dereference struct/union of type '" << cast_type << "'. "
<< "It is not a pointer." << std::endl;
else if (type.type == Type::integer) {
unop.type = SizedType(Type::integer, type.size);
}
}
else {
......@@ -258,30 +267,35 @@ void SemanticAnalyser::visit(FieldAccess &acc)
{
acc.expr->accept(*this);
if (acc.expr->type.type != Type::cast) {
SizedType &type = acc.expr->type;
if (type.type != Type::cast) {
if (is_final_pass()) {
err_ << "Can not access field '" << acc.field
<< "' on expression of type '" << acc.expr->type
<< "' on expression of type '" << type
<< "'" << std::endl;
}
return;
}
std::string cast_type = acc.expr->type.cast_type;
if (cast_type.back() == '*') {
if (type.is_pointer) {
err_ << "Can not access field '" << acc.field << "' on type '"
<< cast_type << "'. Try dereferencing it first, or using '->'"
<< type.cast_type << "'. Try dereferencing it first, or using '->'"
<< std::endl;
return;
}
if (bpftrace_.structs_.count(type.cast_type) == 0) {
err_ << "Unknown struct/union: '" << type.cast_type << "'" << std::endl;
return;
}
auto fields = bpftrace_.structs_[cast_type].fields;
auto fields = bpftrace_.structs_[type.cast_type].fields;
if (fields.count(acc.field) == 0) {
err_ << "Struct/union of type '" << cast_type << "' does not contain "
err_ << "Struct/union of type '" << type.cast_type << "' does not contain "
<< "a field named '" << acc.field << "'" << std::endl;
}
else {
acc.type = fields[acc.field].type;
acc.type.is_internal = type.is_internal;
}
}
......@@ -289,22 +303,20 @@ void SemanticAnalyser::visit(Cast &cast)
{
cast.expr->accept(*this);
std::string cast_type = cast.cast_type;
if (cast_type.back() == '*')
cast_type.pop_back();
if (bpftrace_.structs_.count(cast_type) == 0) {
err_ << "Unknown struct/union: '" << cast_type << "'" << std::endl;
if (bpftrace_.structs_.count(cast.cast_type) == 0) {
err_ << "Unknown struct/union: '" << cast.cast_type << "'" << std::endl;
return;
}
int cast_size;
if (cast.cast_type.back() == '*') {
if (cast.is_pointer) {
cast_size = sizeof(uintptr_t);
}
else {
cast_size = bpftrace_.structs_[cast.cast_type].size;
}
cast.type = SizedType(Type::cast, cast_size, cast.cast_type);
cast.type.is_pointer = cast.is_pointer;
}
void SemanticAnalyser::visit(ExprStatement &expr)
......@@ -338,6 +350,11 @@ void SemanticAnalyser::visit(AssignMapStatement &assignment)
else {
// This map hasn't been seen before
map_val_.insert({map_ident, assignment.expr->type});
if (map_val_[map_ident].type == Type::integer) {
// Store all integer values as 64-bit in maps, so that there will
// be space for any integer to be assigned to the map later
map_val_[map_ident].size = 8;
}
}
if (assignment.expr->type.type == Type::cast) {
......@@ -351,6 +368,7 @@ void SemanticAnalyser::visit(AssignMapStatement &assignment)
}
else {
map_val_[map_ident].cast_type = cast_type;
map_val_[map_ident].is_internal = true;
}
}
}
......
......@@ -93,7 +93,6 @@ void yyerror(bpftrace::Driver &driver, const char *s);
%type <ast::AttachPointList *> attach_points
%type <ast::AttachPoint *> attach_point
%type <std::string> wildcard
%type <std::string> type
%type <std::string> ident
%right ASSIGN
......@@ -190,11 +189,8 @@ expr : INT { $$ = new ast::Integer($1); }
| MUL expr %prec DEREF { $$ = new ast::Unop(token::MUL, $2); }
| expr DOT ident { $$ = new ast::FieldAccess($1, $3); }
| expr PTR ident { $$ = new ast::FieldAccess(new ast::Unop(token::MUL, $1), $3); }
| "(" type ")" expr %prec CAST { $$ = new ast::Cast($2, $4); }
;
type : IDENT { $$ = $1; }
| IDENT MUL { $$ = $1 + "*"; }
| "(" IDENT ")" expr %prec CAST { $$ = new ast::Cast($2, false, $4); }
| "(" IDENT MUL ")" expr %prec CAST { $$ = new ast::Cast($2, true, $5); }
;
ident : IDENT { $$ = $1; }
......
......@@ -13,6 +13,8 @@ std::ostream &operator<<(std::ostream &os, Type type)
std::ostream &operator<<(std::ostream &os, const SizedType &type)
{
os << type.type;
if (type.is_pointer)
os << "*";
return os;
}
......
......@@ -36,6 +36,9 @@ public:
Type type;
size_t size;
std::string cast_type;
bool is_internal = false;
bool is_pointer = false;
size_t pointee_size;
bool operator==(const SizedType &t) const;
};
......
......@@ -2,6 +2,7 @@
#include "gtest/gtest.h"
#include "bpforc.h"
#include "bpftrace.h"
#include "clang_parser.h"
#include "codegen_llvm.h"
#include "driver.h"
#include "fake_map.h"
......@@ -46,6 +47,9 @@ void test(const std::string &input, const std::string expected_output)
ASSERT_EQ(driver.parse_str(input), 0);
ClangParser clang;
clang.parse(driver.root_, bpftrace.structs_);
ast::SemanticAnalyser semantics(driver.root_, bpftrace);
ASSERT_EQ(semantics.analyse(), 0);
ASSERT_EQ(semantics.create_maps(true), 0);
......@@ -1322,6 +1326,634 @@ attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, struct_integers)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%Foo.x = alloca i32, align 4
%1 = bitcast i32* %Foo.x to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %Foo.x, i64 4, i64 0)
%2 = load i32, i32* %Foo.x, align 4
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 0, i64* %"@x_key", align 8
%4 = zext i32 %2 to i64
%5 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 %4, i64* %"@x_val", align 8
%pseudo = 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 %3)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
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("struct Foo { int x; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @x = $foo.x;"
"}",
expected);
test("struct Foo { int x; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @x = $foo->x;"
"}",
expected);
}
TEST(codegen, struct_integer_ptr)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%deref = alloca i32, align 4
%Foo.x = alloca i64, align 8
%1 = bitcast i64* %Foo.x to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %Foo.x, i64 8, i64 0)
%2 = load i64, i64* %Foo.x, align 8
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast i32* %deref to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
%probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %deref, i64 4, i64 %2)
%4 = load i32, i32* %deref, align 4
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
%5 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 0, i64* %"@x_key", align 8
%6 = zext i32 %4 to i64
%7 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7)
store i64 %6, i64* %"@x_val", align 8
%pseudo = 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 %5)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7)
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("struct Foo { int *x; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @x = *$foo.x;"
"}",
expected);
test("struct Foo { int *x; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @x = *$foo->x;"
"}",
expected);
}
TEST(codegen, struct_string_ptr)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@mystr_key" = alloca i64, align 8
%Foo.str = alloca i64, align 8
%str = alloca [64 x i8], align 1
%1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 64, i32 1, i1 false)
%2 = bitcast i64* %Foo.str to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %Foo.str, i64 8, i64 0)
%3 = load i64, i64* %Foo.str, align 8
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
%probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %3)
%4 = bitcast i64* %"@mystr_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4)
store i64 0, i64* %"@mystr_key", align 8
%pseudo = 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 %"@mystr_key", [64 x i8]* nonnull %str, i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4)
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
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED";
test("struct Foo { char *str; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @mystr = str($foo.str);"
"}",
expected);
test("struct Foo { char *str; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @mystr = str($foo->str);"
"}",
expected);
}
TEST(codegen, struct_string_array)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@mystr_key" = alloca i64, align 8
%Foo.str = alloca [32 x i8], align 1
%1 = getelementptr inbounds [32 x i8], [32 x i8]* %Foo.str, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %Foo.str, i64 32, i64 0)
%2 = bitcast i64* %"@mystr_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 0, i64* %"@mystr_key", align 8
%pseudo = 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 %"@mystr_key", [32 x i8]* nonnull %Foo.str, i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
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("struct Foo { char str[32]; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @mystr = $foo.str;"
"}",
expected);
test("struct Foo { char str[32]; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @mystr = $foo->str;"
"}",
expected);
}
TEST(codegen, struct_nested_struct_named)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%Bar.x = alloca i32, align 4
%1 = bitcast i32* %Bar.x to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %Bar.x, i64 4, i64 0)
%2 = load i32, i32* %Bar.x, align 4
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 0, i64* %"@x_key", align 8
%4 = zext i32 %2 to i64
%5 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 %4, i64* %"@x_val", align 8
%pseudo = 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 %3)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
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("struct Bar { int x; } struct Foo { struct Bar bar; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @x = $foo.bar.x;"
"}",
expected);
test("struct Bar { int x; } struct Foo { struct Bar bar; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @x = $foo->bar.x;"
"}",
expected);
}
TEST(codegen, struct_nested_struct_ptr_named)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%Bar.x = alloca i32, align 4
%Foo.bar = alloca i64, align 8
%1 = bitcast i64* %Foo.bar to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %Foo.bar, i64 8, i64 0)
%2 = load i64, i64* %Foo.bar, align 8
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast i32* %Bar.x to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
%probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %Bar.x, i64 4, i64 %2)
%4 = load i32, i32* %Bar.x, align 4
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
%5 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 0, i64* %"@x_key", align 8
%6 = zext i32 %4 to i64
%7 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7)
store i64 %6, i64* %"@x_val", align 8
%pseudo = 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 %5)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7)
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("struct Bar { int x; } struct Foo { struct Bar *bar; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @x = $foo.bar->x;"
"}",
expected);
test("struct Bar { int x; } struct Foo { struct Bar *bar; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @x = $foo->bar->x;"
"}",
expected);
}
TEST(codegen, struct_nested_struct_anon)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%"Foo::(anonymous at definitions.h:1:14).x" = alloca i32, align 4
%1 = bitcast i32* %"Foo::(anonymous at definitions.h:1:14).x" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"Foo::(anonymous at definitions.h:1:14).x", i64 4, i64 0)
%2 = load i32, i32* %"Foo::(anonymous at definitions.h:1:14).x", align 4
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 0, i64* %"@x_key", align 8
%4 = zext i32 %2 to i64
%5 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 %4, i64* %"@x_val", align 8
%pseudo = 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 %3)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
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("struct Foo { struct { int x; } bar; }"
"kprobe:f"
"{"
" $foo = (Foo)0;"
" @x = $foo.bar.x;"
"}",
expected);
test("struct Foo { struct { int x; } bar; }"
"kprobe:f"
"{"
" $foo = (Foo*)0;"
" @x = $foo->bar.x;"
"}",
expected);
}
TEST(codegen, struct_save)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@foo_val" = alloca [12 x i8], align 1
%"@foo_key" = alloca i64, align 8
%1 = bitcast i64* %"@foo_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@foo_key", align 8
%2 = getelementptr inbounds [12 x i8], [12 x i8]* %"@foo_val", i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([12 x i8]* nonnull %"@foo_val", i64 12, i64 0)
%pseudo = 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 %"@foo_key", [12 x i8]* nonnull %"@foo_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
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED";
test("struct Foo { int x, y, z; }"
"kprobe:f"
"{"
" @foo = (Foo)0;"
"}",
expected);
test("struct Foo { int x, y, z; }"
"kprobe:f"
"{"
" @foo = *(Foo*)0;"
"}",
expected);
}
TEST(codegen, struct_save_nested)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%"@foo_key5" = alloca i64, align 8
%"@bar_key" = alloca i64, align 8
%internal_Foo.bar = alloca i64, align 8
%tmpcast = bitcast i64* %internal_Foo.bar to [8 x i8]*
%"@foo_key1" = alloca i64, align 8
%"@foo_val" = alloca [16 x i8], align 1
%"@foo_key" = alloca i64, align 8
%1 = bitcast i64* %"@foo_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@foo_key", align 8
%2 = getelementptr inbounds [16 x i8], [16 x i8]* %"@foo_val", i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* nonnull %"@foo_val", i64 16, i64 0)
%pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@foo_key", [16 x i8]* nonnull %"@foo_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)
%3 = bitcast i64* %"@foo_key1" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 0, i64* %"@foo_key1", align 8
%pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%lookup_elem = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo2, i64* nonnull %"@foo_key1")
%map_lookup_cond = icmp eq i8* %lookup_elem, null
br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success
lookup_success: ; preds = %entry
%lookup_elem_val.sroa.3.0.lookup_elem.sroa_idx = getelementptr inbounds i8, i8* %lookup_elem, i64 4
%lookup_elem_val.sroa.3.0.lookup_elem.sroa_cast = bitcast i8* %lookup_elem_val.sroa.3.0.lookup_elem.sroa_idx to i64*
%lookup_elem_val.sroa.3.0.copyload = load i64, i64* %lookup_elem_val.sroa.3.0.lookup_elem.sroa_cast, align 1
br label %lookup_merge
lookup_merge: ; preds = %entry, %lookup_success
%lookup_elem_val.sroa.3.0 = phi i64 [ %lookup_elem_val.sroa.3.0.copyload, %lookup_success ], [ 0, %entry ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
%4 = bitcast i64* %internal_Foo.bar to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4)
store i64 %lookup_elem_val.sroa.3.0, i64* %internal_Foo.bar, align 8
%5 = bitcast i64* %"@bar_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 0, i64* %"@bar_key", align 8
%pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem4 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo3, i64* nonnull %"@bar_key", [8 x i8]* nonnull %tmpcast, i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4)
%6 = bitcast i64* %"@foo_key5" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6)
store i64 0, i64* %"@foo_key5", align 8
%pseudo6 = call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%lookup_elem7 = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo6, i64* nonnull %"@foo_key5")
%map_lookup_cond12 = icmp eq i8* %lookup_elem7, null
br i1 %map_lookup_cond12, label %lookup_merge10, label %lookup_success8
lookup_success8: ; preds = %lookup_merge
%lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_idx = getelementptr inbounds i8, i8* %lookup_elem7, i64 4
%lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_cast = bitcast i8* %lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_idx to i64*
%lookup_elem_val11.sroa.3.0.copyload = load i64, i64* %lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_cast, align 1
%phitmp17 = and i64 %lookup_elem_val11.sroa.3.0.copyload, 4294967295
br label %lookup_merge10
lookup_merge10: ; preds = %lookup_merge, %lookup_success8
%lookup_elem_val11.sroa.3.0 = phi i64 [ %phitmp17, %lookup_success8 ], [ 0, %lookup_merge ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6)
%7 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7)
store i64 0, i64* %"@x_key", align 8
%8 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %8)
store i64 %lookup_elem_val11.sroa.3.0, i64* %"@x_val", align 8
%pseudo14 = call i64 @llvm.bpf.pseudo(i64 1, i64 3)
%update_elem15 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo14, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %8)
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("struct Foo { int m; struct { int x; int y; } bar; int n; }"
"kprobe:f"
"{"
" @foo = (Foo)0;"
" @bar = @foo.bar;"
" @x = @foo.bar.x;"
"}",
expected);
}
TEST(codegen, struct_save_string)
{
auto expected = 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* nocapture readnone) local_unnamed_addr section "s_kprobe:f" {
entry:
%"@str_key" = alloca i64, align 8
%lookup_elem_val = alloca [32 x i8], align 1
%"@foo_key1" = alloca i64, align 8
%"@foo_val" = alloca [32 x i8], align 1
%"@foo_key" = alloca i64, align 8
%1 = bitcast i64* %"@foo_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@foo_key", align 8
%2 = getelementptr inbounds [32 x i8], [32 x i8]* %"@foo_val", i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %"@foo_val", i64 32, i64 0)
%pseudo = 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 %"@foo_key", [32 x i8]* nonnull %"@foo_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)
%3 = bitcast i64* %"@foo_key1" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 0, i64* %"@foo_key1", align 8
%pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo2, i64* nonnull %"@foo_key1")
%4 = getelementptr inbounds [32 x i8], [32 x i8]* %lookup_elem_val, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4)
%map_lookup_cond = icmp eq i8* %lookup_elem, null
br i1 %map_lookup_cond, label %lookup_failure, label %lookup_success
lookup_success: ; preds = %entry
call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %4, i8* nonnull %lookup_elem, i64 32, i32 1, i1 false)
br label %lookup_merge
lookup_failure: ; preds = %entry
call void @llvm.memset.p0i8.i64(i8* nonnull %4, i8 0, i64 32, i32 1, i1 false)
br label %lookup_merge
lookup_merge: ; preds = %lookup_failure, %lookup_success
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
%5 = bitcast i64* %"@str_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 0, i64* %"@str_key", align 8
%pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%update_elem4 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo3, i64* nonnull %"@str_key", i8* nonnull %4, i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
; Function Attrs: argmemonly nounwind
declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1
; 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("struct Foo { char str[32]; }"
"kprobe:f"
"{"
" @foo = (Foo)0;"
" @str = @foo.str;"
"}",
expected);
}
} // namespace codegen
} // namespace test
} // namespace bpftrace
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "bpftrace.h"
#include "clang_parser.h"
#include "driver.h"
#include "semantic_analyser.h"
......@@ -19,6 +20,9 @@ void test(BPFtrace &bpftrace, Driver &driver, const std::string &input, int expe
{
ASSERT_EQ(driver.parse_str(input), 0);
ClangParser clang;
clang.parse(driver.root_, bpftrace.structs_);
std::stringstream out;
ast::SemanticAnalyser semantics(driver.root_, bpftrace, out);
std::stringstream msg;
......@@ -40,21 +44,7 @@ void test(Driver &driver, const std::string &input, int expected_result=0)
void test(const std::string &input, int expected_result=0)
{
Field field = { SizedType(Type::integer, 8), 0 };
Field mystr = { SizedType(Type::string, 8), 8 };
Field type2_field_ptr = { SizedType(Type::cast, 8, "type2*"), 16 };
Field type2_field = { SizedType(Type::cast, 8, "type2"), 24 };
Struct type1 = { 16, {{"field", field},
{"mystr", mystr},
{"type2ptr", type2_field_ptr},
{"type2", type2_field}} };
Struct type2 = { 8, {{"field", field}} };
BPFtrace bpftrace;
bpftrace.structs_["type1"] = type1;
bpftrace.structs_["type2"] = type2;
Driver driver;
test(bpftrace, driver, input, expected_result);
}
......@@ -217,6 +207,34 @@ TEST(semantic_analyser, variable_type)
EXPECT_EQ(st, assignment->var->type);
}
TEST(semantic_analyser, map_integer_sizes)
{
Driver driver;
std::string structs = "struct type1 { int x; }";
test(driver, structs + "kprobe:f { $x = ((type1)0).x; @x = $x; }", 0);
auto var_assignment = static_cast<ast::AssignVarStatement*>(driver.root_->probes->at(0)->stmts->at(0));
auto map_assignment = static_cast<ast::AssignMapStatement*>(driver.root_->probes->at(0)->stmts->at(1));
EXPECT_EQ(SizedType(Type::integer, 4), var_assignment->var->type);
EXPECT_EQ(SizedType(Type::integer, 8), map_assignment->map->type);
}
TEST(semantic_analyser, unop_dereference)
{
test("kprobe:f { *0; }", 0);
test("struct X { int n; } kprobe:f { $x = (X*)0; *$x; }", 0);
test("struct X { int n; } kprobe:f { $x = (X)0; *$x; }", 1);
test("kprobe:f { *\"0\"; }", 10);
}
TEST(semantic_analyser, unop_not)
{
test("kprobe:f { ~0; }", 0);
test("struct X { int n; } kprobe:f { $x = (X*)0; ~$x; }", 10);
test("struct X { int n; } kprobe:f { $x = (X)0; ~$x; }", 10);
test("kprobe:f { ~\"0\"; }", 10);
}
TEST(semantic_analyser, printf)
{
test("kprobe:f { printf(\"hi\") }", 0);
......@@ -370,24 +388,28 @@ TEST(semantic_analyser, profile)
TEST(semantic_analyser, variable_cast_types)
{
test("kprobe:f { $x = (type1)cpu; $x = (type1)cpu; }", 0);
test("kprobe:f { $x = (type1)cpu; $x = (type2)cpu; }", 1);
std::string structs = "struct type1 { int field; } struct type2 { int field; }";
test(structs + "kprobe:f { $x = (type1)cpu; $x = (type1)cpu; }", 0);
test(structs + "kprobe:f { $x = (type1)cpu; $x = (type2)cpu; }", 1);
}
TEST(semantic_analyser, map_cast_types)
{
test("kprobe:f { @x = (type1)cpu; @x = (type1)cpu; }", 0);
test("kprobe:f { @x = (type1)cpu; @x = (type2)cpu; }", 1);
std::string structs = "struct type1 { int field; } struct type2 { int field; }";
test(structs + "kprobe:f { @x = (type1)cpu; @x = (type1)cpu; }", 0);
test(structs + "kprobe:f { @x = (type1)cpu; @x = (type2)cpu; }", 1);
}
TEST(semantic_analyser, variable_casts_are_local)
{
test("kprobe:f { $x = (type1)cpu } kprobe:g { $x = (type2)cpu; }", 0);
std::string structs = "struct type1 { int field; } struct type2 { int field; }";
test(structs + "kprobe:f { $x = (type1)cpu } kprobe:g { $x = (type2)cpu; }", 0);
}
TEST(semantic_analyser, map_casts_are_global)
{
test("kprobe:f { @x = (type1)cpu } kprobe:g { @x = (type2)cpu; }", 1);
std::string structs = "struct type1 { int field; } struct type2 { int field; }";
test(structs + "kprobe:f { @x = (type1)cpu } kprobe:g { @x = (type2)cpu; }", 1);
}
TEST(semantic_analyser, cast_unknown_type)
......@@ -397,50 +419,76 @@ TEST(semantic_analyser, cast_unknown_type)
TEST(semantic_analyser, field_access)
{
test("kprobe:f { ((type1)cpu).field }", 0);
test("kprobe:f { $x = (type1)cpu; $x.field }", 0);
test("kprobe:f { @x = (type1)cpu; @x.field }", 0);
std::string structs = "struct type1 { int field; }";
test(structs + "kprobe:f { ((type1)cpu).field }", 0);
test(structs + "kprobe:f { $x = (type1)cpu; $x.field }", 0);
test(structs + "kprobe:f { @x = (type1)cpu; @x.field }", 0);
}
TEST(semantic_analyser, field_access_wrong_field)
{
test("kprobe:f { ((type1)cpu).blah }", 1);
test("kprobe:f { $x = (type1)cpu; $x.blah }", 1);
test("kprobe:f { @x = (type1)cpu; @x.blah }", 1);
std::string structs = "struct type1 { int field; }";
test(structs + "kprobe:f { ((type1)cpu).blah }", 1);
test(structs + "kprobe:f { $x = (type1)cpu; $x.blah }", 1);
test(structs + "kprobe:f { @x = (type1)cpu; @x.blah }", 1);
}
TEST(semantic_analyser, field_access_wrong_expr)
{
test("kprobe:f { 1234->field }", 10);
std::string structs = "struct type1 { int field; }";
test(structs + "kprobe:f { 1234->field }", 10);
}
TEST(semantic_analyser, field_access_types)
{
test("kprobe:f { ((type1)0).field == 123 }", 0);
test("kprobe:f { ((type1)0).field == \"abc\" }", 10);
std::string structs = "struct type1 { int field; char mystr[8]; }"
"struct type2 { int field; }";
test("kprobe:f { ((type1)0).mystr == \"abc\" }", 0);
test("kprobe:f { ((type1)0).mystr == 123 }", 10);
test(structs + "kprobe:f { ((type1)0).field == 123 }", 0);
test(structs + "kprobe:f { ((type1)0).field == \"abc\" }", 10);
test("kprobe:f { ((type1)0).field == ((type2)0).field }", 0);
test("kprobe:f { ((type1)0).mystr == ((type2)0).field }", 10);
test(structs + "kprobe:f { ((type1)0).mystr == \"abc\" }", 0);
test(structs + "kprobe:f { ((type1)0).mystr == 123 }", 10);
test(structs + "kprobe:f { ((type1)0).field == ((type2)0).field }", 0);
test(structs + "kprobe:f { ((type1)0).mystr == ((type2)0).field }", 10);
}
TEST(semantic_analyser, field_access_pointer)
{
test("kprobe:f { ((type1*)0)->field }", 0);
test("kprobe:f { ((type1*)0).field }", 1);
test("kprobe:f { *((type1*)0) }", 0);
std::string structs = "struct type1 { int field; }";
test(structs + "kprobe:f { ((type1*)0)->field }", 0);
test(structs + "kprobe:f { ((type1*)0).field }", 1);
test(structs + "kprobe:f { *((type1*)0) }", 0);
}
TEST(semantic_analyser, field_access_sub_struct)
{
test("kprobe:f { ((type1)0).type2ptr->field }", 0);
test("kprobe:f { ((type1)0).type2.field }", 0);
test("kprobe:f { $x = (type2)0; $x = ((type1)0).type2 }", 0);
test("kprobe:f { $x = (type2*)0; $x = ((type1)0).type2ptr }", 0);
test("kprobe:f { $x = (type1)0; $x = ((type1)0).type2 }", 1);
test("kprobe:f { $x = (type1*)0; $x = ((type1)0).type2ptr }", 1);
std::string structs = "struct type1 { struct type2 *type2ptr; struct type2 type2; }"
"struct type2 { int field; }";
test(structs + "kprobe:f { ((type1)0).type2ptr->field }", 0);
test(structs + "kprobe:f { ((type1)0).type2.field }", 0);
test(structs + "kprobe:f { $x = (type2)0; $x = ((type1)0).type2 }", 0);
test(structs + "kprobe:f { $x = (type2*)0; $x = ((type1)0).type2ptr }", 0);
test(structs + "kprobe:f { $x = (type1)0; $x = ((type1)0).type2 }", 1);
test(structs + "kprobe:f { $x = (type1*)0; $x = ((type1)0).type2ptr }", 1);
}
TEST(semantic_analyser, field_access_is_internal)
{
Driver driver;
std::string structs = "struct type1 { int x; }";
test(driver, structs + "kprobe:f { $x = ((type1)0).x }", 0);
auto var_assignment1 = static_cast<ast::AssignVarStatement*>(driver.root_->probes->at(0)->stmts->at(0));
EXPECT_EQ(false, var_assignment1->var->type.is_internal);
test(driver, structs + "kprobe:f { @type1 = (type1)0; $x = @type1.x }", 0);
auto map_assignment = static_cast<ast::AssignMapStatement*>(driver.root_->probes->at(0)->stmts->at(0));
auto var_assignment2 = static_cast<ast::AssignVarStatement*>(driver.root_->probes->at(0)->stmts->at(1));
EXPECT_EQ(true, map_assignment->map->type.is_internal);
EXPECT_EQ(true, var_assignment2->var->type.is_internal);
}
} // namespace semantic_analyser
......
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