Skip to content

Commit

Permalink
asm: moved Instruction.Symbol and Instruction.Reference into metadata
Browse files Browse the repository at this point in the history
This commit adds per-instruction metadata, which is sparse in nature,
meaning that most instructions don't have metadata but some do. Metadata
is linked to an instruction via a pointer which is nil by default, thus
if an instruction has no metadata, at most one pointer worth of space is
unused which is desirable since we expect the size of the metadata
struct to grow in future commits.

The metadata struct is copy-on-write, so multiple instructions can share
the same metadata object after being copied, but as soon as a
modification on one of the instruction's metadata is performed, the
metadata object is copied so writes to a copied instruction doesn't
effect the original instruction.

This commit also deprecates `Instruction.Sym` in favor of
`Instruction.WithSymbol`. This follows the convention of prefixing
func names with `With...` if they have value receivers to make it clear
to callers that this doesn't modify the instruction it is being called
on.
  • Loading branch information
Dylan Reimerink committed Feb 22, 2022
1 parent 299accb commit 7bfde55
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 96 deletions.
18 changes: 13 additions & 5 deletions asm/dsl_test.go
Expand Up @@ -25,21 +25,29 @@ func TestDSL(t *testing.T) {
OpCode: 0x04, Dst: R1, Constant: 22,
}},
{"JSGT.Imm", JSGT.Imm(R1, 4, "foo"), Instruction{
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1, Reference: "foo",
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1, metadata: &metadata{
reference: "foo",
},
}},
{"JSGT.Imm32", JSGT.Imm32(R1, -2, "foo"), Instruction{
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1, Reference: "foo",
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1, metadata: &metadata{
reference: "foo",
},
}},
{"JSLT.Reg", JSLT.Reg(R1, R2, "foo"), Instruction{
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1, Reference: "foo",
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1, metadata: &metadata{
reference: "foo",
},
}},
{"JSLT.Reg32", JSLT.Reg32(R1, R3, "foo"), Instruction{
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1, Reference: "foo",
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1, metadata: &metadata{
reference: "foo",
},
}},
}

