From e8291db1e3b631eecde697a92cd10d7cb3eb0075 Mon Sep 17 00:00:00 2001 From: Alessio Greggi Date: Fri, 10 Jan 2025 16:12:23 +0100 Subject: [PATCH 1/3] feat: add environment variables support for command execution Signed-off-by: Alessio Greggi --- cmd/capture.go | 4 +++- cmd/hunt.go | 2 +- internal/ebpf/probesfacade/captor/capture.go | 5 ++++- internal/executor/exec.go | 8 +++++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/cmd/capture.go b/cmd/capture.go index 0577435..cd8ae6a 100644 --- a/cmd/capture.go +++ b/cmd/capture.go @@ -26,6 +26,7 @@ import ( ) var functionSymbols string +var envVars []string var commandOutput bool var commandError bool var libbpfOutput bool @@ -66,7 +67,7 @@ by passing the function name symbol and the binary args. errorCh := make(chan error) ctx := context.Background() - ebpf, err := captor.InitProbes(functionSymbol, args, opts) + ebpf, err := captor.InitProbes(functionSymbol, args, envVars, opts) if err != nil { return fmt.Errorf("error setting up ebpf module: %w", err) } @@ -100,6 +101,7 @@ func init() { captureCmd.Flags().StringVarP(&functionSymbols, "functions", "f", "", "Name of the function symbols to be traced") captureCmd.MarkFlagRequired("functions") + captureCmd.Flags().StringSliceVarP(&envVars, "env-vars", "E", []string{}, "Additional environment variables to pass to the executed command") captureCmd.Flags().BoolVarP(&commandOutput, "include-cmd-stdout", "c", false, "Include the executed command output") captureCmd.Flags().BoolVarP(&commandError, "include-cmd-stderr", "e", false, "Include the executed command error") diff --git a/cmd/hunt.go b/cmd/hunt.go index 0dddd26..53fe0b0 100644 --- a/cmd/hunt.go +++ b/cmd/hunt.go @@ -84,7 +84,7 @@ var huntCmd = &cobra.Command{ errorCh := make(chan error) ctx := context.Background() - ebpf, err := captor.InitProbes(functionSymbol, captureArgs, opts) + ebpf, err := captor.InitProbes(functionSymbol, captureArgs, envVars, opts) if err != nil { return fmt.Errorf("error setting up ebpf module: %w", err) } diff --git a/internal/ebpf/probesfacade/captor/capture.go b/internal/ebpf/probesfacade/captor/capture.go index c5327b2..937a636 100644 --- a/internal/ebpf/probesfacade/captor/capture.go +++ b/internal/ebpf/probesfacade/captor/capture.go @@ -46,12 +46,13 @@ type ebpfSetup struct { lostCh chan uint64 opts CaptureOptions cmd []string + env []string } // InitProbes setup the ebpf module attaching probes and tracepoints // to the ebpf program. // Returns the ebpfSetup struct in case of seccess, an error in case of failure. -func InitProbes(functionSymbol string, cmdArgs []string, opts CaptureOptions) (*ebpfSetup, error) { +func InitProbes(functionSymbol string, cmdArgs []string, env []string, opts CaptureOptions) (*ebpfSetup, error) { if len(cmdArgs) == 0 { return nil, errors.New("error no arguments provided, at least 1 argument is required") } @@ -138,6 +139,7 @@ func InitProbes(functionSymbol string, cmdArgs []string, opts CaptureOptions) (* lostCh: lostChannel, opts: opts, cmd: cmdArgs, + env: env, }, nil } @@ -173,6 +175,7 @@ func (ebpf *ebpfSetup) Capture(ctx context.Context, resultCh chan []uint32, erro // running command to trace its syscalls go executor.Run( ebpf.cmd, + ebpf.env, ebpf.opts.CommandOutput, ebpf.opts.CommandError, &wg, diff --git a/internal/executor/exec.go b/internal/executor/exec.go index c8d42d8..530e77b 100644 --- a/internal/executor/exec.go +++ b/internal/executor/exec.go @@ -10,12 +10,18 @@ import ( // Run execute the command and wait for its end. // The cmdOutput argument is used to print the command output. -func Run(cmd []string, cmdOutput, cmdError bool, wg *sync.WaitGroup, outputCh, errorCh chan<- string) { +func Run(cmd []string, envVars []string, cmdOutput, cmdError bool, wg *sync.WaitGroup, outputCh, errorCh chan<- string) { defer func() { wg.Done() }() command := exec.Command(cmd[0], cmd[1:]...) + // assign custom env variables to the command + env := os.Environ() + if len(envVars) > 0 { + env = append(env, envVars...) + } + command.Env = env stdout, _ := command.StdoutPipe() stderr, _ := command.StderrPipe() From 1d62691899d9a33dc9875afaf36dc38a1fe0850a Mon Sep 17 00:00:00 2001 From: Alessio Greggi Date: Fri, 10 Jan 2025 16:17:36 +0100 Subject: [PATCH 2/3] test: add smoke test for env vars flag Signed-off-by: Alessio Greggi --- tests/integration.txtar | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration.txtar b/tests/integration.txtar index 28777fa..d50bb0e 100644 --- a/tests/integration.txtar +++ b/tests/integration.txtar @@ -52,6 +52,9 @@ stdout 'write' exec harpoon capture -f main.main -c -- ./bin/example-app coin stdout 'stdout: \[flip coin\]' +exec harpoon capture -f main.main -E "VAR1=0" -E "VAR2=1" -- ./bin/example-app coin +stdout 'write' + exec harpoon capture -f main.main -e -- ./bin/example-app streams stdout 'stderr: 0' From 9a653e71895cae28a1fad4c68f99b0bd7a062667 Mon Sep 17 00:00:00 2001 From: Alessio Greggi Date: Fri, 10 Jan 2025 16:22:04 +0100 Subject: [PATCH 3/3] docs: update tutorial with -E flag for env vars Signed-off-by: Alessio Greggi --- docs/tutorials.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/tutorials.md b/docs/tutorials.md index af65bec..cce7c81 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -88,6 +88,18 @@ To do so, you have the `--dump-interval/-i` flag available. This flag gives you harpoon capture -f main.main --dump-interval 2 -- ./binary_name ``` +## Tracing a program that requires environment variables to run + +`harpoon` provides support to pass environment variables to the executed command. + +This is really useful to trace the command changing its behaviour through env vars. + +Here's how you can pass multiple env vars to the executed command: + +```sh +harpoon capture -f main.main -E "VAR1=value1" -E "VAR2=value2" -- ./binary_name +``` + ## Tracing from unit-tests `harpoon` has additional commands other than `capture`. The commands are `analyze` and `hunt`.