diff --git a/checkpointctl.go b/checkpointctl.go index 3b74a4a8..63bf87de 100644 --- a/checkpointctl.go +++ b/checkpointctl.go @@ -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() { @@ -274,6 +275,24 @@ 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 } @@ -293,5 +312,9 @@ func memparse(cmd *cobra.Command, args []string) error { } defer cleanupTasks(tasks) + if pID != 0 { + return printProcessMemoryPages(tasks[0]) + } + return showProcessMemorySizeTables(tasks) } diff --git a/memparse.go b/memparse.go index 1de3caf0..cd85c1b8 100644 --- a/memparse.go +++ b/memparse.go @@ -5,7 +5,9 @@ package main import ( + "bytes" "fmt" + "io" "os" "path/filepath" @@ -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 @@ -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 +}