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 8c09e60
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 21 additions & 3 deletions man/adoc/bpftrace.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/ast/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> &field,
location loc)
: Expression(loc), record(record), field(field)
{
}

Offsetof::Offsetof(Expression *expr, std::string &field, location loc)
Offsetof::Offsetof(Expression *expr,
std::vector<std::string> &field,
location loc)
: Expression(loc), expr(expr), field(field)
{
}
Expand Down
6 changes: 3 additions & 3 deletions src/ast/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> &field, location loc);
Offsetof(Expression *expr, std::vector<std::string> &field, location loc);

SizedType record;
Expression *expr = nullptr;
std::string field;
std::vector<std::string> field;

private:
Offsetof(const Offsetof &other) = default;
Expand Down
9 changes: 7 additions & 2 deletions src/ast/passes/codegen_llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/ast/passes/printer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
}

Expand Down
27 changes: 18 additions & 9 deletions src/ast/passes/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
12 changes: 9 additions & 3 deletions src/parser.yy
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,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::vector<std::string>> struct_field

%type <ast::AttachPoint *> attach_point
%type <ast::AttachPointList> attach_points
Expand Down Expand Up @@ -627,9 +628,9 @@ sizeof_expr:
;

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 Expand Up @@ -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; }
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
130 changes: 130 additions & 0 deletions tests/codegen/llvm/call_offsetof_sub_field.ll
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 17 additions & 0 deletions tests/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions tests/runtime/builtin
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down
Loading

0 comments on commit 8c09e60

Please sign in to comment.