Skip to content

Commit

Permalink
Implementation of array indexing operator [] for one-dimensional, con…
Browse files Browse the repository at this point in the history
…stant arrays (#285)
  • Loading branch information
dalehamel authored and ajor committed Apr 21, 2019
1 parent 48619f4 commit ec664a1
Show file tree
Hide file tree
Showing 20 changed files with 201 additions and 6 deletions.
14 changes: 14 additions & 0 deletions docs/reference_guide.md
Expand Up @@ -27,6 +27,7 @@ This is a work in progress. If something is missing, check the bpftrace source t
- [7. `if () {...} else {...}`: if-else statements](#7-if---else--if-else-statements)
- [8. `unroll () {...}`: unroll](#8-unroll---unroll)
- [9. `++ and --`: increment operators](#9--and----increment-operators)
- [10. `[]`: Array access](#10--array-access)
- [Probes](#probes)
- [1. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level](#1-kprobekretprobe-dynamic-tracing-kernel-level)
- [2. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level Arguments](#2-kprobekretprobe-dynamic-tracing-kernel-level-arguments)
Expand Down Expand Up @@ -573,6 +574,19 @@ Attaching 1 probe...
@[kprobe:vfs_read]: 13369
```

## 10. Array Access

You may access one-dimensional constant arrays with the array acccess operator `[]`.

Example:

```
# bpftrace -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; @x = $s->y[0]; exit(); }'
Attaching 1 probe...
@x: 1
```

# Probes

- `kprobe` - kernel function start
Expand Down
4 changes: 4 additions & 0 deletions src/ast/ast.cpp
Expand Up @@ -57,6 +57,10 @@ void FieldAccess::accept(Visitor &v) {
v.visit(*this);
}

void ArrayAccess::accept(Visitor &v) {
v.visit(*this);
}

void Cast::accept(Visitor &v) {
v.visit(*this);
}
Expand Down
10 changes: 10 additions & 0 deletions src/ast/ast.h
Expand Up @@ -135,6 +135,15 @@ class FieldAccess : public Expression {
void accept(Visitor &v) override;
};

class ArrayAccess : public Expression {
public:
ArrayAccess(Expression *expr, Expression* indexpr) : expr(expr), indexpr(indexpr) { }
Expression *expr;
Expression *indexpr;

void accept(Visitor &v) override;
};

class Cast : public Expression {
public:
Cast(const std::string &type, bool is_pointer, Expression *expr)
Expand Down Expand Up @@ -305,6 +314,7 @@ class Visitor {
virtual void visit(Unop &unop) = 0;
virtual void visit(Ternary &ternary) = 0;
virtual void visit(FieldAccess &acc) = 0;
virtual void visit(ArrayAccess &arr) = 0;
virtual void visit(Cast &cast) = 0;
virtual void visit(ExprStatement &expr) = 0;
virtual void visit(AssignMapStatement &assignment) = 0;
Expand Down
27 changes: 27 additions & 0 deletions src/ast/codegen_llvm.cpp
Expand Up @@ -991,6 +991,14 @@ void CodegenLLVM::visit(FieldAccess &acc)
// pointer internally and dereference later when necessary.
expr_ = src;
}
else if (field.type.type == Type::array)
{
// For array types, we want to just pass pointer along,
// since the offset of the field should be the start of the array.
// The pointer will be dereferenced when the array is accessed by a []
// operation
expr_ = src;
}
else if (field.type.type == Type::string)
{
AllocaInst *dst = b_.CreateAllocaBPF(field.type, type.cast_type + "." + acc.field);
Expand All @@ -1007,6 +1015,25 @@ void CodegenLLVM::visit(FieldAccess &acc)
}
}

void CodegenLLVM::visit(ArrayAccess &arr)
{
Value *array, *index, *offset;
SizedType &type = arr.expr->type;

arr.expr->accept(*this);
array = expr_;

arr.indexpr->accept(*this);
index = b_.CreateIntCast(expr_, b_.getInt64Ty(), false); // promote int to 64-bit
offset = b_.CreateMul(index, b_.getInt64(type.pointee_size));

AllocaInst *dst = b_.CreateAllocaBPF(SizedType(Type::integer, type.pointee_size), "array_access");
Value *src = b_.CreateAdd(array, offset);
b_.CreateProbeRead(dst, type.pointee_size, src);
expr_ = b_.CreateLoad(dst);
b_.CreateLifetimeEnd(dst);
}

void CodegenLLVM::visit(Cast &cast)
{
cast.expr->accept(*this);
Expand Down
1 change: 1 addition & 0 deletions src/ast/codegen_llvm.h
Expand Up @@ -40,6 +40,7 @@ class CodegenLLVM : public Visitor {
void visit(Unop &unop) override;
void visit(Ternary &ternary) override;
void visit(FieldAccess &acc) override;
void visit(ArrayAccess &arr) override;
void visit(Cast &cast) override;
void visit(ExprStatement &expr) override;
void visit(AssignMapStatement &assignment) override;
Expand Down
11 changes: 11 additions & 0 deletions src/ast/printer.cpp
Expand Up @@ -128,6 +128,17 @@ void Printer::visit(FieldAccess &acc)
out_ << indent << " " << acc.field << std::endl;
}

void Printer::visit(ArrayAccess &arr)
{
std::string indent(depth_, ' ');
out_ << indent << "[]" << std::endl;

++depth_;
arr.expr->accept(*this);
arr.indexpr->accept(*this);
--depth_;
}

void Printer::visit(Cast &cast)
{
std::string indent(depth_, ' ');
Expand Down
1 change: 1 addition & 0 deletions src/ast/printer.h
Expand Up @@ -23,6 +23,7 @@ class Printer : public Visitor {
void visit(Unop &unop) override;
void visit(Ternary &ternary) override;
void visit(FieldAccess &acc) override;
void visit(ArrayAccess &arr) override;
void visit(Cast &cast) override;
void visit(ExprStatement &expr) override;
void visit(AssignMapStatement &assignment) override;
Expand Down
24 changes: 24 additions & 0 deletions src/ast/semantic_analyser.cpp
Expand Up @@ -573,6 +573,30 @@ void SemanticAnalyser::visit(Variable &var)
}
}

void SemanticAnalyser::visit(ArrayAccess &arr)
{
arr.expr->accept(*this);
arr.indexpr->accept(*this);

SizedType &type = arr.expr->type;
SizedType &indextype = arr.indexpr->type;

if (is_final_pass() && !(type.type == Type::array))
err_ << "The array index operator [] can only be used on arrays." << std::endl;

if (is_final_pass() && !(indextype.type == Type::integer))
err_ << "The array index operator [] only accepts integer indices." << std::endl;

if (is_final_pass() && (indextype.type == Type::integer)) {
Integer *index = static_cast<Integer *>(arr.indexpr);

if ((size_t) index->n >= type.size)
err_ << "the index " << index->n << " is out of bounds for array of size " << type.size << std::endl;
}

arr.type = SizedType(type.elem_type, type.pointee_size);
}

void SemanticAnalyser::visit(Binop &binop)
{
binop.left->accept(*this);
Expand Down
1 change: 1 addition & 0 deletions src/ast/semantic_analyser.h
Expand Up @@ -32,6 +32,7 @@ class SemanticAnalyser : public Visitor {
void visit(Unop &unop) override;
void visit(Ternary &ternary) override;
void visit(FieldAccess &acc) override;
void visit(ArrayAccess &arr) override;
void visit(Cast &cast) override;
void visit(ExprStatement &expr) override;
void visit(AssignMapStatement &assignment) override;
Expand Down
14 changes: 12 additions & 2 deletions src/clang_parser.cpp
Expand Up @@ -147,8 +147,18 @@ static SizedType get_sized_type(CXType clang_type)
{
return SizedType(Type::string, size);
}
// TODO add support for arrays
return SizedType(Type::none, 0);

// Only support one-dimensional arrays for now
if (elem_type.kind != CXType_ConstantArray)
{
auto type = get_sized_type(elem_type);
auto sized_type = SizedType(Type::array, size);
sized_type.pointee_size = type.size;
sized_type.elem_type = type.type;
return sized_type;
} else {
return SizedType(Type::none, 0);
}
}
default:
return SizedType(Type::none, 0);
Expand Down
1 change: 1 addition & 0 deletions src/parser.yy
Expand Up @@ -235,6 +235,7 @@ 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); }
| expr "[" expr "]" { $$ = new ast::ArrayAccess($1, $3); }
| "(" IDENT ")" expr %prec CAST { $$ = new ast::Cast($2, false, $4); }
| "(" IDENT MUL ")" expr %prec CAST { $$ = new ast::Cast($2, true, $5); }
;
Expand Down
3 changes: 3 additions & 0 deletions src/tracepoint_format_parser.h
Expand Up @@ -52,6 +52,9 @@ class TracepointArgsVisitor : public Visitor
void visit(FieldAccess &acc) override {
acc.expr->accept(*this);
};
void visit(ArrayAccess &acc) override {
acc.expr->accept(*this);
};
void visit(Cast &cast) override {
cast.expr->accept(*this);
};
Expand Down
5 changes: 4 additions & 1 deletion src/types.cpp
Expand Up @@ -26,7 +26,9 @@ bool SizedType::operator==(const SizedType &t) const

bool SizedType::IsArray() const
{
return type == Type::string || type == Type::usym || type == Type::inet || (type == Type::cast && !is_pointer);
return type == Type::array || type == Type::string ||
type == Type::usym || type == Type::inet ||
(type == Type::cast && !is_pointer);
}

bool SizedType::IsStack() const
Expand Down Expand Up @@ -56,6 +58,7 @@ std::string typestr(Type t)
case Type::inet: return "inet"; break;
case Type::cast: return "cast"; break;
case Type::probe: return "probe"; break;
case Type::array: return "array"; break;
default:
std::cerr << "call or probe type not found" << std::endl;
abort();
Expand Down
2 changes: 2 additions & 0 deletions src/types.h
Expand Up @@ -36,6 +36,7 @@ enum class Type
username,
inet,
stack_mode,
array,
};

std::ostream &operator<<(std::ostream &os, Type type);
Expand Down Expand Up @@ -67,6 +68,7 @@ class SizedType
stack_type = stack_type_;
}
Type type;
Type elem_type; // Array element type if accessing elements of an array
size_t size;
StackType stack_type;
std::string cast_type;
Expand Down
9 changes: 9 additions & 0 deletions tests/CMakeLists.txt
Expand Up @@ -92,3 +92,12 @@ foreach(runtime_test ${runtime_tests})
endforeach()
add_custom_target(runtime-tests COMMAND ./runtime-tests.sh)
add_test(NAME runtime_test COMMAND ./runtime-tests.sh)

# Compile all testprograms, one per .c file for runtime testing
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testprogs/)
file(GLOB testprogs testprogs/*)
foreach(testprog ${testprogs})
get_filename_component(bin_name ${testprog} NAME_WE)
add_executable (${bin_name} ${testprog})
set_target_properties( ${bin_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testprogs/ )
endforeach()
14 changes: 11 additions & 3 deletions tests/README.md
Expand Up @@ -12,6 +12,14 @@ The code generation tests are based on the output of LLVM 5, so may give errors

## Runtime tests

Runtime tests will call the bpftrace executable.
* Run: `sudo make runtime-tests` inside your build folder
* By default, runtime-tests will look for the executable in the build folder. You can set a value to the environment variable `BPFTRACE_RUNTIME_TEST_EXECUTABLE` to customize it
Runtime tests will call the bpftrace executable.
* Run: `sudo make runtime-tests` inside your build folder
* By default, runtime-tests will look for the executable in the build folder. You can set a value to the environment variable `BPFTRACE_RUNTIME_TEST_EXECUTABLE` to customize it

### Test programs

You can add test programs for your runtime tests by placing a `.c` file corresponding to your test program in `tests/testprogs`.

The test file `tests/testprogs/my_test.c` will result in an executable that you can call and probe in your runtime test at `./testprogs/my_test`

This intended to be useful for testing uprobes and USDT probes, or using uprobes to verify some other behavior in bpftrace.
19 changes: 19 additions & 0 deletions tests/parser.cpp
Expand Up @@ -879,6 +879,25 @@ TEST(Parser, field_access_builtin)
" count\n");
}

TEST(Parser, array_access)
{
test("kprobe:sys_read { x[index]; }",
"Program\n"
" kprobe:sys_read\n"
" []\n"
" identifier: x\n"
" identifier: index\n");

test("kprobe:sys_read { $val = x[index]; }",
"Program\n"
" kprobe:sys_read\n"
" =\n"
" variable: $val\n"
" []\n"
" identifier: x\n"
" identifier: index\n");
}

TEST(Parser, cstruct)
{
test("struct Foo { int x, y; char *str; } kprobe:sys_read { 1; }",
Expand Down
17 changes: 17 additions & 0 deletions tests/runtime/array
@@ -0,0 +1,17 @@
NAME array element access - assign to map
RUN bpftrace -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; @x = $s->y[0]; exit(); }'
EXPECT @x: 1
TIMEOUT 5
BEFORE (sleep 1 && ./testprogs/array_access) &

NAME array element access - assign to var
RUN bpftrace -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; $x = $s->y[0]; printf("%d\n", $x); exit(); }'
EXPECT 1
TIMEOUT 5
BEFORE (sleep 1 && ./testprogs/array_access) &

NAME array element access - out of bounds
RUN bpftrace -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; $x = $s->y[5]; printf("%d\n", $x); exit(); }'
EXPECT the index 5 is out of bounds for array of size 4
TIMEOUT 5
BEFORE (sleep 1 && ./testprogs/array_access) &
17 changes: 17 additions & 0 deletions tests/semantic_analyser.cpp
Expand Up @@ -346,6 +346,23 @@ TEST(semantic_analyser, variables_are_local)
test("kprobe:f { $x = 1 } kprobe:g { $x = \"abc\"; }", 0);
}

TEST(semantic_analyser, array_access) {
test("kprobe:f { $s = arg0; @x = $s->y[0];}", 10);
test("kprobe:f { $s = 0; @x = $s->y[0];}", 10);
test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) "
"arg0; @x = $s->y[0];}",
0);
test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) "
"arg0; @x = $s->y[5];}",
10);
test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) "
"arg0; @x = $s->y[-1];}",
10);
test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) "
"arg0; @x = $s->y[\"0\"];}",
10);
}

TEST(semantic_analyser, variable_type)
{
BPFtrace bpftrace;
Expand Down
13 changes: 13 additions & 0 deletions tests/testprogs/array_access.c
@@ -0,0 +1,13 @@
typedef struct MY_STRUCT{
int x[4];

}mystruct;

void test_struct(mystruct *s) { }

int main(int argc, char **argv)
{
mystruct s;
s.x[0] = 1;
test_struct(&s);
}

0 comments on commit ec664a1

Please sign in to comment.