diff --git a/interpreter/loaderinfo.go b/interpreter/loaderinfo.go index 093569585..5847d565b 100644 --- a/interpreter/loaderinfo.go +++ b/interpreter/loaderinfo.go @@ -9,6 +9,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" + "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -68,3 +69,13 @@ func (i *LoaderInfo) FileName() string { func (i *LoaderInfo) Gaps() []util.Range { return i.gaps } + +// ExtractAsFile returns a filename referring to the ELF executable, extracting +// it from a backing archive if needed. +func (i *LoaderInfo) ExtractAsFile() (string, error) { + if pr, ok := i.elfRef.ELFOpener.(process.Process); ok { + return pr.ExtractAsFile(i.FileName()) + } + return "", fmt.Errorf("unable to open main executable '%v' due to wrong interface type", + i.FileName()) +} diff --git a/process/coredump.go b/process/coredump.go index d868f5c82..c57e0fcf0 100644 --- a/process/coredump.go +++ b/process/coredump.go @@ -312,6 +312,12 @@ func (cd *CoredumpProcess) OpenELF(path string) (*pfelf.File, error) { return nil, fmt.Errorf("ELF file `%s` not found", path) } +// ExtractAsFile implements the Process interface +func (cd *CoredumpProcess) ExtractAsFile(_ string) (string, error) { + // No filesystem level backing file in coredumps + return "", errors.New("coredump does not support opening backing file") +} + // getFile returns (creating if needed) a matching CoredumpFile for given file name func (cd *CoredumpProcess) getFile(name string) *CoredumpFile { if cf, ok := cd.files[name]; ok { diff --git a/process/process.go b/process/process.go index 04c22ed6a..c7e27a0fb 100644 --- a/process/process.go +++ b/process/process.go @@ -332,3 +332,7 @@ func (sp *systemProcess) OpenELF(file string) (*pfelf.File, error) { // Fall back to opening the file using the process specific root return pfelf.Open(fmt.Sprintf("/proc/%v/root/%s", sp.pid, file)) } + +func (sp *systemProcess) ExtractAsFile(file string) (string, error) { + return fmt.Sprintf("/proc/%v/root/%s", sp.pid, file), nil +} diff --git a/process/types.go b/process/types.go index ffe9e59c3..a2126a6b5 100644 --- a/process/types.go +++ b/process/types.go @@ -119,6 +119,13 @@ type Process interface { // CalculateMappingFileID calculates FileID of the backing file CalculateMappingFileID(*Mapping) (libpf.FileID, error) + // ExtractAsFile returns a filename suitable for opening the given file from + // the target process namespace. This is a last resort method to access the file + // when the ReaderAt interface from OpenMappingFile is not sufficient. The returned + // filename may refer to /proc or be a temporarily file, and it must not be modified + // or deleted. + ExtractAsFile(string) (string, error) + io.Closer pfelf.ELFOpener diff --git a/processmanager/manager_test.go b/processmanager/manager_test.go index 1ca36f26e..e719e1b1e 100644 --- a/processmanager/manager_test.go +++ b/processmanager/manager_test.go @@ -76,6 +76,10 @@ func (d *dummyProcess) OpenELF(name string) (*pfelf.File, error) { return pfelf.Open(name) } +func (d *dummyProcess) ExtractAsFile(name string) (string, error) { + return name, nil +} + func (d *dummyProcess) Close() error { return nil } diff --git a/tools/coredump/new.go b/tools/coredump/new.go index 56a342508..fb6609790 100644 --- a/tools/coredump/new.go +++ b/tools/coredump/new.go @@ -106,6 +106,16 @@ func (tc *trackedCoredump) OpenELF(fileName string) (*pfelf.File, error) { return tc.CoredumpProcess.OpenELF(fileName) } +func (tc *trackedCoredump) ExtractAsFile(fileName string) (string, error) { + prefixedFileName := tc.prefix + fileName + if _, err := os.Stat(prefixedFileName); err != nil { + tc.warnMissing(fileName) + return "", err + } + tc.seen[fileName] = libpf.Void{} + return prefixedFileName, nil +} + func newNewCmd(store *modulestore.Store) *ffcli.Command { args := &newCmd{store: store} diff --git a/tools/coredump/storecoredump.go b/tools/coredump/storecoredump.go index c189760a6..5363bf364 100644 --- a/tools/coredump/storecoredump.go +++ b/tools/coredump/storecoredump.go @@ -18,8 +18,9 @@ import ( type StoreCoredump struct { *process.CoredumpProcess - store *modulestore.Store - modules map[string]ModuleInfo + store *modulestore.Store + modules map[string]ModuleInfo + tempFiles map[string]string } var _ pfelf.ELFOpener = &StoreCoredump{} @@ -60,6 +61,34 @@ func (scd *StoreCoredump) OpenELF(path string) (*pfelf.File, error) { return scd.CoredumpProcess.OpenELF(path) } +func (scd *StoreCoredump) ExtractAsFile(file string) (string, error) { + info, ok := scd.modules[file] + if !ok { + return "", os.ErrNotExist + } + + f, err := os.CreateTemp("", "ebpf-profiler-coredump.*") + if err != nil { + return "", err + } + tmpFile := f.Name() + f.Close() + + if err := scd.store.UnpackModuleToPath(info.Ref, tmpFile); err != nil { + os.Remove(tmpFile) + return "", err + } + scd.tempFiles[file] = tmpFile + return tmpFile, nil +} + +func (scd *StoreCoredump) Close() error { + for _, tmpFile := range scd.tempFiles { + os.Remove(tmpFile) + } + return scd.CoredumpProcess.Close() +} + func OpenStoreCoredump(store *modulestore.Store, coreFileRef modulestore.ID, modules []ModuleInfo) ( process.Process, error) { // Open the coredump from the module store. @@ -84,7 +113,8 @@ func OpenStoreCoredump(store *modulestore.Store, coreFileRef modulestore.ID, mod return &StoreCoredump{ CoredumpProcess: core, - store: store, - modules: moduleMap, + store: store, + modules: moduleMap, + tempFiles: make(map[string]string), }, nil }