Files
blacktop 196d77de6f feat: add kernel C++ parsing to symbolicate cmds and APIs
Add API endpoints and CLI support for discovering C++ classes and symbolication of kernelcaches, refactor Mach-O handling, and improve symbol collection.

- API: add /kernel/cpp and /kernel/symbolicate routes, request param structs, response types, and openKernel helper. Use cpp scanner and signature parsing to return classes and symbol maps.
- CLI: wire scanner LogStats flag, refactor kernel symbolicate command (schema writer helper, improved signature parsing, and symbol matching logic). Add tests for symbolicator schema and kernel symbol matching.
- Signature pkg: add kernel C++ symbol extraction (pkg/signature/kernel_cpp.go) and SymbolicateMachO to symbolicate already-open Mach-Os; integrate C++ symbols into symbol map and update signature matching/logging behavior.
- Internal: refactor in-memory DB lookups (findMachOByUUID, findSymbolByAddr) to reduce duplication. Improve symbols collection for kernel Mach-Os (collectKernelMachoSymbols, extra kernel symbols from signature/C++), add helpers to append symbols.
- Kernelcache CPP: add LogStats option and conditional logging of scan stats.
- Crashlog/ips: update wording to reflect kernel symbols are from kernel analysis and store KernelSymbols earlier in processing; parse signatures only when configured.

Also add unit tests for new symbolication helpers and kernel C++ signature handling. Overall this consolidates kernel symbol discovery, improves reuse, and surfaces C++-derived symbols in symbol maps.
2026-03-10 12:20:12 -06:00

217 lines
4.8 KiB
Go

package db
import (
"encoding/gob"
"fmt"
"os"
"slices"
"github.com/blacktop/ipsw/internal/model"
"github.com/pkg/errors"
"gorm.io/gorm"
)
// Memory is a database that stores data in memory.
type Memory struct {
IPSWs map[string]*model.Ipsw
Path string
}
// NewInMemory creates a new in-memory database.
func NewInMemory(path string) (Database, error) {
if path == "" {
return nil, errors.New("'path' is required")
}
return &Memory{
IPSWs: make(map[string]*model.Ipsw),
Path: path,
}, nil
}
// Connect connects to the database.
func (m *Memory) Connect() error {
f, err := os.Open(m.Path)
if err != nil {
return err
}
defer f.Close()
return gob.NewDecoder(f).Decode(&m.IPSWs)
}
// Create creates a new entry in the database.
// It returns ErrAlreadyExists if the key already exists.
func (m *Memory) Create(value any) error {
if ipsw, ok := value.(*model.Ipsw); ok {
if _, exists := m.IPSWs[ipsw.ID]; exists {
return gorm.ErrDuplicatedKey
}
m.IPSWs[ipsw.ID] = ipsw
return nil
}
return fmt.Errorf("invalid type: %T", value)
}
// Get returns the IPSW for the given key.
// It returns ErrNotFound if the key does not exist.
func (m *Memory) Get(id string) (*model.Ipsw, error) {
ipsw, exists := m.IPSWs[id]
if !exists {
return nil, errors.Errorf("no IPSW found with id: %s", id)
}
return ipsw, nil
}
// GetIpswByName returns the IPSW for the given name.
// It returns ErrNotFound if the key does not exist.
func (m *Memory) GetIpswByName(name string) (*model.Ipsw, error) {
for _, ipsw := range m.IPSWs {
if ipsw.Name == name {
return ipsw, nil
}
}
return nil, model.ErrNotFound
}
// GetIPSW returns the IPSW for the given version, build, and device.
// It returns ErrNotFound if the IPSW does not exist.
func (m *Memory) GetIPSW(version, build, device string) (*model.Ipsw, error) {
for _, ipsw := range m.IPSWs {
if ipsw.Version == version && ipsw.BuildID == build {
var devs []string
for _, dev := range ipsw.Devices {
devs = append(devs, dev.Name)
}
if slices.Contains(devs, device) {
return ipsw, nil
}
}
}
return nil, model.ErrNotFound
}
func (m *Memory) GetDSC(uuid string) (*model.DyldSharedCache, error) {
for _, ipsw := range m.IPSWs {
for _, dyld := range ipsw.DSCs {
if dyld.UUID == uuid {
return dyld, nil
}
}
}
return nil, model.ErrNotFound
}
func (m *Memory) GetDSCImage(uuid string, addr uint64) (*model.Macho, error) {
for _, ipsw := range m.IPSWs {
for _, dyld := range ipsw.DSCs {
if dyld.UUID == uuid {
for _, img := range dyld.Images {
if addr >= img.TextStart && addr < img.TextEnd {
return img, nil
}
}
}
}
}
return nil, model.ErrNotFound
}
func (m *Memory) GetMachO(uuid string) (*model.Macho, error) {
for _, ipsw := range m.IPSWs {
if macho := findMachOByUUID(ipsw, uuid); macho != nil {
return macho, nil
}
}
return nil, model.ErrNotFound
}
func (m *Memory) GetSymbol(uuid string, addr uint64) (*model.Symbol, error) {
for _, ipsw := range m.IPSWs {
if macho := findMachOByUUID(ipsw, uuid); macho != nil {
if sym := findSymbolByAddr(macho.Symbols, addr); sym != nil {
return sym, nil
}
}
}
return nil, model.ErrNotFound
}
func (m *Memory) GetSymbols(uuid string) ([]*model.Symbol, error) {
for _, ipsw := range m.IPSWs {
if macho := findMachOByUUID(ipsw, uuid); macho != nil {
return macho.Symbols, nil
}
}
return nil, model.ErrNotFound
}
func findMachOByUUID(ipsw *model.Ipsw, uuid string) *model.Macho {
for _, dyld := range ipsw.DSCs {
for _, img := range dyld.Images {
if img.UUID == uuid {
return img
}
}
}
for _, fs := range ipsw.FileSystem {
if fs.UUID == uuid {
return fs
}
}
for _, kc := range ipsw.Kernels {
for _, kext := range kc.Kexts {
if kext.UUID == uuid {
return kext
}
}
}
return nil
}
func findSymbolByAddr(symbols []*model.Symbol, addr uint64) *model.Symbol {
for _, sym := range symbols {
if addr >= sym.Start && addr < sym.End {
return sym
}
}
return nil
}
// Set sets the value for the given key.
// It overwrites any previous value for that key.
func (m *Memory) Save(value any) error {
if ipsw, ok := value.(*model.Ipsw); ok {
m.IPSWs[ipsw.ID] = ipsw
}
return nil
}
func (m *Memory) List(version string) ([]*model.Ipsw, error) {
ipsws := []*model.Ipsw{}
for _, p := range m.IPSWs {
if p.Version == version {
ipsws = append(ipsws, p)
}
}
return ipsws, nil
}
// Delete removes the given key.
// It returns ErrNotFound if the key does not exist.
func (m *Memory) Delete(id string) error {
delete(m.IPSWs, id)
return nil
}
// Close closes the database.
// It returns ErrClosed if the database is already closed.
func (m *Memory) Close() error {
f, err := os.Open(m.Path)
if err != nil {
return err
}
defer f.Close()
gob.Register([]any{})
gob.Register(map[string]any{})
return gob.NewEncoder(f).Encode(m.IPSWs)
}