for _, tc := range testcases {
if tc.have != tc.want {
if !tc.have.equal(tc.want) {
t.Errorf("%s: have %v, want %v", tc.name, tc.have, tc.want)
}
}
Expand Down
136 changes: 107 additions & 29 deletions asm/instruction.go
Expand Up @@ -35,17 +35,36 @@ type Instruction struct {
Offset int16
Constant int64

// Reference denotes a reference (e.g. a jump) to another symbol.
Reference string
// Metadata contains optional metadata about this instruction
metadata *metadata
}

// WithSymbol adds symbol metadata to the instruction, which other instructions can use as Reference.
func (ins Instruction) WithSymbol(name string) Instruction {
if (ins.metadata != nil && ins.metadata.symbol == name) ||
(ins.metadata == nil && name == "") {
return ins
}

// Symbol denotes an instruction at the start of a function body.
Symbol string
ins.metadata = ins.metadata.copy()
ins.metadata.symbol = name
return ins
}

// Sym creates a symbol.
//
// Deprecated: use WithSymbol instread
func (ins Instruction) Sym(name string) Instruction {
ins.Symbol = name
return ins
return ins.WithSymbol(name)
}

// Symbol returns the name of the function that starts at ins.
func (ins Instruction) Symbol() string {
if ins.metadata == nil {
return ""
}

return ins.metadata.symbol
}

// Unmarshal decodes a BPF instruction.
Expand Down Expand Up @@ -299,16 +318,75 @@ func (ins Instruction) Format(f fmt.State, c rune) {
}

ref:
if ins.Reference != "" {
fmt.Fprintf(f, " <%s>", ins.Reference)
if ins.Reference() != "" {
fmt.Fprintf(f, " <%s>", ins.Reference())
}
}

func (ins Instruction) equal(other Instruction) bool {
return ins.OpCode == other.OpCode &&
ins.Dst == other.Dst &&
ins.Src == other.Src &&
ins.Offset == other.Offset &&
ins.Constant == other.Constant
}

// Size returns the amount of bytes ins would occupy in binary form.
func (ins Instruction) Size() uint64 {
return uint64(InstructionSize * ins.OpCode.rawInstructions())
}

// SetReference makes ins reference another Instruction by name within the same
func (ins *Instruction) SetReference(ref string) {
if (ins.metadata != nil && ins.metadata.reference == ref) ||
(ins.metadata == nil && ref == "") {
return
}

ins.metadata = ins.metadata.copy()
ins.metadata.reference = ref
}

// Reference returns a reference (e.g. a jump) to another symbol.
func (ins Instruction) Reference() string {
if ins.metadata == nil {
return ""
}

return ins.metadata.reference
}

// FDer isn't actually used as a meaningful interface, rater it is used because we can't directly use types from the
// ebpf package since this would cause in import loop.
type FDer interface {
FD() int
}

// metadata holds metadata about an Instruction.
type metadata struct {
// reference denotes a reference (e.g. a jump) to another symbol.
reference string
// symbol denotes an instruction at the start of a function body.
symbol string
}

// copy returns a copy of metadata.
// Always returns a valid pointer, even when called on a nil metadata.
func (m *metadata) copy() *metadata {
var copy metadata
if m != nil {
copy = *m
}
return &copy
}

// SimpleContext can be used to set a string as context of an Instruction.
type SimpleContext string

func (sc SimpleContext) String() string {
return string(sc)
}

// Instructions is an eBPF program.
type Instructions []Instruction

Expand Down Expand Up @@ -342,7 +420,7 @@ func (insns Instructions) Name() string {
if len(insns) == 0 {
return ""
}
return insns[0].Symbol
return insns[0].Symbol()
}

func (insns Instructions) String() string {
Expand All @@ -369,7 +447,7 @@ func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
found := false
for i := range insns {
ins := &insns[i]
if ins.Reference != symbol {
if ins.Reference() != symbol {
continue
}

Expand All @@ -393,15 +471,15 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
offsets := make(map[string]int)

for i, ins := range insns {
if ins.Symbol == "" {
if ins.Symbol() == "" {
continue
}

if _, ok := offsets[ins.Symbol]; ok {
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
if _, ok := offsets[ins.Symbol()]; ok {
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol())
}

offsets[ins.Symbol] = i
offsets[ins.Symbol()] = i
}

return offsets, nil
Expand All @@ -418,15 +496,15 @@ func (insns Instructions) FunctionReferences() map[string]bool {
continue
}

if ins.Reference == "" {
if ins.Reference() == "" {
continue
}

if !ins.IsFunctionReference() {
continue
}

calls[ins.Reference] = true
calls[ins.Reference()] = true
}

return calls
Expand All @@ -438,11 +516,11 @@ func (insns Instructions) ReferenceOffsets() map[string][]int {
offsets := make(map[string][]int)

for i, ins := range insns {
if ins.Reference == "" {
if ins.Reference() == "" {
continue
}

offsets[ins.Reference] = append(offsets[ins.Reference], i)
offsets[ins.Reference()] = append(offsets[ins.Reference()], i)
}

return offsets
Expand Down Expand Up @@ -493,8 +571,8 @@ func (insns Instructions) Format(f fmt.State, c rune) {

iter := insns.Iterate()
for iter.Next() {
if iter.Ins.Symbol != "" {
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol)
if iter.Ins.Symbol() != "" {
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol())
}
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
}
Expand Down Expand Up @@ -552,15 +630,15 @@ func (insns Instructions) resolveFunctionReferences() error {
for iter.Next() {
ins := iter.Ins

if ins.Symbol == "" {
if ins.Symbol() == "" {
continue
}

if _, ok := symbolOffsets[ins.Symbol]; ok {
return fmt.Errorf("duplicate symbol %s", ins.Symbol)
if _, ok := symbolOffsets[ins.Symbol()]; ok {
return fmt.Errorf("duplicate symbol %s", ins.Symbol())
}

symbolOffsets[ins.Symbol] = iter.Offset
symbolOffsets[ins.Symbol()] = iter.Offset
}

// Find all instructions tagged as references to other symbols.
Expand All @@ -572,23 +650,23 @@ func (insns Instructions) resolveFunctionReferences() error {
offset := iter.Offset
ins := iter.Ins

if ins.Reference == "" {
if ins.Reference() == "" {
continue
}

switch {
case ins.IsFunctionReference() && ins.Constant == -1:
symOffset, ok := symbolOffsets[ins.Reference]
symOffset, ok := symbolOffsets[ins.Reference()]
if !ok {
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference, ErrUnsatisfiedProgramReference)
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
}

ins.Constant = int64(symOffset - offset - 1)

case ins.OpCode.Class().IsJump() && ins.Offset == -1:
symOffset, ok := symbolOffsets[ins.Reference]
symOffset, ok := symbolOffsets[ins.Reference()]
if !ok {
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference, ErrUnsatisfiedProgramReference)
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
}

ins.Offset = int16(symOffset - offset - 1)
Expand Down
4 changes: 2 additions & 2 deletions asm/instruction_test.go
Expand Up @@ -154,7 +154,7 @@ func TestInstructionsRewriteMapPtr(t *testing.T) {
LoadMapPtr(R1, 0),
Return(),
}
insns[0].Reference = "good"
insns[0].SetReference("good")

if err := insns.RewriteMapPtr("good", 1); err != nil {
t.Fatal(err)
Expand All @@ -181,7 +181,7 @@ func TestInstructionsRewriteMapPtr(t *testing.T) {
// program is stringified.
func ExampleInstructions_Format() {
insns := Instructions{
FnMapLookupElem.Call().Sym("my_func"),
FnMapLookupElem.Call().WithSymbol("my_func"),
LoadImm(R0, 42, DWord),
Return(),
}
Expand Down

0 comments on commit 7bfde55

Please sign in to comment.