Skip to content

Commit

Permalink
Merge pull request #141 from behouba/search-memory-pages
Browse files Browse the repository at this point in the history
memparse: add search functionality to memparse
  • Loading branch information
rst0git authored Sep 24, 2024
2 parents 037da26 + be48b4a commit 3fcc128
Show file tree
Hide file tree
Showing 138 changed files with 2,601 additions and 839 deletions.
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",
)

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

0 comments on commit 3fcc128

Please sign in to comment.