diff --git a/src/ast/passes/codegen_llvm.cpp b/src/ast/passes/codegen_llvm.cpp index 6c78aa55eb04..50b67d3949f0 100644 --- a/src/ast/passes/codegen_llvm.cpp +++ b/src/ast/passes/codegen_llvm.cpp @@ -1522,8 +1522,21 @@ 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; + std::string token; + std::stringstream ss(ofof.field); + std::vector tokens; + + while (std::getline(ss, token, '.')) { + tokens.push_back(token); + } + + const SizedType *record = &ofof.record; + for (const auto& token : tokens) { + offset += record->GetField(token).offset; + record = &record->GetField(token).type; + } + expr_ = b_.getInt64(offset); } void CodegenLLVM::visit(Map &map) diff --git a/src/ast/passes/semantic_analyser.cpp b/src/ast/passes/semantic_analyser.cpp index 71ce641e4af0..7195e456062f 100644 --- a/src/ast/passes/semantic_analyser.cpp +++ b/src/ast/passes/semantic_analyser.cpp @@ -1615,17 +1615,33 @@ 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 << "'"; + std::string token; + std::stringstream ss(ofof.field); + std::vector tokens; + while (std::getline(ss, token, '.')) { + tokens.push_back(token); + } + + // Check if all sub-fields are present. + SizedType record = ofof.record; + for (const auto& token : tokens) { + 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(token)) { + LOG(ERROR, ofof.loc, err_) << "'" << record.GetName() << "' " + << "has no field named " + << "'" << token << "'"; + } else { + // Get next sub-field + record = record.GetField(token).type; + } } } diff --git a/src/parser.yy b/src/parser.yy index 25afb9dbcde2..854853312833 100644 --- a/src/parser.yy +++ b/src/parser.yy @@ -131,7 +131,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 attach_point_def c_definitions ident keyword external_name struct_field %type attach_point %type attach_points @@ -626,10 +626,15 @@ sizeof_expr: | SIZEOF "(" expr ")" { $$ = driver.ctx.make_node($3, @$); } ; +struct_field: + external_name { $$ = $1; } + | struct_field DOT external_name { $$ = $1 + "." + $3; } + ; + 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: 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..25c288435c75 --- /dev/null +++ b/tests/codegen/llvm/call_offsetof_sub_field.ll @@ -0,0 +1,125 @@ +; 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 { i8*, i8*, i8*, i8* } +%"struct map_t.0" = type { i8*, i8* } +%"struct map_t.1" = type { i8*, i8*, i8*, i8* } +%exit_t = type <{ i64, i8 }> + +@LICENSE = local_unnamed_addr 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 + +define i64 @BEGIN_1(i8* nocapture readnone %0) local_unnamed_addr 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 + %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 0, i64* %"@x_val", align 8 + %update_elem = call i64 inttoptr (i64 2 to i64 (%"struct map_t"*, i64*, i64*, i64)*)(%"struct map_t"* nonnull @AT_x, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) + %3 = bitcast %exit_t* %exit to i8* + call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) + %4 = getelementptr inbounds %exit_t, %exit_t* %exit, i64 0, i32 0 + store i64 30000, i64* %4, align 8 + %5 = getelementptr inbounds %exit_t, %exit_t* %exit, i64 0, i32 1 + store i8 0, i8* %5, align 8 + %ringbuf_output = call i64 inttoptr (i64 130 to i64 (%"struct map_t.0"*, %exit_t*, i64, i64)*)(%"struct map_t.0"* nonnull @ringbuf, %exit_t* nonnull %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 + %6 = bitcast i32* %key to i8* + call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) + store i32 0, i32* %key, align 4 + %lookup_elem = call i8* inttoptr (i64 1 to i8* (%"struct map_t.1"*, i32*)*)(%"struct map_t.1"* nonnull @event_loss_counter, i32* nonnull %key) + %map_lookup_cond.not = icmp eq i8* %lookup_elem, null + br i1 %map_lookup_cond.not, label %lookup_merge, label %lookup_success + +counter_merge: ; preds = %lookup_merge, %entry + call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) + ret i64 0 + +lookup_success: ; preds = %event_loss_counter + %7 = bitcast i8* %lookup_elem to i64* + %8 = atomicrmw add i64* %7, i64 1 seq_cst, align 8 + br label %lookup_merge + +lookup_merge: ; preds = %event_loss_counter, %lookup_success + call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) + br label %counter_merge +} + +; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn +declare void @llvm.lifetime.start.p0i8(i64 immarg %0, i8* nocapture %1) #0 + +; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn +declare void @llvm.lifetime.end.p0i8(i64 immarg %0, i8* nocapture %1) #0 + +attributes #0 = { argmemonly mustprogress nofree nosync nounwind willreturn } + +!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/semantic_analyser.cpp b/tests/semantic_analyser.cpp index 0215da894dbf..f47e3c983033 100644 --- a/tests/semantic_analyser.cpp +++ b/tests/semantic_analyser.cpp @@ -3741,9 +3741,16 @@ 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 { struct { int a; } anon; } bar; } \ + BEGIN { @x = offsetof(struct Foo, bar.anon.a); }"); 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, bar.__notexist_subfield__); }", + 1); test("BEGIN { @x = offsetof(__passident__, x); }", 1); test("BEGIN { @x = offsetof(struct __notexiststruct__, x); }", 1); }