diff --git a/docs/reference_guide.md b/docs/reference_guide.md index 176cefce5dc..e3fba3d07d6 100644 --- a/docs/reference_guide.md +++ b/docs/reference_guide.md @@ -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) @@ -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 diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index c9431073c8a..1308bda0b02 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -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); } diff --git a/src/ast/ast.h b/src/ast/ast.h index ada3bee2c12..519361481e2 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -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) @@ -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; diff --git a/src/ast/codegen_llvm.cpp b/src/ast/codegen_llvm.cpp index 3effd300b3f..4dcc2ed6b3e 100644 --- a/src/ast/codegen_llvm.cpp +++ b/src/ast/codegen_llvm.cpp @@ -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); @@ -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); diff --git a/src/ast/codegen_llvm.h b/src/ast/codegen_llvm.h index 853990b1ad5..2774e724a3c 100644 --- a/src/ast/codegen_llvm.h +++ b/src/ast/codegen_llvm.h @@ -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; diff --git a/src/ast/printer.cpp b/src/ast/printer.cpp index 0a8f09fb03a..b79c342a2e8 100644 --- a/src/ast/printer.cpp +++ b/src/ast/printer.cpp @@ -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_, ' '); diff --git a/src/ast/printer.h b/src/ast/printer.h index 11c23c82d8f..45bba1569a9 100644 --- a/src/ast/printer.h +++ b/src/ast/printer.h @@ -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; diff --git a/src/ast/semantic_analyser.cpp b/src/ast/semantic_analyser.cpp index 4ed6a613ce9..4d37a2aff32 100644 --- a/src/ast/semantic_analyser.cpp +++ b/src/ast/semantic_analyser.cpp @@ -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(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); diff --git a/src/ast/semantic_analyser.h b/src/ast/semantic_analyser.h index 8fbc174e810..8aa8dbffc35 100644 --- a/src/ast/semantic_analyser.h +++ b/src/ast/semantic_analyser.h @@ -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; diff --git a/src/clang_parser.cpp b/src/clang_parser.cpp index 5bfd4f90747..868aa4cfb7d 100644 --- a/src/clang_parser.cpp +++ b/src/clang_parser.cpp @@ -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); diff --git a/src/parser.yy b/src/parser.yy index 249665557e8..5e202564839 100644 --- a/src/parser.yy +++ b/src/parser.yy @@ -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); } ; diff --git a/src/tracepoint_format_parser.h b/src/tracepoint_format_parser.h index f40093dca74..5a11f3f54b2 100644 --- a/src/tracepoint_format_parser.h +++ b/src/tracepoint_format_parser.h @@ -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); }; diff --git a/src/types.cpp b/src/types.cpp index 4599f75df08..af9bafbba3b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -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 @@ -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(); diff --git a/src/types.h b/src/types.h index aafa31e02c7..e0fe8735fd1 100644 --- a/src/types.h +++ b/src/types.h @@ -36,6 +36,7 @@ enum class Type username, inet, stack_mode, + array, }; std::ostream &operator<<(std::ostream &os, Type type); @@ -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; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 66e0c1175cd..b7f8904d852 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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() diff --git a/tests/README.md b/tests/README.md index db867fa5400..c0be4cf54c3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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. diff --git a/tests/parser.cpp b/tests/parser.cpp index 3e573256dea..c6f27bb187f 100644 --- a/tests/parser.cpp +++ b/tests/parser.cpp @@ -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; }", diff --git a/tests/runtime/array b/tests/runtime/array new file mode 100644 index 00000000000..993385dddde --- /dev/null +++ b/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) & diff --git a/tests/semantic_analyser.cpp b/tests/semantic_analyser.cpp index fbf49ba0d2d..b9459e960ac 100644 --- a/tests/semantic_analyser.cpp +++ b/tests/semantic_analyser.cpp @@ -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; diff --git a/tests/testprogs/array_access.c b/tests/testprogs/array_access.c new file mode 100644 index 00000000000..b73d6a776ff --- /dev/null +++ b/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); +}