From 115305c0f92432c1d020483a67baa335c26ca0a2 Mon Sep 17 00:00:00 2001 From: yotti Date: Thu, 7 May 2026 21:37:25 +0900 Subject: [PATCH] Add parallel memory scanning with goroutine pool Region reads are now parallelized with a semaphore-bounded goroutine pool (8 workers). This significantly speeds up full scans and filter operations since each ReadRegion is an independent ADB round-trip. Filter also pre-loads only the regions that contain candidates rather than lazily reading on demand. --- internal/memory/search/filter.go | 50 +++++++++++++------ internal/memory/search/search.go | 85 ++++++++++++++++++++++++-------- 2 files changed, 100 insertions(+), 35 deletions(-) diff --git a/internal/memory/search/filter.go b/internal/memory/search/filter.go index 0604a46..d8e5e7d 100644 --- a/internal/memory/search/filter.go +++ b/internal/memory/search/filter.go @@ -3,6 +3,7 @@ package search import ( "fmt" "sort" + "sync" "memodroid/internal/driver" ) @@ -38,8 +39,8 @@ func (s *Session) Filter(mode FilterMode, target []byte) error { start uintptr buf []byte } - cache := make(map[int]*regionData) + // Determine which regions contain candidates so we only read those. type candEntry struct { addr uintptr prev []byte @@ -59,20 +60,40 @@ func (s *Session) Filter(mode FilterMode, target []byte) error { return -1, nil } - readRegion := func(idx int, r *driver.Region) *regionData { - if d, ok := cache[idx]; ok { - return d - } - buf, err := s.Driver.ReadRegion(s.PID, r.Start, int(r.End-r.Start)) - if err != nil { - cache[idx] = nil - return nil + // Identify needed regions. + needed := make(map[int]struct{}) + for _, e := range entries { + if idx, _ := regionFor(e.addr); idx >= 0 { + needed[idx] = struct{}{} } - d := ®ionData{start: r.Start, buf: buf} - cache[idx] = d - return d } + // Pre-load needed regions in parallel. + cache := make(map[int]*regionData) + var cacheMu sync.Mutex + var wg sync.WaitGroup + sem := make(chan struct{}, scanWorkers) + + for idx := range needed { + idx := idx + r := ®ions[idx] + wg.Add(1) + sem <- struct{}{} + go func() { + defer wg.Done() + defer func() { <-sem }() + buf, readErr := s.Driver.ReadRegion(s.PID, r.Start, int(r.End-r.Start)) + cacheMu.Lock() + if readErr != nil { + cache[idx] = nil + } else { + cache[idx] = ®ionData{start: r.Start, buf: buf} + } + cacheMu.Unlock() + }() + } + wg.Wait() + next := make(map[uintptr][]byte) for _, e := range entries { sz := fixedSize @@ -80,11 +101,10 @@ func (s *Session) Filter(mode FilterMode, target []byte) error { sz = len(e.prev) } - idx, r := regionFor(e.addr) + idx, _ := regionFor(e.addr) var cur []byte if idx >= 0 { - d := readRegion(idx, r) - if d != nil { + if d := cache[idx]; d != nil { off := int(e.addr - d.start) if off >= 0 && off+sz <= len(d.buf) { cur = d.buf[off : off+sz] diff --git a/internal/memory/search/search.go b/internal/memory/search/search.go index 6cb4b29..3aca4bc 100644 --- a/internal/memory/search/search.go +++ b/internal/memory/search/search.go @@ -1,9 +1,14 @@ package search import ( + "sync" + "memodroid/internal/driver" ) +// scanWorkers limits concurrent ADB calls during parallel memory reads. +const scanWorkers = 8 + // Search scans all rw memory regions for target and stores results in the session. func (s *Session) Search(target []byte) error { return s.SearchFiltered(target, driver.RegionAll, 0, 0) @@ -22,23 +27,43 @@ func (s *Session) SearchFiltered(target []byte, filter driver.RegionFilter, cust searchBytesInRegions(s.Driver, s.PID, regions, target, found) } else { size := s.ValueType.Size() + var mu sync.Mutex + var wg sync.WaitGroup + sem := make(chan struct{}, scanWorkers) + for _, r := range regions { regionSize := int(r.End - r.Start) if regionSize <= 0 { continue } - buf, err := s.Driver.ReadRegion(s.PID, r.Start, regionSize) - if err != nil { - continue - } - for i := 0; i+size <= len(buf); i += size { - if EqualBytes(buf[i:i+size], target) { - cp := make([]byte, size) - copy(cp, buf[i:i+size]) - found[r.Start+uintptr(i)] = cp + wg.Add(1) + sem <- struct{}{} + go func(r driver.Region, regionSize int) { + defer wg.Done() + defer func() { <-sem }() + + buf, err := s.Driver.ReadRegion(s.PID, r.Start, regionSize) + if err != nil { + return } - } + local := make(map[uintptr][]byte) + for i := 0; i+size <= len(buf); i += size { + if EqualBytes(buf[i:i+size], target) { + cp := make([]byte, size) + copy(cp, buf[i:i+size]) + local[r.Start+uintptr(i)] = cp + } + } + if len(local) > 0 { + mu.Lock() + for addr, val := range local { + found[addr] = val + } + mu.Unlock() + } + }(r, regionSize) } + wg.Wait() } s.Candidates = found @@ -48,21 +73,41 @@ func (s *Session) SearchFiltered(target []byte, filter driver.RegionFilter, cust func searchBytesInRegions(drv driver.Driver, pid int, regions []driver.Region, target []byte, found map[uintptr][]byte) { tlen := len(target) + var mu sync.Mutex + var wg sync.WaitGroup + sem := make(chan struct{}, scanWorkers) + for _, r := range regions { size := int(r.End - r.Start) if size <= 0 || size < tlen { continue } - buf, err := drv.ReadRegion(pid, r.Start, size) - if err != nil { - continue - } - for i := 0; i <= len(buf)-tlen; i++ { - if EqualBytes(buf[i:i+tlen], target) { - cp := make([]byte, tlen) - copy(cp, target) - found[r.Start+uintptr(i)] = cp + wg.Add(1) + sem <- struct{}{} + go func(r driver.Region, size int) { + defer wg.Done() + defer func() { <-sem }() + + buf, err := drv.ReadRegion(pid, r.Start, size) + if err != nil { + return } - } + local := make(map[uintptr][]byte) + for i := 0; i <= len(buf)-tlen; i++ { + if EqualBytes(buf[i:i+tlen], target) { + cp := make([]byte, tlen) + copy(cp, target) + local[r.Start+uintptr(i)] = cp + } + } + if len(local) > 0 { + mu.Lock() + for addr, val := range local { + found[addr] = val + } + mu.Unlock() + } + }(r, size) } + wg.Wait() }