122 lines
2.6 KiB
Go
122 lines
2.6 KiB
Go
package glance
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/sjzar/chatlog/internal/errors"
|
|
)
|
|
|
|
// FIXME 按照 region 读取效率较低,512MB 内存读取耗时约 18s
|
|
|
|
type Glance struct {
|
|
PID uint32
|
|
MemRegions []MemRegion
|
|
pipePath string
|
|
data []byte
|
|
}
|
|
|
|
func NewGlance(pid uint32) *Glance {
|
|
return &Glance{
|
|
PID: pid,
|
|
pipePath: filepath.Join(os.TempDir(), fmt.Sprintf("chatlog_pipe_%d", time.Now().UnixNano())),
|
|
}
|
|
}
|
|
|
|
func (g *Glance) Read() ([]byte, error) {
|
|
if g.data != nil {
|
|
return g.data, nil
|
|
}
|
|
|
|
regions, err := GetVmmap(g.PID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g.MemRegions = MemRegionsFilter(regions)
|
|
|
|
if len(g.MemRegions) == 0 {
|
|
return nil, errors.ErrNoMemoryRegionsFound
|
|
}
|
|
|
|
region := g.MemRegions[0]
|
|
|
|
// 1. Create pipe file
|
|
if err := exec.Command("mkfifo", g.pipePath).Run(); err != nil {
|
|
return nil, errors.CreatePipeFileFailed(err)
|
|
}
|
|
defer os.Remove(g.pipePath)
|
|
|
|
// Start a goroutine to read from the pipe
|
|
dataCh := make(chan []byte, 1)
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
// Open pipe for reading
|
|
file, err := os.OpenFile(g.pipePath, os.O_RDONLY, 0600)
|
|
if err != nil {
|
|
errCh <- errors.OpenPipeFileFailed(err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Read all data from pipe
|
|
data, err := io.ReadAll(file)
|
|
if err != nil {
|
|
errCh <- errors.ReadPipeFileFailed(err)
|
|
return
|
|
}
|
|
dataCh <- data
|
|
}()
|
|
|
|
// 2 & 3. Execute lldb command to read memory directly with all parameters
|
|
size := region.End - region.Start
|
|
lldbCmd := fmt.Sprintf("lldb -p %d -o \"memory read --binary --force --outfile %s --count %d 0x%x\" -o \"quit\"",
|
|
g.PID, g.pipePath, size, region.Start)
|
|
|
|
cmd := exec.Command("bash", "-c", lldbCmd)
|
|
|
|
// Set up stdout pipe for monitoring (optional)
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Start the command
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, errors.RunCmdFailed(err)
|
|
}
|
|
|
|
// Monitor lldb output (optional)
|
|
go func() {
|
|
scanner := bufio.NewScanner(stdout)
|
|
for scanner.Scan() {
|
|
// Uncomment for debugging:
|
|
// fmt.Println(scanner.Text())
|
|
}
|
|
}()
|
|
|
|
// Wait for data with timeout
|
|
select {
|
|
case data := <-dataCh:
|
|
g.data = data
|
|
case err := <-errCh:
|
|
return nil, errors.ReadMemoryFailed(err)
|
|
case <-time.After(30 * time.Second):
|
|
cmd.Process.Kill()
|
|
return nil, errors.ErrReadMemoryTimeout
|
|
}
|
|
|
|
// Wait for the command to finish
|
|
if err := cmd.Wait(); err != nil {
|
|
// We already have the data, so just log the error
|
|
log.Err(err).Msg("lldb process exited with error")
|
|
}
|
|
|
|
return g.data, nil
|
|
}
|