-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: move saving logic outside captor package #25
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,12 @@ limitations under the License. | |
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/alegrey91/harpoon/internal/captor" | ||
"github.com/alegrey91/harpoon/internal/writer" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
|
@@ -35,13 +40,29 @@ by passing the function name symbol and the binary args. | |
`, | ||
Example: " harpoon -f main.doSomething ./command arg1 arg2 ...", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
opts := captor.CaptureOptions{ | ||
|
||
functionSymbolList := strings.Split(functionSymbols, ",") | ||
|
||
captureOpts := captor.CaptureOptions{ | ||
CommandOutput: commandOutput, | ||
LibbpfOutput: libbpfOutput, | ||
Save: save, | ||
Directory: directory, | ||
} | ||
captor.Capture(functionSymbols, args, opts) | ||
for _, functionSymbol := range functionSymbolList { | ||
syscalls, err := captor.Capture(functionSymbol, args, captureOpts) | ||
if err != nil { | ||
fmt.Printf("error capturing syscall: %v", err) | ||
os.Exit(1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bad pattern. No os.Exit should be found outside main.go It's outside the scope of your PR, and could be addressed in another one. But here I would use RunE not Run because RunE signature supports returning an error. So the os.Exit can be found in main.go only This way you can test your code, and you will have one code to handle things There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please note it could also mean you could use errors wrapping So instead of this You would use return fmt.Errorf("error capturing syscall: %w", err) This way the fmt.Printis only present in main.go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm removing this bad pattern from |
||
} | ||
|
||
saveOpts := writer.WriteOptions{ | ||
Save: save, | ||
Directory: directory, | ||
} | ||
if err := writer.Write(syscalls, functionSymbols, saveOpts); err != nil { | ||
fmt.Printf("error writing syscalls for symbol %s", functionSymbol) | ||
os.Exit(1) | ||
} | ||
} | ||
}, | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,17 +5,13 @@ import ( | |
"encoding/binary" | ||
"fmt" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
"unsafe" | ||
|
||
"github.com/alegrey91/harpoon/internal/archiver" | ||
embedded "github.com/alegrey91/harpoon/internal/embeddable" | ||
"github.com/alegrey91/harpoon/internal/executor" | ||
probes "github.com/alegrey91/harpoon/internal/probesfacade" | ||
syscallsw "github.com/alegrey91/harpoon/internal/syscallswriter" | ||
bpf "github.com/aquasecurity/libbpfgo" | ||
) | ||
|
||
|
@@ -36,11 +32,9 @@ type event struct { | |
type CaptureOptions struct { | ||
CommandOutput bool | ||
LibbpfOutput bool | ||
Save bool | ||
Directory string | ||
} | ||
|
||
func Capture(functionSymbols string, cmdArgs []string, opts CaptureOptions) { | ||
func Capture(functionSymbol string, cmdArgs []string, opts CaptureOptions) ([]uint32, error) { | ||
if !opts.LibbpfOutput { | ||
// suppress libbpf log ouput | ||
bpf.SetLoggerCbs( | ||
|
@@ -52,13 +46,10 @@ func Capture(functionSymbols string, cmdArgs []string, opts CaptureOptions) { | |
) | ||
} | ||
|
||
functionSymbolList := strings.Split(functionSymbols, ",") | ||
|
||
objectFile, err := embedded.BPFObject.ReadFile("output/ebpf.o") | ||
bpfModule, err := bpf.NewModuleFromBuffer(objectFile, "ebpf.o") | ||
if err != nil { | ||
fmt.Printf("error loading BPF object file: %v\n", err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error loading BPF object file: %v", err) | ||
} | ||
defer bpfModule.Close() | ||
|
||
|
@@ -68,44 +59,35 @@ func Capture(functionSymbols string, cmdArgs []string, opts CaptureOptions) { | |
*/ | ||
config, err := bpfModule.GetMap(bpfConfigMap) | ||
if err != nil { | ||
fmt.Printf("error retrieving map (%s) from BPF program: %v\n", bpfConfigMap, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error retrieving map (%s) from BPF program: %v", bpfConfigMap, err) | ||
} | ||
enterFuncProbe, err := bpfModule.GetProgram(uprobeEnterFunc) | ||
if err != nil { | ||
fmt.Printf("error loading program (%s): %v\n", uprobeEnterFunc, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error loading program (%s): %v", uprobeEnterFunc, err) | ||
} | ||
exitFuncProbe, err := bpfModule.GetProgram(uprobeExitFunc) | ||
if err != nil { | ||
fmt.Printf("error loading program (%s): %v\n", uprobeExitFunc, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error loading program (%s): %v", uprobeExitFunc, err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All errors should be wrapped with %w, not %v |
||
} | ||
traceFunction, err := bpfModule.GetProgram(tracepointFunc) | ||
if err != nil { | ||
fmt.Printf("error loading program (%s): %v\n", tracepointFunc, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error loading program (%s): %v", tracepointFunc, err) | ||
} | ||
|
||
bpfModule.BPFLoadObject() | ||
for _, functionSymbol := range functionSymbolList { | ||
offset, err := probes.AttachUProbe(cmdArgs[0], functionSymbol, enterFuncProbe) | ||
if err != nil { | ||
fmt.Printf("error attaching uprobe to %s: %v", functionSymbol, err) | ||
os.Exit(-1) | ||
} | ||
offset, err := probes.AttachUProbe(cmdArgs[0], functionSymbol, enterFuncProbe) | ||
if err != nil { | ||
return nil, fmt.Errorf("error attaching uprobe to %s: %v", functionSymbol, err) | ||
} | ||
|
||
err = probes.AttachURETProbe(cmdArgs[0], functionSymbol, exitFuncProbe, offset) | ||
if err != nil { | ||
fmt.Printf("error attaching uretprobe to %s: %v", functionSymbol, err) | ||
os.Exit(-1) | ||
} | ||
err = probes.AttachURETProbe(cmdArgs[0], functionSymbol, exitFuncProbe, offset) | ||
if err != nil { | ||
return nil, fmt.Errorf("error attaching uretprobe to %s: %v", functionSymbol, err) | ||
} | ||
|
||
traceLink, err := traceFunction.AttachTracepoint(tracepointCategory, tracepointName) | ||
if err != nil { | ||
fmt.Printf("error attaching tracepoint at event (%s:%s): %v\n", tracepointCategory, tracepointName, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error attaching tracepoint at event (%s:%s): %v", tracepointCategory, tracepointName, err) | ||
} | ||
defer traceLink.Destroy() | ||
|
||
|
@@ -118,17 +100,15 @@ func Capture(functionSymbols string, cmdArgs []string, opts CaptureOptions) { | |
baseCmd := append([]byte(baseargs), 0) | ||
err = config.Update(unsafe.Pointer(&config_key_args), unsafe.Pointer(&baseCmd[0])) | ||
if err != nil { | ||
fmt.Printf("error updating map (%s) with values %d / %s: %v\n", bpfConfigMap, config_key_args, baseargs, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error updating map (%s) with values %d / %s: %v", bpfConfigMap, config_key_args, baseargs, err) | ||
} | ||
|
||
// init perf buffer | ||
eventsChannel := make(chan []byte) | ||
lostChannel := make(chan uint64) | ||
rb, err := bpfModule.InitPerfBuf(bpfEventsMap, eventsChannel, lostChannel, 1) | ||
if err != nil { | ||
fmt.Printf("error initializing map (%s) with PerfBuffer: %v\n", bpfEventsMap, err) | ||
os.Exit(-1) | ||
return nil, fmt.Errorf("error initializing map (%s) with PerfBuffer: %v", bpfEventsMap, err) | ||
} | ||
|
||
// run args that we want to trace | ||
|
@@ -144,6 +124,7 @@ func Capture(functionSymbols string, cmdArgs []string, opts CaptureOptions) { | |
var e event | ||
err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &e) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "error reading event: %v\n", err) | ||
return | ||
} | ||
syscalls = append(syscalls, e.SyscallID) | ||
|
@@ -159,32 +140,5 @@ func Capture(functionSymbols string, cmdArgs []string, opts CaptureOptions) { | |
wg.Wait() | ||
rb.Stop() | ||
|
||
var errOut error | ||
if opts.Save { | ||
fileName := archiver.Convert(functionSymbolList[0]) | ||
err := os.MkdirAll(opts.Directory, os.ModePerm) | ||
if err != nil { | ||
fmt.Printf("error creating directory: %v\n", err) | ||
os.Exit(-1) | ||
} | ||
file, err := os.Create(path.Join(opts.Directory, fileName)) | ||
if err != nil { | ||
fmt.Printf("error creating file %s: %v\n", file.Name(), err) | ||
os.Exit(-1) | ||
} | ||
defer file.Close() | ||
|
||
if err := file.Chmod(0744); err != nil { | ||
fmt.Printf("error setting permissions to %s: %v\n", file.Name(), err) | ||
} | ||
// write to file | ||
errOut = syscallsw.Print(file, syscalls) | ||
} else { | ||
// write to stdout | ||
errOut = syscallsw.Print(os.Stdout, syscalls) | ||
} | ||
if errOut != nil { | ||
fmt.Printf("error printing out system calls: %v\n", errOut) | ||
os.Exit(-1) | ||
} | ||
return syscalls, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package writer | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path" | ||
|
||
"github.com/alegrey91/harpoon/internal/archiver" | ||
syscallsw "github.com/alegrey91/harpoon/internal/syscallswriter" | ||
) | ||
|
||
type WriteOptions struct { | ||
Save bool | ||
Directory string | ||
} | ||
|
||
func Write(syscalls []uint32, functionSymbol string, opts WriteOptions) error { | ||
var errOut error | ||
if opts.Save { | ||
fileName := archiver.Convert(functionSymbol) | ||
err := os.MkdirAll(opts.Directory, os.ModePerm) | ||
if err != nil { | ||
return fmt.Errorf("error creating directory: %v", err) | ||
} | ||
file, err := os.Create(path.Join(opts.Directory, fileName)) | ||
if err != nil { | ||
return fmt.Errorf("error creating file %s: %v", file.Name(), err) | ||
} | ||
defer file.Close() | ||
|
||
if err := file.Chmod(0744); err != nil { | ||
return fmt.Errorf("error setting permissions to %s: %v", file.Name(), err) | ||
} | ||
// write to file | ||
errOut = syscallsw.Print(file, syscalls) | ||
} else { | ||
// write to stdout | ||
errOut = syscallsw.Print(os.Stdout, syscalls) | ||
} | ||
if errOut != nil { | ||
return fmt.Errorf("error printing out system calls: %v", errOut) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm always worried when I see such code
And empty string will lead to a slice of 1 string with empty string in it.
So []string{""}
I would either check the length of functionSymbols before splitting, or I would skip in the for loop when strings.TrimSpace(item) == ""
I'm unsure if it can happen with your cost. But I cannot help myself to raise this to your attention
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't agree more with you. I'll add some checks to avoid that sometimes like so could happen. Thanks for raising the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the
--function
flag is marked as required, we should never face with an empty value in input.So for now I would skip any control and add them in the future.