mirror of
https://github.com/go-delve/delve.git
synced 2026-05-06 04:36:16 +02:00
pkg/proc: extend feature to print procedure parameters in trace on eBPF backend (#4305)
* Extend feature to print procedure parameters in trace on eBPF backend Fixes #4266 * address review comments * ran go fmt
This commit is contained in:
parent
08ef5f3d75
commit
29aa227c5c
@ -56,6 +56,41 @@ func assertNoError(err error, t testing.TB, s string) {
|
||||
}
|
||||
}
|
||||
|
||||
// preCondEBPFTest skips the test if eBPF testing requirements are not met.
|
||||
// eBPF tests require: Linux/amd64, Go 1.16+, root privileges, and BTF kernel support.
|
||||
func preCondEBPFTest(t *testing.T) {
|
||||
t.Helper()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
}
|
||||
|
||||
// filterProcessExitLines removes "Process <pid> has exited with status" lines from output.
|
||||
// This is useful when comparing trace outputs from different backends that use different PIDs.
|
||||
func filterProcessExitLines(output []byte) []byte {
|
||||
lines := bytes.Split(output, []byte("\n"))
|
||||
var filtered [][]byte
|
||||
for _, line := range lines {
|
||||
if !bytes.HasPrefix(line, []byte("Process ")) || !bytes.Contains(line, []byte("has exited with status")) {
|
||||
filtered = append(filtered, line)
|
||||
}
|
||||
}
|
||||
return bytes.Join(filtered, []byte("\n"))
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
const listenAddr = "127.0.0.1:40573"
|
||||
@ -1172,22 +1207,7 @@ func TestTracePrintStack(t *testing.T) {
|
||||
|
||||
func TestTraceEBPF(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
|
||||
@ -1212,22 +1232,7 @@ func TestTraceEBPF(t *testing.T) {
|
||||
|
||||
func TestTraceEBPF2(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
|
||||
@ -1273,22 +1278,7 @@ func TestTraceEBPF2(t *testing.T) {
|
||||
|
||||
func TestTraceEBPF3(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
|
||||
@ -1332,22 +1322,7 @@ func TestTraceEBPF3(t *testing.T) {
|
||||
|
||||
func TestTraceEBPF4(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
|
||||
@ -1391,22 +1366,7 @@ func TestTraceEBPF4(t *testing.T) {
|
||||
|
||||
func TestTraceBackendParity(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
fixtures := protest.FindFixturesDir()
|
||||
@ -1444,17 +1404,6 @@ func TestTraceBackendParity(t *testing.T) {
|
||||
}
|
||||
|
||||
// Filter out process exit messages which contain different PIDs
|
||||
filterProcessExitLines := func(output []byte) []byte {
|
||||
lines := bytes.Split(output, []byte("\n"))
|
||||
var filtered [][]byte
|
||||
for _, line := range lines {
|
||||
if !bytes.HasPrefix(line, []byte("Process ")) || !bytes.Contains(line, []byte("has exited with status")) {
|
||||
filtered = append(filtered, line)
|
||||
}
|
||||
}
|
||||
return bytes.Join(filtered, []byte("\n"))
|
||||
}
|
||||
|
||||
ptraceFiltered := filterProcessExitLines(ptraceOutput)
|
||||
ebpfFiltered := filterProcessExitLines(ebpfOutput)
|
||||
|
||||
@ -1466,22 +1415,7 @@ func TestTraceBackendParity(t *testing.T) {
|
||||
|
||||
func TestTraceEBPFTypes(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Getenv("CI") == "true" {
|
||||
t.Skip("cannot run test in CI, requires kernel compiled with btf support")
|
||||
}
|
||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
||||
t.Skip("not implemented on non linux/amd64 systems")
|
||||
}
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
t.Skip("requires at least Go 1.16 to run test")
|
||||
}
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if usr.Uid != "0" {
|
||||
t.Skip("test must be run as root")
|
||||
}
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
fixtures := protest.FindFixturesDir()
|
||||
@ -1536,6 +1470,229 @@ func TestTraceEBPFTypes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTraceVerbosityBackendParityLevel0(t *testing.T) {
|
||||
t.Parallel()
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
fixtures := protest.FindFixturesDir()
|
||||
fixturePath := filepath.Join(fixtures, "traceverb.go")
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Run trace with ptrace backend at verbosity level 0
|
||||
// Test primitives from traceverb.go
|
||||
ptraceCmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpDir, "__debug_ptrace"),
|
||||
"--verbose", "0", fixturePath, "main.testPrimitives")
|
||||
ptraceStderr, err := ptraceCmd.StderrPipe()
|
||||
assertNoError(err, t, "ptrace stderr pipe")
|
||||
defer ptraceStderr.Close()
|
||||
|
||||
assertNoError(ptraceCmd.Start(), t, "running ptrace trace")
|
||||
ptraceOutput, err := io.ReadAll(ptraceStderr)
|
||||
assertNoError(err, t, "reading ptrace output")
|
||||
ptraceCmd.Wait()
|
||||
|
||||
if len(ptraceOutput) == 0 {
|
||||
t.Fatal("ptrace backend produced no output")
|
||||
}
|
||||
|
||||
// Run trace with eBPF backend at verbosity level 0
|
||||
// Test primitives from traceverb.go
|
||||
ebpfCmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpDir, "__debug_ebpf"),
|
||||
"--verbose", "0", fixturePath, "main.testPrimitives")
|
||||
ebpfStderr, err := ebpfCmd.StderrPipe()
|
||||
assertNoError(err, t, "ebpf stderr pipe")
|
||||
defer ebpfStderr.Close()
|
||||
|
||||
assertNoError(ebpfCmd.Start(), t, "running ebpf trace")
|
||||
ebpfOutput, err := io.ReadAll(ebpfStderr)
|
||||
assertNoError(err, t, "reading ebpf output")
|
||||
ebpfCmd.Wait()
|
||||
|
||||
if len(ebpfOutput) == 0 {
|
||||
t.Fatal("ebpf backend produced no output")
|
||||
}
|
||||
|
||||
// Filter out process exit messages which contain different PIDs
|
||||
ptraceFiltered := filterProcessExitLines(ptraceOutput)
|
||||
ebpfFiltered := filterProcessExitLines(ebpfOutput)
|
||||
|
||||
// Compare outputs byte-for-byte
|
||||
if !bytes.Equal(ptraceFiltered, ebpfFiltered) {
|
||||
t.Fatalf("Output mismatch between ptrace and ebpf backends at verbosity level 0:\n\nPtrace output:\n%s\n\neBPF output:\n%s",
|
||||
string(ptraceOutput), string(ebpfOutput))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceVerbosityBackendParityLevel1(t *testing.T) {
|
||||
t.Parallel()
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
fixtures := protest.FindFixturesDir()
|
||||
fixturePath := filepath.Join(fixtures, "ebpf_trace_types.go")
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Run trace with ptrace backend at verbosity level 1
|
||||
// Test small integer types from ebpf_trace_types.go (newly supported in eBPF)
|
||||
ptraceCmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpDir, "__debug_ptrace"),
|
||||
"--verbose", "1", fixturePath, "main.tracedSmallInts")
|
||||
ptraceStderr, err := ptraceCmd.StderrPipe()
|
||||
assertNoError(err, t, "ptrace stderr pipe")
|
||||
defer ptraceStderr.Close()
|
||||
|
||||
assertNoError(ptraceCmd.Start(), t, "running ptrace trace")
|
||||
ptraceOutput, err := io.ReadAll(ptraceStderr)
|
||||
assertNoError(err, t, "reading ptrace output")
|
||||
ptraceCmd.Wait()
|
||||
|
||||
if len(ptraceOutput) == 0 {
|
||||
t.Fatal("ptrace backend produced no output")
|
||||
}
|
||||
|
||||
// Run trace with eBPF backend at verbosity level 1
|
||||
// Test small integer types from ebpf_trace_types.go (newly supported in eBPF)
|
||||
ebpfCmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpDir, "__debug_ebpf"),
|
||||
"--verbose", "1", fixturePath, "main.tracedSmallInts")
|
||||
ebpfStderr, err := ebpfCmd.StderrPipe()
|
||||
assertNoError(err, t, "ebpf stderr pipe")
|
||||
defer ebpfStderr.Close()
|
||||
|
||||
assertNoError(ebpfCmd.Start(), t, "running ebpf trace")
|
||||
ebpfOutput, err := io.ReadAll(ebpfStderr)
|
||||
assertNoError(err, t, "reading ebpf output")
|
||||
ebpfCmd.Wait()
|
||||
|
||||
if len(ebpfOutput) == 0 {
|
||||
t.Fatal("ebpf backend produced no output")
|
||||
}
|
||||
|
||||
// Filter out process exit messages
|
||||
ptraceFiltered := filterProcessExitLines(ptraceOutput)
|
||||
ebpfFiltered := filterProcessExitLines(ebpfOutput)
|
||||
|
||||
// Compare outputs byte-for-byte
|
||||
if !bytes.Equal(ptraceFiltered, ebpfFiltered) {
|
||||
t.Fatalf("Output mismatch between ptrace and ebpf backends at verbosity level 1:\n\nPtrace output:\n%s\n\neBPF output:\n%s",
|
||||
string(ptraceOutput), string(ebpfOutput))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceVerbosityBackendParityLevel2(t *testing.T) {
|
||||
t.Parallel()
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
fixtures := protest.FindFixturesDir()
|
||||
fixturePath := filepath.Join(fixtures, "ebpf_trace_types.go")
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Run trace with ptrace backend at verbosity level 2
|
||||
// Test pointer types from ebpf_trace_types.go (newly supported in eBPF)
|
||||
ptraceCmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpDir, "__debug_ptrace"),
|
||||
"--verbose", "2", fixturePath, "main.tracedPointer")
|
||||
ptraceStderr, err := ptraceCmd.StderrPipe()
|
||||
assertNoError(err, t, "ptrace stderr pipe")
|
||||
defer ptraceStderr.Close()
|
||||
|
||||
assertNoError(ptraceCmd.Start(), t, "running ptrace trace")
|
||||
ptraceOutput, err := io.ReadAll(ptraceStderr)
|
||||
assertNoError(err, t, "reading ptrace output")
|
||||
ptraceCmd.Wait()
|
||||
|
||||
if len(ptraceOutput) == 0 {
|
||||
t.Fatal("ptrace backend produced no output")
|
||||
}
|
||||
|
||||
// Run trace with eBPF backend at verbosity level 2
|
||||
// Test pointer types from ebpf_trace_types.go (newly supported in eBPF)
|
||||
ebpfCmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpDir, "__debug_ebpf"),
|
||||
"--verbose", "2", fixturePath, "main.tracedPointer")
|
||||
ebpfStderr, err := ebpfCmd.StderrPipe()
|
||||
assertNoError(err, t, "ebpf stderr pipe")
|
||||
defer ebpfStderr.Close()
|
||||
|
||||
assertNoError(ebpfCmd.Start(), t, "running ebpf trace")
|
||||
ebpfOutput, err := io.ReadAll(ebpfStderr)
|
||||
assertNoError(err, t, "reading ebpf output")
|
||||
ebpfCmd.Wait()
|
||||
|
||||
if len(ebpfOutput) == 0 {
|
||||
t.Fatal("ebpf backend produced no output")
|
||||
}
|
||||
|
||||
// Filter out process exit messages
|
||||
ptraceFiltered := filterProcessExitLines(ptraceOutput)
|
||||
ebpfFiltered := filterProcessExitLines(ebpfOutput)
|
||||
|
||||
// Compare outputs byte-for-byte
|
||||
if !bytes.Equal(ptraceFiltered, ebpfFiltered) {
|
||||
t.Fatalf("Output mismatch between ptrace and ebpf backends at verbosity level 2:\n\nPtrace output:\n%s\n\neBPF output:\n%s",
|
||||
string(ptraceOutput), string(ebpfOutput))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTraceVerbosityBackendParityLevel3 tests level 3.
|
||||
// Note: For eBPF backend, levels 3 and 4 produce identical output because:
|
||||
// - Level 3: multi-line format with short types
|
||||
// - Level 4: multi-line format with full nested struct expansion
|
||||
// Since eBPF can only capture primitive types, pointers (as addresses), and slices (as addresses)
|
||||
// without dereferencing or reading struct fields, there is no additional nesting to expand at level 4.
|
||||
// Both levels use PrettyShortenType|PrettyNewlines and differ only from level 2 by line breaks.
|
||||
func TestTraceVerbosityBackendParityLevel3(t *testing.T) {
|
||||
t.Parallel()
|
||||
preCondEBPFTest(t)
|
||||
|
||||
dlvbin := protest.GetDlvBinaryEBPF(t)
|
||||
fixtures := protest.FindFixturesDir()
|
||||
fixturePath := filepath.Join(fixtures, "ebpf_trace_types.go")
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Run trace with ptrace backend at verbosity level 3
|
||||
// Test small integer and slice types from ebpf_trace_types.go (newly supported in eBPF)
|
||||
ptraceCmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpDir, "__debug_ptrace"),
|
||||
"--verbose", "3", fixturePath, "main.tracedSmallInts", "main.tracedSlice")
|
||||
ptraceStderr, err := ptraceCmd.StderrPipe()
|
||||
assertNoError(err, t, "ptrace stderr pipe")
|
||||
defer ptraceStderr.Close()
|
||||
|
||||
assertNoError(ptraceCmd.Start(), t, "running ptrace trace")
|
||||
ptraceOutput, err := io.ReadAll(ptraceStderr)
|
||||
assertNoError(err, t, "reading ptrace output")
|
||||
ptraceCmd.Wait()
|
||||
|
||||
if len(ptraceOutput) == 0 {
|
||||
t.Fatal("ptrace backend produced no output")
|
||||
}
|
||||
|
||||
// Run trace with eBPF backend at verbosity level 3
|
||||
// Test small integer and slice types from ebpf_trace_types.go (newly supported in eBPF)
|
||||
ebpfCmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpDir, "__debug_ebpf"),
|
||||
"--verbose", "3", fixturePath, "main.tracedSmallInts", "main.tracedSlice")
|
||||
ebpfStderr, err := ebpfCmd.StderrPipe()
|
||||
assertNoError(err, t, "ebpf stderr pipe")
|
||||
defer ebpfStderr.Close()
|
||||
|
||||
assertNoError(ebpfCmd.Start(), t, "running ebpf trace")
|
||||
ebpfOutput, err := io.ReadAll(ebpfStderr)
|
||||
assertNoError(err, t, "reading ebpf output")
|
||||
ebpfCmd.Wait()
|
||||
|
||||
if len(ebpfOutput) == 0 {
|
||||
t.Fatal("ebpf backend produced no output")
|
||||
}
|
||||
|
||||
// Filter out process exit messages
|
||||
ptraceFiltered := filterProcessExitLines(ptraceOutput)
|
||||
ebpfFiltered := filterProcessExitLines(ebpfOutput)
|
||||
|
||||
// Compare outputs byte-for-byte
|
||||
if !bytes.Equal(ptraceFiltered, ebpfFiltered) {
|
||||
t.Fatalf("Output mismatch between ptrace and ebpf backends at verbosity level 3:\n\nPtrace output:\n%s\n\neBPF output:\n%s",
|
||||
string(ptraceOutput), string(ebpfOutput))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDlvTestChdir(t *testing.T) {
|
||||
t.Parallel()
|
||||
dlvbin := protest.GetDlvBinary(t)
|
||||
|
||||
@ -627,7 +627,7 @@ func (t *Target) setEBPFTracepointOnFunc(fn *Function, goidOffset int64) error {
|
||||
var args []ebpf.UProbeArgMap
|
||||
varEntries := reader.Variables(dwarfTree, fn.Entry, l, variablesFlags)
|
||||
for _, entry := range varEntries {
|
||||
_, dt, err := readVarEntry(entry.Tree, fn.cu.image)
|
||||
name, dt, err := readVarEntry(entry.Tree, fn.cu.image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -645,9 +645,11 @@ func (t *Target) setEBPFTracepointOnFunc(fn *Function, goidOffset int64) error {
|
||||
isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
|
||||
offset += int64(t.BinInfo().Arch.PtrSize())
|
||||
args = append(args, ebpf.UProbeArgMap{
|
||||
Name: name,
|
||||
Offset: offset,
|
||||
Size: dt.Size(),
|
||||
Kind: dt.Common().ReflectKind,
|
||||
TypeName: dt.String(),
|
||||
Pieces: paramPieces,
|
||||
InReg: len(pieces) > 0,
|
||||
Ret: isret,
|
||||
|
||||
@ -8,15 +8,19 @@ import (
|
||||
)
|
||||
|
||||
type UProbeArgMap struct {
|
||||
Offset int64 // Offset from the stackpointer.
|
||||
Size int64 // Size in bytes.
|
||||
Kind reflect.Kind // Kind of variable.
|
||||
Pieces []int // Pieces of the variables as stored in registers.
|
||||
InReg bool // True if this param is contained in a register.
|
||||
Ret bool // True if this param is a return value.
|
||||
Name string // Parameter name from DWARF.
|
||||
Offset int64 // Offset from the stackpointer.
|
||||
Size int64 // Size in bytes.
|
||||
Kind reflect.Kind // Kind of variable.
|
||||
TypeName string // Original type name from DWARF (e.g., "*int", "[]byte").
|
||||
Pieces []int // Pieces of the variables as stored in registers.
|
||||
InReg bool // True if this param is contained in a register.
|
||||
Ret bool // True if this param is a return value.
|
||||
}
|
||||
|
||||
type RawUProbeParam struct {
|
||||
Name string // Parameter name from DWARF.
|
||||
TypeName string // Original type name from DWARF (e.g., "*int", "[]byte").
|
||||
Pieces []op.Piece
|
||||
RealType godwarf.Type
|
||||
Kind reflect.Kind
|
||||
|
||||
@ -66,6 +66,7 @@ type EBPFContext struct {
|
||||
links []link.Link
|
||||
|
||||
parsedBpfEvents []RawUProbeParams
|
||||
argTypeInfo map[uint64][]UProbeArgMap // Maps function address to argument type information
|
||||
m sync.Mutex
|
||||
|
||||
ctx context.Context
|
||||
@ -108,6 +109,15 @@ func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbe
|
||||
}
|
||||
params := createFunctionParameterList(key, goidOffset, args, isret)
|
||||
params.g_addr_offset = gAddrOffset
|
||||
|
||||
// Store argument type information for later use when parsing results
|
||||
ctx.m.Lock()
|
||||
if ctx.argTypeInfo == nil {
|
||||
ctx.argTypeInfo = make(map[uint64][]UProbeArgMap)
|
||||
}
|
||||
ctx.argTypeInfo[key] = args
|
||||
ctx.m.Unlock()
|
||||
|
||||
return ctx.bpfArgMap.Update(unsafe.Pointer(&key), unsafe.Pointer(¶ms), ebpf.UpdateAny)
|
||||
}
|
||||
|
||||
@ -175,7 +185,7 @@ func (ctx *EBPFContext) pollEvents() {
|
||||
return
|
||||
}
|
||||
|
||||
parsed := parseFunctionParameterList(e.RawSample)
|
||||
parsed := parseFunctionParameterList(ctx, e.RawSample)
|
||||
|
||||
ctx.m.Lock()
|
||||
ctx.parsedBpfEvents = append(ctx.parsedBpfEvents, parsed)
|
||||
@ -184,7 +194,7 @@ func (ctx *EBPFContext) pollEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
||||
func parseFunctionParameterList(ctx *EBPFContext, rawParamBytes []byte) RawUProbeParams {
|
||||
params := (*function_parameter_list_t)(unsafe.Pointer(&rawParamBytes[0]))
|
||||
|
||||
defer runtime.KeepAlive(params) // Ensure the param is not garbage collected.
|
||||
@ -194,12 +204,23 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
||||
rawParams.GoroutineID = int(params.goroutine_id)
|
||||
rawParams.IsRet = params.is_ret
|
||||
|
||||
parseParam := func(param function_parameter_t) *RawUProbeParam {
|
||||
// Look up original type information for this function
|
||||
ctx.m.Lock()
|
||||
argTypes := ctx.argTypeInfo[params.fn_addr]
|
||||
ctx.m.Unlock()
|
||||
|
||||
parseParam := func(param function_parameter_t, paramIdx int) *RawUProbeParam {
|
||||
iparam := &RawUProbeParam{}
|
||||
data := make([]byte, 0x60)
|
||||
ret := param
|
||||
iparam.Kind = reflect.Kind(ret.kind)
|
||||
|
||||
// Populate name and type name from argTypes if available
|
||||
if paramIdx < len(argTypes) {
|
||||
iparam.Name = argTypes[paramIdx].Name
|
||||
iparam.TypeName = argTypes[paramIdx].TypeName
|
||||
}
|
||||
|
||||
val := ret.val[:ret.size]
|
||||
rawDerefValue := ret.deref_val[:0x30]
|
||||
copy(data, val)
|
||||
@ -215,19 +236,19 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
||||
|
||||
switch iparam.Kind {
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size)}}}
|
||||
iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), Name: iparam.Kind.String(), ReflectKind: iparam.Kind}}}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
iparam.RealType = &godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size)}}}
|
||||
iparam.RealType = &godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), Name: iparam.Kind.String(), ReflectKind: iparam.Kind}}}
|
||||
case reflect.Bool:
|
||||
iparam.RealType = &godwarf.BoolType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, ReflectKind: reflect.Bool}}}
|
||||
iparam.RealType = &godwarf.BoolType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "bool", ReflectKind: reflect.Bool}}}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if !usesXMMRegisters(ret) {
|
||||
iparam.RealType = &godwarf.FloatType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), ReflectKind: iparam.Kind}}}
|
||||
iparam.RealType = &godwarf.FloatType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), Name: iparam.Kind.String(), ReflectKind: iparam.Kind}}}
|
||||
}
|
||||
// If in XMM registers, RealType stays nil, marked unreadable in target.go
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
if !usesXMMRegisters(ret) {
|
||||
iparam.RealType = &godwarf.ComplexType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), ReflectKind: iparam.Kind}}}
|
||||
iparam.RealType = &godwarf.ComplexType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), Name: iparam.Kind.String(), ReflectKind: iparam.Kind}}}
|
||||
}
|
||||
case reflect.Ptr, reflect.UnsafePointer:
|
||||
// Display the raw pointer address as a uintptr value.
|
||||
@ -238,14 +259,14 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
||||
// 2) deref_val is limited to 48 bytes, insufficient for
|
||||
// nested or variable-length pointed-to types
|
||||
iparam.Kind = reflect.Uintptr
|
||||
iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), ReflectKind: reflect.Uintptr}}}
|
||||
iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(ret.size), Name: "uintptr", ReflectKind: reflect.Uintptr}}}
|
||||
case reflect.Slice:
|
||||
// Display the slice data pointer address as a uintptr value.
|
||||
// Same limitations as pointers above: element type info is
|
||||
// not available, and deref_val (48 bytes) can only hold a
|
||||
// few elements.
|
||||
iparam.Kind = reflect.Uintptr
|
||||
iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 8, ReflectKind: reflect.Uintptr}}}
|
||||
iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 8, Name: "uintptr", ReflectKind: reflect.Uintptr}}}
|
||||
case reflect.String:
|
||||
strLen := binary.LittleEndian.Uint64(val[8:])
|
||||
iparam.Base = FakeAddressBase + 0x30
|
||||
@ -254,6 +275,7 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
||||
StructType: godwarf.StructType{
|
||||
CommonType: godwarf.CommonType{
|
||||
ByteSize: 16,
|
||||
Name: "string",
|
||||
ReflectKind: reflect.String,
|
||||
},
|
||||
Kind: "struct",
|
||||
@ -276,10 +298,11 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
||||
}
|
||||
|
||||
for i := 0; i < int(params.n_parameters); i++ {
|
||||
rawParams.InputParams = append(rawParams.InputParams, parseParam(params.params[i]))
|
||||
rawParams.InputParams = append(rawParams.InputParams, parseParam(params.params[i], i))
|
||||
}
|
||||
// Return parameters start after input parameters in argTypes
|
||||
for i := 0; i < int(params.n_ret_parameters); i++ {
|
||||
rawParams.ReturnParams = append(rawParams.ReturnParams, parseParam(params.ret_params[i]))
|
||||
rawParams.ReturnParams = append(rawParams.ReturnParams, parseParam(params.ret_params[i], int(params.n_parameters)+i))
|
||||
}
|
||||
|
||||
return rawParams
|
||||
|
||||
@ -496,6 +496,8 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult {
|
||||
tracepoints := t.proc.GetBufferedTracepoints()
|
||||
convertInputParamToVariable := func(ip *ebpf.RawUProbeParam) *Variable {
|
||||
v := &Variable{}
|
||||
v.Name = ip.Name
|
||||
v.DwarfType = ip.RealType
|
||||
v.RealType = ip.RealType
|
||||
v.Len = ip.Len
|
||||
v.Base = ip.Base
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user