Skip to content

Commit 59ab761

Browse files
committed
feat: display content of process memory pages
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]>
1 parent 48c9fdc commit 59ab761

File tree

2 files changed

+131
-11
lines changed

2 files changed

+131
-11
lines changed

checkpointctl.go

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ import (
1212
)
1313

1414
var (
15-
name string
16-
version string
17-
format string
18-
stats bool
19-
mounts bool
20-
pID uint32
21-
psTree bool
22-
psTreeCmd bool
23-
psTreeEnv bool
24-
files bool
25-
showAll bool
15+
name string
16+
version string
17+
format string
18+
stats bool
19+
mounts bool
20+
outputFilePath string
21+
pID uint32
22+
psTree bool
23+
psTreeCmd bool
24+
psTreeEnv bool
25+
files bool
26+
showAll bool
2627
)
2728

2829
func main() {
@@ -274,6 +275,23 @@ func setupMemParse() *cobra.Command {
274275
Args: cobra.MinimumNArgs(1),
275276
}
276277

278+
flags := cmd.Flags()
279+
280+
flags.Uint32VarP(
281+
&pID,
282+
"pid",
283+
"p",
284+
0,
285+
"Specify the pid of a process to analyze",
286+
)
287+
flags.StringVarP(
288+
&outputFilePath,
289+
"output",
290+
"o",
291+
"",
292+
"Specify a file where the ouput should be written",
293+
)
294+
277295
return cmd
278296
}
279297

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

314+
if pID != 0 {
315+
return printProcessMemoryPages(tasks[0])
316+
}
317+
296318
return showProcessMemorySizeTables(tasks)
297319
}

memparse.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package main
66

77
import (
8+
"bytes"
89
"fmt"
10+
"io"
911
"os"
1012
"path/filepath"
1113

@@ -14,6 +16,12 @@ import (
1416
"github.com/olekukonko/tablewriter"
1517
)
1618

19+
const (
20+
// chunkSize represents the default size of memory chunk (in bytes)
21+
// to read for each output line when printing memory pages content in hexdump-like format.
22+
chunkSize = 16
23+
)
24+
1725
// Display processes memory sizes within the given container checkpoints.
1826
func showProcessMemorySizeTables(tasks []task) error {
1927
// Initialize the table
@@ -81,3 +89,93 @@ func showProcessMemorySizeTables(tasks []task) error {
8189

8290
return nil
8391
}
92+
93+
func printProcessMemoryPages(task task) error {
94+
memReader, err := crit.NewMemoryReader(
95+
filepath.Join(task.outputDir, metadata.CheckpointDirectory),
96+
pID, pageSize,
97+
)
98+
if err != nil {
99+
return err
100+
}
101+
102+
// Write the output to stdout by default
103+
var output io.Writer = os.Stdout
104+
var compact bool
105+
106+
if outputFilePath != "" {
107+
// Write to ouput to file if --output is provided
108+
output, err = os.Create(outputFilePath)
109+
if err != nil {
110+
return err
111+
}
112+
} else {
113+
compact = true // Use a compact format when writing the output to stdout
114+
fmt.Printf("\nDisplaying memory pages content for Process ID: %d from checkpoint: %s\n\n", pID, task.checkpointFilePath)
115+
}
116+
117+
fmt.Fprintf(output, "Address Hexadecimal ASCII \n")
118+
fmt.Fprintf(output, "-------------------------------------------------------------------------------------\n")
119+
120+
pagemapEntries := memReader.GetPagemapEntries()
121+
for _, entry := range pagemapEntries {
122+
start := entry.GetVaddr()
123+
end := start + (uint64(pageSize) * uint64(entry.GetNrPages()))
124+
buf, err := memReader.GetMemPages(start, end)
125+
if err != nil {
126+
return err
127+
}
128+
129+
hexdump(output, buf, start, compact)
130+
}
131+
return nil
132+
}
133+
134+
// hexdump generates a hexdump of the buffer 'buf' starting at the virtual address 'start'
135+
// and writes the output to 'out'. If compact is true, consecutive duplicate rows will be represented
136+
// with an asterisk (*).
137+
func hexdump(out io.Writer, buf *bytes.Buffer, vaddr uint64, compact bool) {
138+
prevAscii := ""
139+
duplicate := 0
140+
for buf.Len() > 0 {
141+
row := buf.Next(chunkSize)
142+
hex, ascii := generateHexAndAscii(row)
143+
144+
if compact {
145+
if prevAscii == ascii {
146+
if duplicate == 1 {
147+
fmt.Fprint(out, "*\n")
148+
}
149+
duplicate++
150+
} else {
151+
printHexdumpRow(out, vaddr, hex, ascii)
152+
duplicate = 0
153+
}
154+
} else {
155+
printHexdumpRow(out, vaddr, hex, ascii)
156+
}
157+
158+
vaddr += chunkSize
159+
prevAscii = ascii
160+
}
161+
}
162+
163+
func printHexdumpRow(out io.Writer, vaddr uint64, hex string, ascii string) {
164+
fmt.Fprintf(out, "%016x %s |%s|\n", vaddr, hex, ascii)
165+
}
166+
167+
// generateHexAndAscii takes a byte slice and generates its hexadecimal and ASCII representations.
168+
func generateHexAndAscii(data []byte) (hex, ascii string) {
169+
s := string(data)
170+
for i := 0; i < len(s); i++ {
171+
if s[i] < 32 || s[i] >= 127 {
172+
ascii += "."
173+
hex += fmt.Sprintf("%02x ", s[i])
174+
} else {
175+
ascii += string(s[i])
176+
hex += fmt.Sprintf("%02x ", s[i])
177+
}
178+
}
179+
180+
return hex, ascii
181+
}

0 commit comments

Comments
 (0)