Files
ipsw/pkg/dyld/format.go
blacktop 8e6f2c554b chore: modernize to 1.26
Refactor string concatenation to use strings.Builder for improved performance in multiple files
2026-02-12 10:02:01 -07:00

570 lines
18 KiB
Go

package dyld
import (
"bytes"
"encoding/binary"
"fmt"
"sort"
"strings"
"github.com/blacktop/go-macho/types"
"github.com/blacktop/ipsw/pkg/table"
"github.com/dustin/go-humanize"
)
func (dch CacheHeader) String() string {
var magicBytes []byte = dch.Magic[:]
return fmt.Sprintf(
"Magic = %s\n"+
"MappingOffset = %08X\n"+
"MappingCount = %d\n"+
"ImagesOffset = %08X\n"+
"ImagesCount = %d\n"+
"DyldBaseAddress = %08X\n"+
"CodeSignatureOffset = %08X\n"+
"CodeSignatureSize = %08X\n"+
"SlideInfoOffset = %08X\n"+
"SlideInfoSize = %08X\n"+
"LocalSymbolsOffset = %08X\n"+
"LocalSymbolsSize = %08X\n"+
"UUID = %s\n"+
"Platform = %s\n"+
"Format = %d\n",
bytes.Trim(magicBytes, "\x00"),
dch.MappingOffset,
dch.MappingCount,
dch.ImagesOffsetOld,
dch.ImagesCountOld,
dch.DyldBaseAddress,
dch.CodeSignatureOffset,
dch.CodeSignatureSize,
dch.SlideInfoOffsetUnused,
dch.SlideInfoSizeUnused,
dch.LocalSymbolsOffset,
dch.LocalSymbolsSize,
dch.UUID.String(),
dch.Platform.String(),
dch.FormatVersion.Version(),
)
}
func (m *CacheMapping) String() string {
return fmt.Sprintf(
"Name = %s\n"+
"Address = %#x\n"+
"Size = %s\n"+
"FileOffset = %#x\n"+
"MaxProt = %s\n"+
"InitProt = %s\n",
m.Name,
m.Address,
humanize.Bytes(m.Size),
m.FileOffset,
m.MaxProt.String(),
m.InitProt.String(),
)
}
func (c CacheImageInfo) String() string {
return fmt.Sprintf(
"Address = %016X\n"+
"ModTime = %016X\n"+
"Inode = %d\n"+
"PathFileOffset = %08X\n",
c.Address,
c.ModTime,
c.Inode,
c.PathFileOffset,
)
}
func (dch CacheHeader) Print() {
fmt.Println("Header")
fmt.Println("======")
fmt.Println(dch.String())
fmt.Printf("Slide Info: %4dKB, file offset: 0x%09X -> 0x%09X\n", dch.SlideInfoSizeUnused/1024, dch.SlideInfoOffsetUnused, dch.SlideInfoOffsetUnused+dch.SlideInfoSizeUnused)
fmt.Printf("Local Symbols: %3dMB, file offset: 0x%09X -> 0x%09X\n", dch.LocalSymbolsSize/(1024*1024), dch.LocalSymbolsOffset, dch.LocalSymbolsOffset+dch.LocalSymbolsSize)
fmt.Printf("Accelerate Tab: %3dKB, address: 0x%09X -> 0x%09X\n", dch.AccelerateInfoSizeUnusedOrDyldStartFuncAddr/1024, dch.AccelerateInfoAddrUnusedOrDyldAddr, dch.AccelerateInfoAddrUnusedOrDyldAddr+dch.AccelerateInfoSizeUnusedOrDyldStartFuncAddr)
fmt.Println()
}
func (l *localSymbolInfo) Print() {
fmt.Printf("Local symbols nlist array: %3dMB, file offset: 0x%09X -> 0x%09X\n", l.NListByteSize/(1024*1024), l.NListFileOffset, l.NListFileOffset+l.NListByteSize)
fmt.Printf("Local symbols string pool: %3dMB, file offset: 0x%09X -> 0x%09X\n", l.StringsSize/(1024*1024), l.StringsFileOffset, l.StringsFileOffset+l.StringsSize)
}
func (mappings cacheMappings) String() string {
tableString := &strings.Builder{}
mdata := [][]string{}
for _, mapping := range mappings {
mdata = append(mdata, []string{
mapping.Name,
mapping.InitProt.String(),
mapping.MaxProt.String(),
fmt.Sprintf("%d MB", mapping.Size/(1024*1024)),
// humanize.Bytes(mapping.Size),
fmt.Sprintf("%08X -> %08X", mapping.Address, mapping.Address+mapping.Size),
fmt.Sprintf("%08X -> %08X", mapping.FileOffset, mapping.FileOffset+mapping.Size),
})
}
tbl := table.NewStringBuilderTableWriter(tableString)
tbl.SetHeader([]string{"Seg", "InitProt", "MaxProt", "Size", "Address", "File Offset"})
tbl.SetBorders(nil)
tbl.SetCenterSeparator("|")
tbl.AppendBulk(mdata)
tbl.SetAlignment(1) // ALIGN_LEFT
tbl.Render()
return tableString.String()
}
func (mapping CacheMappingWithSlideInfo) String() string {
tableString := &strings.Builder{}
mdata := [][]string{}
slideInfoSize := fmt.Sprintf("%#08x -> %#08x", mapping.SlideInfoOffset, mapping.SlideInfoOffset+mapping.SlideInfoSize)
if mapping.SlideInfoSize == 0 {
slideInfoSize = fmt.Sprintf("%#08x", mapping.SlideInfoOffset)
}
mdata = append(mdata, []string{
mapping.Name,
mapping.InitProt.String(),
mapping.MaxProt.String(),
fmt.Sprintf("%#08x (%s)", mapping.Size, humanize.Bytes(mapping.Size)),
fmt.Sprintf("%#08x", mapping.Address),
fmt.Sprintf("%#08x", mapping.FileOffset),
slideInfoSize,
fmt.Sprintf("%d", mapping.Flags),
})
tbl := table.NewStringBuilderTableWriter(tableString)
tbl.SetHeader([]string{"Seg", "InitProt", "MaxProt", "Size", "Address", "File Offset", "Slide Info Offset", "Flags"})
tbl.SetBorders(nil)
tbl.SetCenterSeparator("|")
tbl.AppendBulk(mdata)
tbl.SetAlignment(1)
tbl.Render()
return tableString.String()
}
func (mappings cacheMappingsWithSlideInfo) String(slideVersion uint32, verbose bool) string {
tableString := &strings.Builder{}
mdata := [][]string{}
for _, mapping := range mappings {
mappingAddr := fmt.Sprintf("%#08x", mapping.Address)
mappingOff := fmt.Sprintf("%#08x", mapping.FileOffset)
mappingInitProt := mapping.InitProt.String()
mappingMaxProt := mapping.MaxProt.String()
mappingFlags := mapping.Flags.String()
if verbose {
mappingAddr = fmt.Sprintf("%#08x -> %#08x", mapping.Address, mapping.Address+mapping.Size)
mappingOff = fmt.Sprintf("%#08x -> %#08x", mapping.FileOffset, mapping.FileOffset+mapping.Size)
mappingInitProt += fmt.Sprintf(" (%d)", mapping.InitProt)
mappingMaxProt += fmt.Sprintf(" (%d)", mapping.MaxProt)
mappingFlags += fmt.Sprintf(" (%d)", mapping.Flags)
}
var slideInfoSize string
if mapping.SlideInfoSize > 0 {
slideInfoSize = fmt.Sprintf("%#08x -> %#08x", mapping.SlideInfoOffset, mapping.SlideInfoOffset+mapping.SlideInfoSize)
}
mdata = append(mdata, []string{
mapping.Name,
mappingInitProt,
mappingMaxProt,
fmt.Sprintf("%#08x (%s)", mapping.Size, humanize.Bytes(mapping.Size)),
mappingAddr,
mappingOff,
slideInfoSize,
mappingFlags,
})
}
tbl := table.NewStringBuilderTableWriter(tableString)
tbl.SetHeader([]string{"Seg", "InitProt", "MaxProt", "Size", "Address", "File Offset", fmt.Sprintf("Slide Info (v%d) Offset", slideVersion), "Flags"})
tbl.SetBorders(nil)
tbl.SetCenterSeparator("|")
tbl.AppendBulk(mdata)
tbl.SetAlignment(1)
tbl.Render()
return tableString.String()
}
func (images cacheImages) Print() {
fmt.Println("Images")
fmt.Println("======")
for idx, image := range images {
fmt.Printf("%d:\t%08x %s\n", idx+1, image.Info.Address, image.Name)
}
}
func (f *File) String(verbose bool) string {
var slideVersion uint32
if f.SlideInfo != nil {
slideVersion = f.SlideInfo.GetVersion()
}
return fmt.Sprintf(
"Header\n"+
"======\n"+
"Magic = \"%s\"\n"+
"Platform = %s\n"+
"%s"+ // alt platform
"%s"+ // os version
"%s"+ // alt os version
"%s"+ // format
"%s"+ // max slide
"%s"+ // image count
"%s\n"+ // sub cache info
"%s"+ // shared region
"%s"+ // local symbols
"%s"+ // image text info
"%s"+ // slideInfo
"%s"+ // branchPools
"%s"+ // accelerateInfo
"%s"+ // patch info
"%s"+ // progClosures
"%s"+ // progClosuresTrie
"%s"+ // prebuiltLoaderSet
"%s"+ // prebuiltLoaderSet pool
"%s"+ // dynamic config
"%s"+ // objcOpts
"%s"+ // swiftOpts
"%s"+ // dyld/_dyld_start addrs
"%s", // mappings
f.Headers[f.UUID].Magic.String(),
f.Headers[f.UUID].Platform,
f.getAltPlatform(f.UUID),
f.getOSVersion(f.UUID),
f.getAltOSVersion(f.UUID),
f.getFormatVersion(f.UUID),
f.getMaxSlide(f.UUID),
f.getImageCount(f.UUID),
f.getSubCacheCount(),
f.getSharedRegion(f.UUID),
f.getLocalSymbols(),
f.getImagesTextInfo(f.UUID),
f.getSlideInfo(f.UUID, slideVersion),
f.getBranchPools(f.UUID),
f.getAccelerateInfo(f.UUID),
f.getPatchInfo(f.UUID),
f.getProgClosures(f.UUID),
f.getProgClosuresTrie(f.UUID),
f.getPrebuiltLoaderSet(f.UUID),
f.getPrebuiltLoaderSetPool(f.UUID),
f.getDynamicConfig(f.UUID),
f.getObjcOpts(f.UUID),
f.getSwiftOpts(f.UUID),
f.getDyldInfo(f.UUID),
f.getMappings(slideVersion, verbose),
)
}
func (f *File) getFormatVersion(uuid types.UUID) string {
var output string
if f.Headers[uuid].FormatVersion > 0 {
output = fmt.Sprintf("Format = %s\n", f.Headers[uuid].FormatVersion.String())
}
return output
}
func (f *File) getOSVersion(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].OsVersion > 0 {
output = fmt.Sprintf("OS Version = %s\n", f.Headers[uuid].OsVersion.String())
}
return output
}
func (f *File) getAltOSVersion(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].AltOsVersion > 0 {
output = fmt.Sprintf("Alt OS Version = %s\n", f.Headers[uuid].AltOsVersion.String())
}
return output
}
func (f *File) getAltPlatform(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].AltPlatform > 0 {
output = fmt.Sprintf("Alt Platform = %s\n", f.Headers[uuid].AltPlatform.String())
}
return output
}
func (f *File) getMaxSlide(uuid types.UUID) string {
var output string
if f.Headers[uuid].MaxSlide > 0 {
output = fmt.Sprintf("Max Slide = %s\n", f.Headers[uuid].MaxSlide)
}
return output
}
func (f *File) getImageCount(uuid types.UUID) string {
var output string
if f.Headers[uuid].ImagesCountOld > 0 {
output = fmt.Sprintf("Num Images = %d\n", f.Headers[uuid].ImagesCountOld)
} else {
output = fmt.Sprintf("Num Images = %d\n", f.Headers[uuid].ImagesCount)
}
return output
}
func (f *File) getSubCacheCount() string {
var output string
if f.IsDyld4 {
output = fmt.Sprintf("Num SubCaches = %d\n", f.Headers[f.UUID].SubCacheArrayCount)
}
return output
}
func (f *File) getLocalSymbols() string {
var output string
if f.LocalSymInfo.NListFileOffset > 0 && f.LocalSymInfo.StringsFileOffset > 0 {
output = fmt.Sprintf(
"Local Symbols (nlist array): %3dMB, offset: 0x%09X -> 0x%09X\n"+
"Local Symbols (string pool): %3dMB, offset: 0x%09X -> 0x%09X\n",
f.LocalSymInfo.NListByteSize/(1024*1024),
f.LocalSymInfo.NListFileOffset,
f.LocalSymInfo.NListFileOffset+f.LocalSymInfo.NListByteSize,
f.LocalSymInfo.StringsSize/(1024*1024),
f.LocalSymInfo.StringsFileOffset,
f.LocalSymInfo.StringsFileOffset+f.LocalSymInfo.StringsSize,
)
}
return output
}
func (f *File) getImagesTextInfo(uuid types.UUID) string {
var output string
imagesCount := f.Headers[uuid].ImagesCountOld
imageHumanSize := int(f.Headers[uuid].ImagesCountOld) * binary.Size(CacheImageInfo{}) / 1024
imagesOffset := int(f.Headers[uuid].ImagesOffsetOld)
imagesSize := int(imagesCount) * binary.Size(CacheImageInfo{})
if f.Headers[uuid].ImagesOffsetOld == 0 && f.Headers[uuid].ImagesCountOld == 0 {
imagesCount = f.Headers[uuid].ImagesCount
imageHumanSize = int(f.Headers[uuid].ImagesCount) * binary.Size(CacheImageInfo{}) / 1024
imagesOffset = int(f.Headers[uuid].ImagesOffset)
imagesSize = int(f.Headers[uuid].ImagesCount) * binary.Size(CacheImageInfo{})
}
if imagesCount > 0 || imagesOffset > 0 || imagesSize > 0 {
output = fmt.Sprintf("ImagesText Info: %3dKB, offset: 0x%09X -> 0x%09X\n", imageHumanSize, imagesOffset, imagesOffset+imagesSize)
}
return output
}
func (f *File) getSlideInfo(uuid types.UUID, slideVersion uint32) string {
var output string
if f.Headers[uuid].SlideInfoOffsetUnused > 0 {
output = fmt.Sprintf("Slide Info (v%d): %4dKB, offset: 0x%09X -> 0x%09X\n",
slideVersion,
f.Headers[uuid].SlideInfoSizeUnused/1024,
f.Headers[uuid].SlideInfoOffsetUnused,
f.Headers[uuid].SlideInfoOffsetUnused+f.Headers[uuid].SlideInfoSizeUnused)
}
return output
}
func (f *File) getBranchPools(uuid types.UUID) string {
var output string
if f.Headers[uuid].BranchPoolsOffset > 0 {
output = fmt.Sprintf("Branch Pool: %3dMB, offset: 0x%09X -> 0x%09X\n",
binary.Size(f.BranchPools),
f.Headers[uuid].BranchPoolsOffset,
int(f.Headers[uuid].BranchPoolsOffset)+binary.Size(f.BranchPools))
}
return output
}
func (f *File) getAccelerateInfo(uuid types.UUID) string {
var output string
if !f.IsDyld4 {
if f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr > 0 {
output = fmt.Sprintf("Accelerate Tab: %3dKB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr/1024,
f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr,
f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr+f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr)
}
}
return output
}
func (f *File) getDyldInfo(uuid types.UUID) string {
var output string
if f.IsDyld4 {
output = fmt.Sprintf("\ndyld MachO: address: 0x%09X\n", f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr)
output += fmt.Sprintf("_dyld_start: address: 0x%09X\n", f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr)
}
return output
}
func (f *File) getPatchInfo(uuid types.UUID) string {
var output string
if f.Headers[uuid].PatchInfoAddr > 0 || f.Headers[uuid].PatchInfoSize > 0 {
output = fmt.Sprintf("Patch Info: %3dMB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].PatchInfoSize/(1024*1024),
f.Headers[uuid].PatchInfoAddr, f.Headers[uuid].PatchInfoAddr+f.Headers[uuid].PatchInfoSize)
}
return output
}
func (f *File) getProgClosures(uuid types.UUID) string {
var output string
if f.Headers[uuid].ProgClosuresAddr > 0 {
output = fmt.Sprintf("Closures: %3dMB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].ProgClosuresSize/(1024*1024),
f.Headers[uuid].ProgClosuresAddr,
f.Headers[uuid].ProgClosuresAddr+f.Headers[uuid].ProgClosuresSize)
}
return output
}
func (f *File) getProgClosuresTrie(uuid types.UUID) string {
var output string
if f.Headers[uuid].ProgClosuresTrieAddr > 0 {
output = fmt.Sprintf("Closures Trie: %3dKB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].ProgClosuresTrieSize/1024,
f.Headers[uuid].ProgClosuresTrieAddr,
f.Headers[uuid].ProgClosuresTrieAddr+f.Headers[uuid].ProgClosuresTrieSize)
}
return output
}
func (f *File) getPrebuiltLoaderSet(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].DylibsPblSetAddr > 0 {
output = fmt.Sprintf("Prebuilt Loader Set: %3dMB, address: 0x%09X -> 0x%09X\n",
(f.Headers[uuid].ProgramsPblSetPoolAddr-f.Headers[uuid].DylibsPblSetAddr)/1024/1024,
f.Headers[uuid].DylibsPblSetAddr,
f.Headers[uuid].ProgramsPblSetPoolAddr-f.Headers[uuid].DylibsPblSetAddr)
}
return output
}
func (f *File) getPrebuiltLoaderSetPool(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].ProgramsPblSetPoolAddr > 0 {
output = fmt.Sprintf("Prebuilt Loader Pool: %3dMB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].ProgramsPblSetPoolSize/(1024*1024),
f.Headers[uuid].ProgramsPblSetPoolAddr,
f.Headers[uuid].ProgramsPblSetPoolAddr+f.Headers[uuid].ProgramsPblSetPoolSize)
}
return output
}
func (f *File) getObjcOpts(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].ObjcOptsOffset > 0 {
output = fmt.Sprintf("ObjC Opts: %3dB, offset: 0x%09X -> 0x%09X\n",
f.Headers[uuid].ObjcOptsSize,
f.Headers[uuid].ObjcOptsOffset,
f.Headers[uuid].ObjcOptsOffset+f.Headers[uuid].ObjcOptsSize)
}
return output
}
func (f *File) getSwiftOpts(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].SwiftOptsOffset > 0 {
output = fmt.Sprintf("Swift Opts: %3dkB, offset: 0x%09X -> 0x%09X\n",
f.Headers[uuid].SwiftOptsSize/(1024),
f.Headers[uuid].SwiftOptsOffset,
f.Headers[uuid].SwiftOptsOffset+f.Headers[uuid].SwiftOptsSize)
}
return output
}
func (f *File) getDynamicConfig(uuid types.UUID) string {
var output string
if f.IsDyld4 && f.Headers[uuid].SwiftOptsOffset > 0 {
output = fmt.Sprintf("Dynamic Config: %3dkB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].DynamicDataMaxSize/(1024),
f.Headers[uuid].SharedRegionStart+f.Headers[uuid].DynamicDataOffset,
f.Headers[uuid].SharedRegionStart+f.Headers[uuid].DynamicDataOffset+f.Headers[uuid].DynamicDataMaxSize)
}
return output
}
func (f *File) getSharedRegion(uuid types.UUID) string {
var output string
if f.Headers[uuid].SharedRegionSize > 0 {
output = fmt.Sprintf("Shared Region: %3dGB, address: 0x%09X -> 0x%09X\n",
f.Headers[uuid].SharedRegionSize/(1024*1024*1024),
f.Headers[uuid].SharedRegionStart,
f.Headers[uuid].SharedRegionStart+f.Headers[uuid].SharedRegionSize)
}
return output
}
func (f *File) getCodeSignature(uuid types.UUID) string {
var output string
if f.Headers[uuid].CodeSignatureSize > 0 {
output = fmt.Sprintf("Code Signature: %3dMB, offset: 0x%09X -> 0x%09X\n",
f.Headers[uuid].CodeSignatureSize/(1024*1024),
f.Headers[uuid].CodeSignatureOffset,
f.Headers[uuid].CodeSignatureOffset+f.Headers[uuid].CodeSignatureSize)
}
return output
}
func (f *File) getRosetta(uuid types.UUID) string {
var output string
if f.Headers[uuid].RosettaReadOnlySize > 0 {
output += fmt.Sprintf("Rosetta RO: %s, address: 0x%09X -> 0x%09X\n",
humanize.Bytes(f.Headers[uuid].RosettaReadOnlySize),
f.Headers[uuid].RosettaReadOnlyAddr,
f.Headers[uuid].RosettaReadOnlyAddr+f.Headers[uuid].RosettaReadOnlySize)
}
if f.Headers[uuid].RosettaReadWriteSize > 0 {
output += fmt.Sprintf("Rosetta RW: %s, address: 0x%09X -> 0x%09X\n",
humanize.Bytes(f.Headers[uuid].RosettaReadWriteSize),
f.Headers[uuid].RosettaReadWriteAddr,
f.Headers[uuid].RosettaReadWriteAddr+f.Headers[uuid].RosettaReadWriteSize)
}
return output
}
func (f *File) getMappings(slideVersion uint32, verbose bool) string {
var output strings.Builder
if f.Headers[f.UUID].SlideInfoOffsetUnused > 0 {
for uuid, cacheMappings := range f.Mappings {
output.WriteString(fmt.Sprintf("\n> SubCache %s", uuid))
output.WriteString(cacheMappings.String())
}
} else {
// sort mappings map by address
uuids := make([]types.UUID, 0, len(f.MappingsWithSlideInfo))
for u := range f.MappingsWithSlideInfo {
uuids = append(uuids, u)
}
sort.SliceStable(uuids, func(i, j int) bool {
return f.MappingsWithSlideInfo[uuids[i]][0].Address < f.MappingsWithSlideInfo[uuids[j]][0].Address
})
for _, uuid := range uuids {
if uuid == f.symUUID {
output.WriteString(fmt.Sprintf("\n> Cache (.symbols) UUID: %s\n\n", uuid))
} else {
ext, err := f.GetSubCacheExtensionFromUUID(uuid)
if err != nil {
output.WriteString(fmt.Sprintf("\n> Cache UUID: %s\n\n", uuid))
} else {
output.WriteString(fmt.Sprintf("\n> Cache (%s) UUID: %s\n\n", ext, uuid))
}
}
output.WriteString("Mappings\n--------\n\n")
output.WriteString(f.MappingsWithSlideInfo[uuid].String(slideVersion, verbose))
output.WriteString(fmt.Sprintln())
output.WriteString(f.getCodeSignature(uuid))
output.WriteString(f.getRosetta(uuid))
}
}
return output.String()
}