Skip to content

Commit

Permalink
bpflbr: Dump jited info of prog
Browse files Browse the repository at this point in the history
Signed-off-by: Leon Hwang <[email protected]>
  • Loading branch information
Asphaltt committed Dec 5, 2024
1 parent 1880269 commit c03a509
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright 2024 Leon Hwang.
# SPDX-License-Identifier: Apache-2.0

GOBUILD := go build -v -trimpath
GOBUILD_CGO_LDFLAGS := CGO_LDFLAGS='-O2 -g -lcapstone -static'

.DEFAULT_GOAL := build

build:
$(GOBUILD_CGO_LDFLAGS) $(GOBUILD)
15 changes: 15 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module github.com/Asphaltt/bpflbr

go 1.22.4

require (
github.com/cilium/ebpf v0.16.0
github.com/knightsc/gapstone v4.0.1+incompatible
github.com/spf13/pflag v1.0.5
)

require golang.org/x/sys v0.20.0 // indirect

replace github.com/knightsc/gapstone v4.0.1+incompatible => github.com/Asphaltt/gapstone v0.0.0-20241029140935-c5412a26abf7

replace github.com/cilium/ebpf v0.16.0 => github.com/Asphaltt/ebpf v0.0.0-20241102052356-d5a4c9e8b9c2
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
github.com/Asphaltt/ebpf v0.0.0-20241102052356-d5a4c9e8b9c2 h1:XUIgMZYZAQfAZ72Q2dXcKjA7smub7lu1h6/GNsn+alc=
github.com/Asphaltt/ebpf v0.0.0-20241102052356-d5a4c9e8b9c2/go.mod h1:g+Zxp5bVKYJy9/njLFYE+mNTcw1P8TnE59/2Qq2Pr3I=
github.com/Asphaltt/gapstone v0.0.0-20241029140935-c5412a26abf7 h1:z1Ohf61MSfgVMxAs2Z4nWZ4p+a12K/u41JJurdIxdBU=
github.com/Asphaltt/gapstone v0.0.0-20241029140935-c5412a26abf7/go.mod h1:1K5hEzsMBLTPdRJKEHqBFJ8Zt2VRqDhomcQ11KH0WW4=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
53 changes: 53 additions & 0 deletions internal/assert/assert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2024 Leon Hwang.
// SPDX-License-Identifier: Apache-2.0

package assert

import (
"errors"
"log"

"github.com/cilium/ebpf"
)

func NoErr(err error, msg string, args ...any) {
if err != nil {
args = append(args, err)
log.Fatalf(msg, args...)
}
}

func NoVerifierErr(err error, msg string, args ...any) {
if err != nil {
var verr *ebpf.VerifierError
if errors.As(err, &verr) {
log.Printf("Verifier log:\n%+v", verr)
}
args = append(args, err)
log.Fatalf(msg, args...)
}
}

func Equal[T comparable](a, b T, msg string, args ...any) {
if a != b {
log.Fatalf(msg, args...)
}
}

func True(cond bool, msg string, args ...any) {
if !cond {
log.Fatalf(msg, args...)
}
}

func SliceNotEmpty[T any](s []T, msg string, args ...any) {
if len(s) == 0 {
log.Fatalf(msg, args...)
}
}

func SliceLen[T any](s []T, l int, msg string, args ...any) {
if len(s) != l {
log.Fatalf(msg, args...)
}
}
102 changes: 102 additions & 0 deletions internal/bpflbr/dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2024 Leon Hwang.
// SPDX-License-Identifier: Apache-2.0

package bpflbr

import (
"fmt"
"os"
"strings"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/knightsc/gapstone"

"github.com/Asphaltt/bpflbr/internal/assert"
)

