Skip to content
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

memparse: add search functionality to memparse #141

Merged
merged 4 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions cmd/memparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ func MemParse() *cobra.Command {
"Specify the output file to be written to",
)

flags.StringVarP(
searchPattern,
"search",
"s",
"",
"Search for a string pattern in memory pages",
)

flags.StringVarP(
searchRegexPattern,
"search-regex",
"r",
"",
"Search for a regex pattern in memory pages",
)

flags.IntVarP(
searchContext,
"context",
"c",
0,
"Print the specified number of bytes surrounding each match",
)
rst0git marked this conversation as resolved.
Show resolved Hide resolved

return cmd
}

Expand Down Expand Up @@ -79,6 +103,10 @@ func memparse(cmd *cobra.Command, args []string) error {
}
defer internal.CleanupTasks(tasks)

if *searchPattern != "" || *searchRegexPattern != "" {
return printMemorySearchResultForPID(tasks[0])
}

if *pID != 0 {
return printProcessMemoryPages(tasks[0])
}
Expand Down Expand Up @@ -273,3 +301,68 @@ func generateHexAndAscii(data []byte) (string, string) {

return hex, ascii
}

// Searches for a pattern in the memory of a given PID and prints the results.
func printMemorySearchResultForPID(task internal.Task) error {
c := crit.New(nil, nil, filepath.Join(task.OutputDir, metadata.CheckpointDirectory), false, false)
psTree, err := c.ExplorePs()
if err != nil {
return fmt.Errorf("failed to get process tree: %w", err)
}

// Check if PID exist within the checkpoint
ps := psTree.FindPs(*pID)
if ps == nil {
return fmt.Errorf("no process with PID %d (use `inspect --ps-tree` to view all PIDs)", *pID)
}

memReader, err := crit.NewMemoryReader(
filepath.Join(task.OutputDir, metadata.CheckpointDirectory),
*pID, pageSize,
)
if err != nil {
return fmt.Errorf("failed to create memory reader: %w", err)
}

if err := internal.UntarFiles(
task.CheckpointFilePath, task.OutputDir,
[]string{filepath.Join(metadata.CheckpointDirectory, fmt.Sprintf("pages-%d.img", memReader.GetPagesID()))},
); err != nil {
return fmt.Errorf("failed to extract pages file: %w", err)
}

pattern := *searchPattern
escapeRegExpCharacters := true
if pattern == "" {
pattern = *searchRegexPattern
escapeRegExpCharacters = false
}

results, err := memReader.SearchPattern(pattern, escapeRegExpCharacters, *searchContext, 0)
if err != nil {
return fmt.Errorf("failed to search pattern in memory: %w", err)
}

if len(results) == 0 {
fmt.Printf("No matches for pattern \"%s\" in the memory of PID %d\n", pattern, *pID)
return nil
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Address", "Match", "Instance"})
table.SetAutoMergeCells(false)
table.SetRowLine(true)

for i, result := range results {
table.Append([]string{
fmt.Sprintf(
"%016x", result.Vaddr),
result.Match,
fmt.Sprintf("%d", i+1),
})
}

table.Render()

return nil
}
25 changes: 14 additions & 11 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package cmd
import "github.com/checkpoint-restore/checkpointctl/internal"

var (
format *string = &internal.Format
stats *bool = &internal.Stats
mounts *bool = &internal.Mounts
outputFilePath *string = &internal.OutputFilePath
pID *uint32 = &internal.PID
psTree *bool = &internal.PsTree
psTreeCmd *bool = &internal.PsTreeCmd
psTreeEnv *bool = &internal.PsTreeEnv
files *bool = &internal.Files
sockets *bool = &internal.Sockets
showAll *bool = &internal.ShowAll
format *string = &internal.Format
stats *bool = &internal.Stats
mounts *bool = &internal.Mounts
outputFilePath *string = &internal.OutputFilePath
pID *uint32 = &internal.PID
psTree *bool = &internal.PsTree
psTreeCmd *bool = &internal.PsTreeCmd
psTreeEnv *bool = &internal.PsTreeEnv
files *bool = &internal.Files
sockets *bool = &internal.Sockets
showAll *bool = &internal.ShowAll
searchPattern *string = &internal.SearchPattern
searchRegexPattern *string = &internal.SearchRegexPattern
searchContext *int = &internal.SearchContext
)
11 changes: 10 additions & 1 deletion docs/checkpointctl-memparse.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ include::footer.adoc[]
*-p, --pid*=_PID_::
Specify the PID of a process to analyze

*-s, --search*=_STRING_::
Search for a string pattern in memory pages

*-r, --search-regex*=_REGEX_::
Search for a regex pattern in memory pages

*-c, --context*=_CONTEXT_::
Print the specified number of bytes surrounding each match

== See also

checkpointctl(1)
checkpointctl(1)
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
toolchain go1.21.1

require (
github.com/checkpoint-restore/go-criu/v7 v7.1.0
github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containers/storage v1.54.0
github.com/olekukonko/tablewriter v0.0.5
github.com/opencontainers/runtime-spec v1.2.0
Expand All @@ -25,6 +25,6 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
golang.org/x/sys v0.20.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
golang.org/x/sys v0.25.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/checkpoint-restore/go-criu/v7 v7.1.0 h1:JbQyO4o+P8ycNTMLPiiDqXg49bAcy4WljWCzYQho35A=
github.com/checkpoint-restore/go-criu/v7 v7.1.0/go.mod h1:1svAtmbtvX4BKI45OFzgoTTLG7oYFKdColv/Vcsb2A8=
github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo=
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
github.com/containers/storage v1.54.0 h1:xwYAlf6n9OnIlURQLLg3FYHbO74fQ/2W2N6EtQEUM4I=
github.com/containers/storage v1.54.0/go.mod h1:PlMOoinRrBSnhYODLxt4EXl0nmJt+X0kjG0Xdt9fMTw=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
Expand Down Expand Up @@ -47,10 +47,10 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
25 changes: 14 additions & 11 deletions internal/options.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package internal

var (
Format string
Stats bool
Mounts bool
OutputFilePath string
PID uint32
PsTree bool
PsTreeCmd bool
PsTreeEnv bool
Files bool
Sockets bool
ShowAll bool
Format string
Stats bool
Mounts bool
OutputFilePath string
PID uint32
PsTree bool
PsTreeCmd bool
PsTreeEnv bool
Files bool
Sockets bool
ShowAll bool
SearchPattern string
SearchRegexPattern string
SearchContext int
)
70 changes: 70 additions & 0 deletions test/checkpointctl.bats
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,76 @@ function teardown() {
[[ ${lines[3]} == *"....H...H.../..H"* ]]
}

@test "Run checkpointctl memparse --search=PATH with invalid PID" {
cp data/config.dump \
data/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
cp test-imgs/pstree.img \
test-imgs/core-*.img \
test-imgs/pagemap-*.img \
test-imgs/pages-*.img "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl memparse --search=PATH "$TEST_TMP_DIR2"/test.tar --pid=999
[ "$status" -eq 1 ]
[[ ${lines[0]} == *"no process with PID 999"* ]]
}

@test "Run checkpointctl memparse with --search=PATH and --context=-1" {
cp data/config.dump \
data/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
cp test-imgs/pstree.img \
test-imgs/core-*.img \
test-imgs/pagemap-*.img \
test-imgs/pages-*.img "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl memparse --search=PATH --context=-1 "$TEST_TMP_DIR2"/test.tar --pid=1
[ "$status" -eq 1 ]
[[ ${lines[0]} == *"context size cannot be negative"* ]]
}

@test "Run checkpointctl memparse with --search=NON_EXISTING_PATTERN" {
cp data/config.dump \
data/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
cp test-imgs/pstree.img \
test-imgs/core-*.img \
test-imgs/pagemap-*.img \
test-imgs/pages-*.img "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl memparse --search=NON_EXISTING_PATTERN "$TEST_TMP_DIR2"/test.tar --pid=1
[ "$status" -eq 0 ]
[[ ${lines[0]} == *"No matches"* ]]
}

@test "Run checkpointctl memparse with --search=PATH and --context=10 flags" {
cp data/config.dump \
data/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
cp test-imgs/pstree.img \
test-imgs/core-*.img \
test-imgs/pagemap-*.img \
test-imgs/pages-*.img "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl memparse --search=PATH --context=10 "$TEST_TMP_DIR2"/test.tar --pid=1
[ "$status" -eq 0 ]
[[ ${lines[3]} == *"PATH"* ]]
}

@test "Run checkpointctl memparse with --search-regex='HOME=([^?]+)' " {
cp data/config.dump \
data/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
cp test-imgs/pstree.img \
test-imgs/core-*.img \
test-imgs/pagemap-*.img \
test-imgs/pages-*.img "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . )
checkpointctl memparse --search-regex='HOME=([^?]+)' "$TEST_TMP_DIR2"/test.tar --pid=1
[ "$status" -eq 0 ]
[[ ${lines[3]} == *"HOME"* ]]
}

@test "Run checkpointctl memparse with tar file and invalid PID" {
cp data/config.dump \
data/spec.dump "$TEST_TMP_DIR1"
Expand Down
Loading
Loading