Skip to content

Commit

Permalink
feat: display content of process memory pages
Browse files Browse the repository at this point in the history
This commit extends `memparse` sub-command with the ability to display
the content of process memory pages content in a hexdump-like format when
the `--pid` flag is provided. Additionally, the output can be written to a
file using the `--output` flag.

Signed-off-by: Kouame Behouba Manasse <[email protected]>
  • Loading branch information
behouba committed Jul 29, 2023
1 parent 48c9fdc commit 59ab761
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 11 deletions.
44 changes: 33 additions & 11 deletions checkpointctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ import (
)

var (
name string
version string
format string
stats bool
mounts bool
pID uint32
psTree bool
psTreeCmd bool
psTreeEnv bool
files bool
showAll bool
name string
version string
format string
stats bool
mounts bool
outputFilePath string
pID uint32
psTree bool
psTreeCmd bool
psTreeEnv bool
files bool
showAll bool
)

func main() {
Expand Down Expand Up @@ -274,6 +275,23 @@ func setupMemParse() *cobra.Command {
Args: cobra.MinimumNArgs(1),
}

flags := cmd.Flags()

flags.Uint32VarP(
&pID,
"pid",
"p",
0,
"Specify the pid of a process to analyze",
)
flags.StringVarP(
&outputFilePath,
"output",
"o",
"",
"Specify a file where the ouput should be written",
)

return cmd
}

Expand All @@ -293,5 +311,9 @@ func memparse(cmd *cobra.Command, args []string) error {
}
defer cleanupTasks(tasks)

if pID != 0 {
return printProcessMemoryPages(tasks[0])
}

return showProcessMemorySizeTables(tasks)
}
98 changes: 98 additions & 0 deletions memparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package main

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"

Expand All @@ -14,6 +16,12 @@ import (
"github.com/olekukonko/tablewriter"
)

const (
// chunkSize represents the default size of memory chunk (in bytes)
// to read for each output line when printing memory pages content in hexdump-like format.
chunkSize = 16
)

// Display processes memory sizes within the given container checkpoints.
func showProcessMemorySizeTables(tasks []task) error {
// Initialize the table
Expand Down Expand Up @@ -81,3 +89,93 @@ func showProcessMemorySizeTables(tasks []task) error {

return nil
}

func printProcessMemoryPages(task task) error {
memReader, err := crit.NewMemoryReader(
filepath.Join(task.outputDir, metadata.CheckpointDirectory),
pID, pageSize,
)
if err != nil {
return err
}

// Write the output to stdout by default
var output io.Writer = os.Stdout
var compact bool

if outputFilePath != "" {
// Write to ouput to file if --output is provided
output, err = os.Create(outputFilePath)
if err != nil {
return err
}
} else {
compact = true // Use a compact format when writing the output to stdout
fmt.Printf("\nDisplaying memory pages content for Process ID: %d from checkpoint: %s\n\n", pID, task.checkpointFilePath)
}

fmt.Fprintf(output, "Address Hexadecimal ASCII \n")
fmt.Fprintf(output, "-------------------------------------------------------------------------------------\n")

pagemapEntries := memReader.GetPagemapEntries()
for _, entry := range pagemapEntries {
start := entry.GetVaddr()
end := start + (uint64(pageSize) * uint64(entry.GetNrPages()))
buf, err := memReader.GetMemPages(start, end)
if err != nil {
return err
}

hexdump(output, buf, start, compact)
}
return nil
}

// hexdump generates a hexdump of the buffer 'buf' starting at the virtual address 'start'
// and writes the output to 'out'. If compact is true, consecutive duplicate rows will be represented
// with an asterisk (*).
func hexdump(out io.Writer, buf *bytes.Buffer, vaddr uint64, compact bool) {
prevAscii := ""
duplicate := 0
for buf.Len() > 0 {
row := buf.Next(chunkSize)
hex, ascii := generateHexAndAscii(row)

if compact {
if prevAscii == ascii {
if duplicate == 1 {
fmt.Fprint(out, "*\n")
}
duplicate++
} else {
printHexdumpRow(out, vaddr, hex, ascii)
duplicate = 0
}
} else {
printHexdumpRow(out, vaddr, hex, ascii)
}

vaddr += chunkSize
prevAscii = ascii
}
}

func printHexdumpRow(out io.Writer, vaddr uint64, hex string, ascii string) {
fmt.Fprintf(out, "%016x %s |%s|\n", vaddr, hex, ascii)
}

// generateHexAndAscii takes a byte slice and generates its hexadecimal and ASCII representations.
func generateHexAndAscii(data []byte) (hex, ascii string) {
s := string(data)
for i := 0; i < len(s); i++ {
if s[i] < 32 || s[i] >= 127 {
ascii += "."
hex += fmt.Sprintf("%02x ", s[i])
} else {
ascii += string(s[i])
hex += fmt.Sprintf("%02x ", s[i])
}
}

return hex, ascii
}

0 comments on commit 59ab761

Please sign in to comment.