From d9cc9fa60c9af92a181d0d05fe6535b98df4bdb2 Mon Sep 17 00:00:00 2001 From: Rong Tao Date: Sat, 25 Jan 2025 16:16:30 +0800 Subject: [PATCH] offsetof: Support sub-field Since bpftrace PR [1] support offsetof() function. As previously discussed [2], the current offsetof() does not support sub-fields, and this PR implements offsetof(struct foo, a.b.c), implement [1] unfinished features. Example: struct Foo { struct e { int a; int b; struct c { int a, b, c; } c; } e; } struct offsetof { struct { int comm; int ustack; } kstack; } BEGIN { printf("struct Foo e.b offset %ld\n", offsetof(struct Foo, e.b)); printf("struct Foo e.c offset %ld\n", offsetof(struct Foo, e.c)); printf("struct Foo e.c.b offset %ld\n", offsetof(struct Foo, e.c.b)); printf("struct offsetof kstack.ustack offset %ld\n", offsetof(struct offsetof, kstack.ustack)); exit(); } Link: https://github.com/bpftrace/bpftrace/pull/2579 [1] merged Link: https://github.com/bpftrace/bpftrace/pull/2216 [2] Signed-off-by: Rong Tao --- CHANGELOG.md | 2 + man/adoc/bpftrace.adoc | 24 +++- src/ast/ast.cpp | 8 +- src/ast/ast.h | 6 +- src/ast/passes/codegen_llvm.cpp | 9 +- src/ast/passes/printer.cpp | 4 +- src/ast/passes/semantic_analyser.cpp | 27 ++-- src/parser.yy | 12 +- tests/codegen/call_offsetof.cpp | 7 + tests/codegen/llvm/call_offsetof_sub_field.ll | 130 ++++++++++++++++++ tests/parser.cpp | 17 +++ tests/runtime/builtin | 4 +- tests/semantic_analyser.cpp | 25 ++++ 13 files changed, 250 insertions(+), 25 deletions(-) create mode 100644 tests/codegen/llvm/call_offsetof_sub_field.ll diff --git a/CHANGELOG.md b/CHANGELOG.md index e952a9fc551e..d3e59d71babd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to #### Breaking Changes #### Added +- `offsetof()` now supports sub fields e.g. `offsetof(struct Foo, bar.a.b);` + - [#3761](https://github.com/bpftrace/bpftrace/pull/3761) - Pointers may now be used in if conditions, tenary conditions and as operands in logical AND and OR expressions - [#3656](https://github.com/bpftrace/bpftrace/pull/3656) #### Changed diff --git a/man/adoc/bpftrace.adoc b/man/adoc/bpftrace.adoc index 78fa10cad906..f220be663005 100644 --- a/man/adoc/bpftrace.adoc +++ b/man/adoc/bpftrace.adoc @@ -1789,14 +1789,32 @@ You can also pass the address type (e.g. AF_INET) explicitly as the first parame === offsetof .variants -* `offsetof(STRUCT, FIELD)` -* `offsetof(EXPRESSION, FIELD)` +* `offsetof(STRUCT, FIELD[.SUBFIELD])` +* `offsetof(EXPRESSION, FIELD[.SUBFIELD])` *compile time* Returns offset of the field offset bytes in struct. Similar to kernel `offsetof` operator. -Note that subfields are not yet supported. + +Support any number of sub field levels, for example: + +---- +struct Foo { + struct { + struct { + struct { + int d; + } c; + } b; + } a; +} +BEGIN { + @x = offsetof(struct Foo, a.b.c.d); + exit(); +} +---- + [#functions-override] === override diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index a314d2c61291..c48d806f5d67 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -105,12 +105,16 @@ Sizeof::Sizeof(Expression *expr, location loc) : Expression(loc), expr(expr) { } -Offsetof::Offsetof(SizedType record, std::string &field, location loc) +Offsetof::Offsetof(SizedType record, + std::vector &field, + location loc) : Expression(loc), record(record), field(field) { } -Offsetof::Offsetof(Expression *expr, std::string &field, location loc) +Offsetof::Offsetof(Expression *expr, + std::vector &field, + location loc) : Expression(loc), expr(expr), field(field) { } diff --git a/src/ast/ast.h b/src/ast/ast.h index 21c90dae8a6b..0441fa3e0641 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -219,12 +219,12 @@ class Offsetof : public Expression { public: DEFINE_ACCEPT - Offsetof(SizedType record, std::string &field, location loc); - Offsetof(Expression *expr, std::string &field, location loc); + Offsetof(SizedType record, std::vector &field, location loc); + Offsetof(Expression *expr, std::vector &field, location loc); SizedType record; Expression *expr = nullptr; - std::string field; + std::vector field; private: Offsetof(const Offsetof &other) = default; diff --git a/src/ast/passes/codegen_llvm.cpp b/src/ast/passes/codegen_llvm.cpp index 6c78aa55eb04..ad68e2d2dabd 100644 --- a/src/ast/passes/codegen_llvm.cpp +++ b/src/ast/passes/codegen_llvm.cpp @@ -1522,8 +1522,13 @@ void CodegenLLVM::visit(Sizeof &szof) void CodegenLLVM::visit(Offsetof &ofof) { - auto &field = ofof.record.GetField(ofof.field); - expr_ = b_.getInt64(field.offset); + ssize_t offset = 0; + const SizedType *record = &ofof.record; + for (const auto &field : ofof.field) { + offset += record->GetField(field).offset; + record = &record->GetField(field).type; + } + expr_ = b_.getInt64(offset); } void CodegenLLVM::visit(Map &map) diff --git a/src/ast/passes/printer.cpp b/src/ast/passes/printer.cpp index a4de2d61f2b9..e6f1d36d826f 100644 --- a/src/ast/passes/printer.cpp +++ b/src/ast/passes/printer.cpp @@ -141,7 +141,9 @@ void Printer::visit(Offsetof &ofof) out_ << indentParam << ofof.record << std::endl; } - out_ << indentParam << ofof.field << std::endl; + for (const auto &field : ofof.field) { + out_ << indentParam << field << std::endl; + } --depth_; } diff --git a/src/ast/passes/semantic_analyser.cpp b/src/ast/passes/semantic_analyser.cpp index 71ce641e4af0..74f647b81fe5 100644 --- a/src/ast/passes/semantic_analyser.cpp +++ b/src/ast/passes/semantic_analyser.cpp @@ -1615,17 +1615,26 @@ void SemanticAnalyser::visit(Offsetof &ofof) Visit(ofof.expr); ofof.record = ofof.expr->type; } + resolve_struct_type(ofof.record, ofof.loc); - if (!ofof.record.IsRecordTy()) { - LOG(ERROR, ofof.loc, err_) - << "offsetof() 1st argument is not of a record type."; - } else if (!bpftrace_.structs.Has(ofof.record.GetName())) { - LOG(ERROR, ofof.loc, err_) << "'" << ofof.record << "' does not exist."; - } else if (!ofof.record.HasField(ofof.field)) { - LOG(ERROR, ofof.loc, err_) << "'" << ofof.record << "' " - << "has no field named " - << "'" << ofof.field << "'"; + // Check if all sub-fields are present. + SizedType record = ofof.record; + for (const auto &field : ofof.field) { + if (!record.IsRecordTy()) { + LOG(ERROR, ofof.loc, err_) << "'" << record << "' " + << "is not of a record type."; + } else if (!bpftrace_.structs.Has(record.GetName())) { + LOG(ERROR, ofof.loc, err_) + << "'" << record.GetName() << "' does not exist."; + } else if (!record.HasField(field)) { + LOG(ERROR, ofof.loc, err_) << "'" << record.GetName() << "' " + << "has no field named " + << "'" << field << "'"; + } else { + // Get next sub-field + record = record.GetField(field).type; + } } } diff --git a/src/parser.yy b/src/parser.yy index 25afb9dbcde2..35516edbc50a 100644 --- a/src/parser.yy +++ b/src/parser.yy @@ -132,6 +132,7 @@ void yyerror(bpftrace::Driver &driver, const char *s); %type unary_op compound_op %type attach_point_def c_definitions ident keyword external_name +%type > struct_field %type attach_point %type attach_points @@ -627,9 +628,9 @@ sizeof_expr: ; offsetof_expr: - OFFSETOF "(" struct_type "," external_name ")" { $$ = driver.ctx.make_node($3, $5, @$); } -/* For example: offsetof(*curtask, comm) */ - | OFFSETOF "(" expr "," external_name ")" { $$ = driver.ctx.make_node($3, $5, @$); } + OFFSETOF "(" struct_type "," struct_field ")" { $$ = driver.ctx.make_node($3, $5, @$); } + /* For example: offsetof(*curtask, comm) */ + | OFFSETOF "(" expr "," struct_field ")" { $$ = driver.ctx.make_node($3, $5, @$); } ; int: @@ -662,6 +663,11 @@ ident: | STACK_MODE { $$ = $1; } ; +struct_field: + external_name { $$.push_back($1); } + | struct_field DOT external_name { $$ = std::move($1); $$.push_back($3); } + ; + external_name: keyword { $$ = $1; } | ident { $$ = $1; } diff --git a/tests/codegen/call_offsetof.cpp b/tests/codegen/call_offsetof.cpp index e5825b71acb7..dd8de22484e9 100644 --- a/tests/codegen/call_offsetof.cpp +++ b/tests/codegen/call_offsetof.cpp @@ -11,6 +11,13 @@ TEST(codegen, call_offsetof) NAME); } +TEST(codegen, call_offsetof_sub_field) +{ + test("struct Foo { struct Bar { int a; } d; }" + "BEGIN { @x = offsetof(struct Foo, d.a); exit(); }", + NAME); +} + } // namespace codegen } // namespace test } // namespace bpftrace diff --git a/tests/codegen/llvm/call_offsetof_sub_field.ll b/tests/codegen/llvm/call_offsetof_sub_field.ll new file mode 100644 index 000000000000..aade5bc2e20a --- /dev/null +++ b/tests/codegen/llvm/call_offsetof_sub_field.ll @@ -0,0 +1,130 @@ +; ModuleID = 'bpftrace' +source_filename = "bpftrace" +target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128" +target triple = "bpf-pc-linux" + +%"struct map_t" = type { ptr, ptr, ptr, ptr } +%"struct map_t.0" = type { ptr, ptr } +%"struct map_t.1" = type { ptr, ptr, ptr, ptr } +%exit_t = type <{ i64, i8 }> + +@LICENSE = global [4 x i8] c"GPL\00", section "license" +@AT_x = dso_local global %"struct map_t" zeroinitializer, section ".maps", !dbg !0 +@ringbuf = dso_local global %"struct map_t.0" zeroinitializer, section ".maps", !dbg !16 +@event_loss_counter = dso_local global %"struct map_t.1" zeroinitializer, section ".maps", !dbg !30 + +; Function Attrs: nounwind +declare i64 @llvm.bpf.pseudo(i64 %0, i64 %1) #0 + +define i64 @BEGIN_1(ptr %0) section "s_BEGIN_1" !dbg !45 { +entry: + %key = alloca i32, align 4 + %exit = alloca %exit_t, align 8 + %"@x_val" = alloca i64, align 8 + %"@x_key" = alloca i64, align 8 + call void @llvm.lifetime.start.p0(i64 -1, ptr %"@x_key") + store i64 0, ptr %"@x_key", align 8 + call void @llvm.lifetime.start.p0(i64 -1, ptr %"@x_val") + store i64 0, ptr %"@x_val", align 8 + %update_elem = call i64 inttoptr (i64 2 to ptr)(ptr @AT_x, ptr %"@x_key", ptr %"@x_val", i64 0) + call void @llvm.lifetime.end.p0(i64 -1, ptr %"@x_val") + call void @llvm.lifetime.end.p0(i64 -1, ptr %"@x_key") + call void @llvm.lifetime.start.p0(i64 -1, ptr %exit) + %1 = getelementptr %exit_t, ptr %exit, i64 0, i32 0 + store i64 30000, ptr %1, align 8 + %2 = getelementptr %exit_t, ptr %exit, i64 0, i32 1 + store i8 0, ptr %2, align 1 + %ringbuf_output = call i64 inttoptr (i64 130 to ptr)(ptr @ringbuf, ptr %exit, i64 9, i64 0) + %ringbuf_loss = icmp slt i64 %ringbuf_output, 0 + br i1 %ringbuf_loss, label %event_loss_counter, label %counter_merge + +event_loss_counter: ; preds = %entry + call void @llvm.lifetime.start.p0(i64 -1, ptr %key) + store i32 0, ptr %key, align 4 + %lookup_elem = call ptr inttoptr (i64 1 to ptr)(ptr @event_loss_counter, ptr %key) + %map_lookup_cond = icmp ne ptr %lookup_elem, null + br i1 %map_lookup_cond, label %lookup_success, label %lookup_failure + +counter_merge: ; preds = %lookup_merge, %entry + call void @llvm.lifetime.end.p0(i64 -1, ptr %exit) + ret i64 0 + +lookup_success: ; preds = %event_loss_counter + %3 = atomicrmw add ptr %lookup_elem, i64 1 seq_cst, align 8 + br label %lookup_merge + +lookup_failure: ; preds = %event_loss_counter + br label %lookup_merge + +lookup_merge: ; preds = %lookup_failure, %lookup_success + call void @llvm.lifetime.end.p0(i64 -1, ptr %key) + br label %counter_merge + +deadcode: ; No predecessors! + ret i64 0 +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg %0, ptr nocapture %1) #1 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg %0, ptr nocapture %1) #1 + +attributes #0 = { nounwind } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } + +!llvm.dbg.cu = !{!42} +!llvm.module.flags = !{!44} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "AT_x", linkageName: "global", scope: !2, file: !2, type: !3, isLocal: false, isDefinition: true) +!2 = !DIFile(filename: "bpftrace.bpf.o", directory: ".") +!3 = !DICompositeType(tag: DW_TAG_structure_type, scope: !2, file: !2, size: 256, elements: !4) +!4 = !{!5, !11, !12, !15} +!5 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !6, size: 64) +!6 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64) +!7 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 32, elements: !9) +!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!9 = !{!10} +!10 = !DISubrange(count: 1, lowerBound: 0) +!11 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !2, file: !2, baseType: !6, size: 64, offset: 64) +!12 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !2, file: !2, baseType: !13, size: 64, offset: 128) +!13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64) +!14 = !DIBasicType(name: "int64", size: 64, encoding: DW_ATE_signed) +!15 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !2, file: !2, baseType: !13, size: 64, offset: 192) +!16 = !DIGlobalVariableExpression(var: !17, expr: !DIExpression()) +!17 = distinct !DIGlobalVariable(name: "ringbuf", linkageName: "global", scope: !2, file: !2, type: !18, isLocal: false, isDefinition: true) +!18 = !DICompositeType(tag: DW_TAG_structure_type, scope: !2, file: !2, size: 128, elements: !19) +!19 = !{!20, !25} +!20 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !21, size: 64) +!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !22, size: 64) +!22 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 864, elements: !23) +!23 = !{!24} +!24 = !DISubrange(count: 27, lowerBound: 0) +!25 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !2, file: !2, baseType: !26, size: 64, offset: 64) +!26 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !27, size: 64) +!27 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 8388608, elements: !28) +!28 = !{!29} +!29 = !DISubrange(count: 262144, lowerBound: 0) +!30 = !DIGlobalVariableExpression(var: !31, expr: !DIExpression()) +!31 = distinct !DIGlobalVariable(name: "event_loss_counter", linkageName: "global", scope: !2, file: !2, type: !32, isLocal: false, isDefinition: true) +!32 = !DICompositeType(tag: DW_TAG_structure_type, scope: !2, file: !2, size: 256, elements: !33) +!33 = !{!34, !11, !39, !15} +!34 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !35, size: 64) +!35 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !36, size: 64) +!36 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 64, elements: !37) +!37 = !{!38} +!38 = !DISubrange(count: 2, lowerBound: 0) +!39 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !2, file: !2, baseType: !40, size: 64, offset: 128) +!40 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !41, size: 64) +!41 = !DIBasicType(name: "int32", size: 32, encoding: DW_ATE_signed) +!42 = distinct !DICompileUnit(language: DW_LANG_C, file: !2, producer: "bpftrace", isOptimized: false, runtimeVersion: 0, emissionKind: LineTablesOnly, globals: !43) +!43 = !{!0, !16, !30} +!44 = !{i32 2, !"Debug Info Version", i32 3} +!45 = distinct !DISubprogram(name: "BEGIN_1", linkageName: "BEGIN_1", scope: !2, file: !2, type: !46, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !42, retainedNodes: !50) +!46 = !DISubroutineType(types: !47) +!47 = !{!14, !48} +!48 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !49, size: 64) +!49 = !DIBasicType(name: "int8", size: 8, encoding: DW_ATE_signed) +!50 = !{!51} +!51 = !DILocalVariable(name: "ctx", arg: 1, scope: !45, file: !2, type: !48) diff --git a/tests/parser.cpp b/tests/parser.cpp index 61dc3750f98e..8f56f9942b48 100644 --- a/tests/parser.cpp +++ b/tests/parser.cpp @@ -1963,6 +1963,23 @@ TEST(Parser, offsetof_type) " offsetof: \n" " struct Foo\n" " x\n"); + test("struct Foo { struct Bar { int x; } bar; } " + "BEGIN { offsetof(struct Foo, bar.x); }", + "struct Foo { struct Bar { int x; } bar; };\n" + "\n" + "Program\n" + " BEGIN\n" + " offsetof: \n" + " struct Foo\n" + " bar\n" + " x\n"); + test_parse_failure("struct Foo { struct Bar { int x; } *bar; } " + "BEGIN { offsetof(struct Foo, bar->x); }", + R"( +stdin:1:74-79: ERROR: syntax error, unexpected ->, expecting ) or . +struct Foo { struct Bar { int x; } *bar; } BEGIN { offsetof(struct Foo, bar->x); } + ~~~~~ +)"); } TEST(Parser, offsetof_expression) diff --git a/tests/runtime/builtin b/tests/runtime/builtin index 31c4c3df6c6a..000cba6d7642 100644 --- a/tests/runtime/builtin +++ b/tests/runtime/builtin @@ -174,8 +174,8 @@ EXPECT_REGEX ^size= REQUIRES_FEATURE btf NAME offsetof -PROG struct Foo { int x; long l; char c; } BEGIN { printf("%ld\n", offsetof(struct Foo, x)); exit(); } -EXPECT_REGEX ^0$ +PROG struct Foo { int x; struct Bar { int x; } bar; } BEGIN { printf("%ld %ld\n", offsetof(struct Foo, x), offsetof(struct Foo, bar.x)); exit(); } +EXPECT_REGEX ^0 4$ NAME print args in fentry PROG fentry:vfs_open { print(args); exit(); } diff --git a/tests/semantic_analyser.cpp b/tests/semantic_analyser.cpp index 0215da894dbf..6c945e5512bc 100644 --- a/tests/semantic_analyser.cpp +++ b/tests/semantic_analyser.cpp @@ -3741,11 +3741,36 @@ TEST(semantic_analyser, call_offsetof) long l; \ } \ BEGIN { @x = offsetof(struct Ano, a); }"); + test("struct Foo { struct Bar { int a; } bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.a); }"); + test("struct Foo { struct Bar { int *a; } bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.a); }"); + test("struct Foo { struct Bar { struct { int a; } anon; } bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.anon.a); }"); + test("struct Foo { struct Bar { struct { int a; }; } bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.a); }"); + + // Error tests + + // Bad type + test("struct Foo { struct { int a; } *bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.a); }", + 1); + // Not exist (sub)field test("struct Foo { int x; long l; char c; } \ BEGIN { @x = offsetof(struct Foo, __notexistfield__); }", 1); + test("struct Foo { struct { int a; } bar; } \ + BEGIN { @x = offsetof(struct Foo, a); }", + 1); + test("struct Foo { struct { int a; } bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.__notexist_subfield__); }", + 1); + // Not exist record test("BEGIN { @x = offsetof(__passident__, x); }", 1); + test("BEGIN { @x = offsetof(__passident__, x.y.z); }", 1); test("BEGIN { @x = offsetof(struct __notexiststruct__, x); }", 1); + test("BEGIN { @x = offsetof(struct __notexiststruct__, x.y.z); }", 1); } TEST(semantic_analyser, int_ident)