func DumpProg(pf ProgFlag) {
prog, err := ebpf.NewProgramFromID(ebpf.ProgramID(pf.progID))
assert.NoErr(err, "Failed to load prog %d: %v", pf.progID)
defer prog.Close()

info, err := prog.Info()
assert.NoErr(err, "Failed to get prog info: %v", err)

jitedInsns, _ := info.JitedInsns()
jitedKsyms, _ := info.KsymAddrs()
jitedFuncLens, _ := info.JitedFuncLens()
jitedLineInfos, _ := info.JitedLineInfos()
assert.SliceNotEmpty(jitedInsns, "No jited insns")
assert.Equal(len(jitedFuncLens), len(jitedKsyms), "Func len number %d != ksym number %d", len(jitedFuncLens), len(jitedKsyms))

// jitedFuncLens are the insns length of each function.
// Then, jitedInsns can be split into len(jitedFuncLens) functions with each function having jitedFuncLens[i] insns.

li, err := info.LineInfos()
assert.NoErr(err, "Failed to get line infos: %v")

lines := li.Lines()
assert.Equal(len(lines), len(jitedLineInfos), "Line info mismatch: %d != %d (jited)", len(lines), len(jitedLineInfos))

// Each jitedLineInfo is corresponding to a lineInfo.

// jitedLineInfos are kernel addresses.
// jitedKsyms are ksyms for funcs.
//
// ksym addr + insn offset = jited line info addr.
// jited line info addr => line info.

jited2LineInfos := make(map[uint64]btf.LineInfo, len(jitedLineInfos))
for i, kaddr := range jitedLineInfos {
jited2LineInfos[kaddr] = lines[i]
}

engine, err := gapstone.New(int(gapstone.CS_ARCH_X86), int(gapstone.CS_MODE_64))
assert.NoErr(err, "Failed to create engine: %v")
defer engine.Close()

intelSyntax := os.Getenv("BPFLBR_DUMP_INTEL_SYNTAX") == "1"
if !intelSyntax {
err = engine.SetOption(uint(gapstone.CS_OPT_SYNTAX), uint(gapstone.CS_OPT_SYNTAX_ATT))
assert.NoErr(err, "Failed to set syntax: %v")
}

var sb strings.Builder

insns := jitedInsns
for i, funcLen := range jitedFuncLens {
ksym := uint64(jitedKsyms[i])
fnInsns := insns[:funcLen]
pc := uint64(0)

for len(fnInsns) > 0 {
kaddr := ksym + pc
if li, ok := jited2LineInfos[kaddr]; ok {
fmt.Fprintf(&sb, "; %s:%d:%d %s\n",
li.Line.FileName(), li.Line.LineNumber(), li.Line.LineColumn(),
strings.TrimSpace(li.Line.Line()))
}

inst, err := engine.Disasm(fnInsns, kaddr, 1)
assert.NoErr(err, "Failed to disasm instruction: %v", err)

var opcodes []string
for _, insn := range inst[0].Bytes {
opcodes = append(opcodes, fmt.Sprintf("%02x", insn))
}
opcode := strings.Join(opcodes, " ")
fmt.Fprintf(&sb, "%#x: %-19s\t%s\t%s\n", kaddr, opcode, inst[0].Mnemonic, inst[0].OpStr)

insnSize := uint64(inst[0].Size)
pc += insnSize
fnInsns = fnInsns[insnSize:]
}

fmt.Fprintln(&sb)

insns = insns[funcLen:]
}

fmt.Print(sb.String())
}
63 changes: 63 additions & 0 deletions internal/bpflbr/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2024 Leon Hwang.
// SPDX-License-Identifier: Apache-2.0

package bpflbr

import (
"fmt"
"os"
"strconv"
"strings"

flag "github.com/spf13/pflag"
)

type ProgFlag struct {
progID uint32
funcName string
}

func parseProgsFlag(progs []string) ([]ProgFlag, error) {
flags := make([]ProgFlag, 0, len(progs))
for _, p := range progs {
id, funcName, ok := strings.Cut(p, ":")

progID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse progID %s from %s: %v", id, p, err)
}

var pf ProgFlag
pf.progID = uint32(progID)
if ok {
pf.funcName = funcName
}
flags = append(flags, pf)
}

return flags, nil
}

type Flags struct {
progs []string

dumpProg bool
}

func ParseFlags() (*Flags, error) {
var flags Flags

f := flag.NewFlagSet("bpflbr", flag.ExitOnError)
f.StringSliceVarP(&flags.progs, "prog", "p", nil, "bpf prog info for bpflbr in format PROG[,PROG,..], PROG: <prog ID>[:<prog function name>]; all bpf progs will be traced by default")
f.BoolVar(&flags.dumpProg, "dump-jited", false, "dump native insn info of bpf prog, the one prog ID must be provided by --prog (its function name will be ignored)")

return &flags, f.Parse(os.Args)
}

func (f *Flags) ParseProgs() ([]ProgFlag, error) {
return parseProgsFlag(f.progs)
}

func (f *Flags) DumpProg() bool {
return f.dumpProg
}
28 changes: 28 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 Leon Hwang.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"github.com/Asphaltt/bpflbr/internal/assert"
"github.com/Asphaltt/bpflbr/internal/bpflbr"
)

func main() {
flags, err := bpflbr.ParseFlags()
assert.NoErr(err, "Failed to parse flags: %v")

progs, err := flags.ParseProgs()
assert.NoErr(err, "Failed to parse bpf prog infos: %v")

if flags.DumpProg() {
dumpProg(progs)
}
}

func dumpProg(progs []bpflbr.ProgFlag) {
assert.SliceLen(progs, 1, "Only one prog ID is allowed for --dump-jited")

pf := progs[0]
bpflbr.DumpProg(pf)
}

0 comments on commit c03a509

Please sign in to comment.