Skip to content

Commit

Permalink
offsetof: Support sub-field
Browse files Browse the repository at this point in the history
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: bpftrace#2579 [1] merged
Link: bpftrace#2216 [2]
Signed-off-by: Rong Tao <rongtao@cestc.cn>
  • Loading branch information
Rtoax committed Feb 1, 2025
1 parent 04c87f6 commit 036a6b0
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 15 deletions.
17 changes: 15 additions & 2 deletions src/ast/passes/codegen_llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> 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)
Expand Down
34 changes: 25 additions & 9 deletions src/ast/passes/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> 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;
}
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/parser.yy
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ void yyerror(bpftrace::Driver &driver, const char *s);


%type <ast::Operator> unary_op compound_op
%type <std::string> attach_point_def c_definitions ident keyword external_name
%type <std::string> attach_point_def c_definitions ident keyword external_name struct_field

%type <ast::AttachPoint *> attach_point
%type <ast::AttachPointList> attach_points
Expand Down Expand Up @@ -626,10 +626,15 @@ sizeof_expr:
| SIZEOF "(" expr ")" { $$ = driver.ctx.make_node<ast::Sizeof>($3, @$); }
;

struct_field:
external_name { $$ = $1; }
| struct_field DOT external_name { $$ = $1 + "." + $3; }
;

offsetof_expr:
OFFSETOF "(" struct_type "," external_name ")" { $$ = driver.ctx.make_node<ast::Offsetof>($3, $5, @$); }
/* For example: offsetof(*curtask, comm) */
| OFFSETOF "(" expr "," external_name ")" { $$ = driver.ctx.make_node<ast::Offsetof>($3, $5, @$); }
OFFSETOF "(" struct_type "," struct_field ")" { $$ = driver.ctx.make_node<ast::Offsetof>($3, $5, @$); }
/* For example: offsetof(*curtask, comm) */
| OFFSETOF "(" expr "," struct_field ")" { $$ = driver.ctx.make_node<ast::Offsetof>($3, $5, @$); }
;

int:
Expand Down
7 changes: 7 additions & 0 deletions tests/codegen/call_offsetof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
125 changes: 125 additions & 0 deletions tests/codegen/llvm/call_offsetof_sub_field.ll
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions tests/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1963,6 +1963,15 @@ 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.x\n");
}

TEST(Parser, offsetof_expression)
Expand Down
7 changes: 7 additions & 0 deletions tests/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down

0 comments on commit 036a6b0

Please sign in to comment.