mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
436 lines
11 KiB
Go
436 lines
11 KiB
Go
package dyld
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/blacktop/go-macho/types"
|
|
)
|
|
|
|
/*
|
|
* objc_stringhash_t - Precomputed perfect hash table of strings <dyld/include/objc-shared-cache.h>
|
|
*
|
|
* Base class for precomputed selector table and class table.
|
|
*/
|
|
|
|
type strHashType uint8
|
|
|
|
const (
|
|
selopt strHashType = iota
|
|
clsopt
|
|
)
|
|
|
|
type stringHash struct {
|
|
Capacity uint32
|
|
Occupied uint32
|
|
Shift uint32
|
|
Mask uint32
|
|
_ uint32 // was zero
|
|
_ uint32 // alignment pad
|
|
Salt uint64
|
|
Scramble [256]uint32
|
|
}
|
|
|
|
type stringHashV16 struct {
|
|
Version uint32
|
|
Capacity uint32
|
|
Occupied uint32
|
|
Shift uint32
|
|
Mask uint32
|
|
_ uint32 // was zero
|
|
Salt uint64
|
|
Scramble [256]uint32
|
|
}
|
|
|
|
// StringHash struct
|
|
type StringHash struct {
|
|
Type strHashType
|
|
FileOffset int64
|
|
shash any
|
|
Tab []byte /* tab[mask+1] (always power-of-2) */
|
|
CheckBytes []byte /* check byte for each string */
|
|
Offsets []int32 /* offsets from &capacity to cstrings */
|
|
ObjectOffsets []ObjectData /* offsets from &capacity to cstrings */
|
|
DuplicateCount uint32
|
|
DuplicateOffsets []ObjectData
|
|
|
|
hdrRO *objc_headeropt_ro_t
|
|
hdrRW *objc_headeropt_rw_t
|
|
opt Optimization
|
|
dylibMap map[uint16]string
|
|
}
|
|
|
|
// Capacity returns the Capacity
|
|
func (s *StringHash) Capacity() uint32 {
|
|
switch h := s.shash.(type) {
|
|
case stringHash:
|
|
return h.Capacity
|
|
case stringHashV16:
|
|
return h.Capacity
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Occupied returns the Occupied
|
|
func (s *StringHash) Occupied() uint32 {
|
|
switch h := s.shash.(type) {
|
|
case stringHash:
|
|
return h.Occupied
|
|
case stringHashV16:
|
|
return h.Occupied
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Shift returns the Shift
|
|
func (s *StringHash) Shift() uint32 {
|
|
switch h := s.shash.(type) {
|
|
case stringHash:
|
|
return h.Shift
|
|
case stringHashV16:
|
|
return h.Shift
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Mask returns the Mask
|
|
func (s *StringHash) Mask() uint32 {
|
|
switch h := s.shash.(type) {
|
|
case stringHash:
|
|
return h.Mask
|
|
case stringHashV16:
|
|
return h.Mask
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Salt returns the Salt
|
|
func (s *StringHash) Salt() uint64 {
|
|
switch h := s.shash.(type) {
|
|
case stringHash:
|
|
return h.Salt
|
|
case stringHashV16:
|
|
return h.Salt
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Scramble returns the Scramble
|
|
func (s *StringHash) Scramble() [256]uint32 {
|
|
switch h := s.shash.(type) {
|
|
case stringHash:
|
|
return h.Scramble
|
|
case stringHashV16:
|
|
return h.Scramble
|
|
}
|
|
return [256]uint32{0}
|
|
}
|
|
|
|
func (s *StringHash) Read(r io.ReadSeeker) error {
|
|
if s.opt.GetVersion() >= 16 {
|
|
var sh stringHashV16
|
|
if err := binary.Read(r, binary.LittleEndian, &sh); err != nil {
|
|
return fmt.Errorf("failed to read %T: %v", sh, err)
|
|
}
|
|
s.shash = sh
|
|
} else {
|
|
var sh stringHash
|
|
if err := binary.Read(r, binary.LittleEndian, &sh); err != nil {
|
|
return fmt.Errorf("failed to read %T: %v", sh, err)
|
|
}
|
|
s.shash = sh
|
|
}
|
|
|
|
s.Tab = make([]byte, s.Mask()+1)
|
|
if err := binary.Read(r, binary.LittleEndian, &s.Tab); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.CheckBytes = make([]byte, s.Capacity())
|
|
if err := binary.Read(r, binary.LittleEndian, &s.CheckBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.Offsets = make([]int32, s.Capacity())
|
|
if err := binary.Read(r, binary.LittleEndian, &s.Offsets); err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.Type == clsopt {
|
|
s.ObjectOffsets = make([]ObjectData, s.Capacity())
|
|
if err := binary.Read(r, binary.LittleEndian, &s.ObjectOffsets); err != nil {
|
|
if err == io.ErrUnexpectedEOF { // FIXME: gross hack (selectors don't use these fields)
|
|
s.ObjectOffsets = nil
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &s.DuplicateCount); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.DuplicateOffsets = make([]ObjectData, s.DuplicateCount)
|
|
if err := binary.Read(r, binary.LittleEndian, &s.DuplicateOffsets); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s StringHash) String() string {
|
|
return fmt.Sprintf(
|
|
"FileOffset = %X\n"+
|
|
"Capacity = %X\n"+
|
|
"Occupied = %X\n"+
|
|
"Shift = %X\n"+
|
|
"Mask = %X\n"+
|
|
"Salt = %016X\n",
|
|
s.FileOffset,
|
|
s.Capacity(),
|
|
s.Occupied(),
|
|
s.Shift(),
|
|
s.Mask(),
|
|
s.Salt())
|
|
}
|
|
|
|
type ObjectData uint64
|
|
|
|
func (o ObjectData) IsDuplicate() bool {
|
|
return types.ExtractBits(uint64(o), 0, 1) != 0
|
|
}
|
|
func (o ObjectData) DuplicateIndex() uint64 {
|
|
return types.ExtractBits(uint64(o), 1, 47)
|
|
}
|
|
func (o ObjectData) DuplicateCount() uint16 {
|
|
return uint16(types.ExtractBits(uint64(o), 48, 16))
|
|
}
|
|
func (o ObjectData) ObjectCacheOffset() uint64 {
|
|
return types.ExtractBits(uint64(o), 1, 47)
|
|
}
|
|
func (o ObjectData) DylibObjCIndex() uint16 {
|
|
return uint16(types.ExtractBits(uint64(o), 48, 16))
|
|
}
|
|
func (o ObjectData) String() string {
|
|
if o.IsDuplicate() {
|
|
return fmt.Sprintf("Duplicate: count=%#x, index=%d", o.DuplicateCount(), o.DuplicateIndex())
|
|
}
|
|
return fmt.Sprintf("cache_offset=%#x, dylib_index=%d", o.ObjectCacheOffset(), o.DylibObjCIndex())
|
|
}
|
|
|
|
/*
|
|
--------------------------------------------------------------------
|
|
mix -- mix 3 64-bit values reversibly.
|
|
mix() takes 48 machine instructions, but only 24 cycles on a superscalar
|
|
|
|
machine (like Intel's new MMX architecture). It requires 4 64-bit
|
|
registers for 4::2 parallelism.
|
|
|
|
All 1-bit deltas, all 2-bit deltas, all deltas composed of top bits of
|
|
|
|
(a,b,c), and all deltas of bottom bits were tested. All deltas were
|
|
tested both on random keys and on keys that were nearly all zero.
|
|
These deltas all cause every bit of c to change between 1/3 and 2/3
|
|
of the time (well, only 113/400 to 287/400 of the time for some
|
|
2-bit delta). These deltas all cause at least 80 bits to change
|
|
among (a,b,c) when the mix is run either forward or backward (yes it
|
|
is reversible).
|
|
|
|
This implies that a hash using mix64 has no funnels. There may be
|
|
|
|
characteristics with 3-bit deltas or bigger, I didn't test for
|
|
those.
|
|
|
|
--------------------------------------------------------------------
|
|
*/
|
|
func mix64(a, b, c *uint64) {
|
|
*a = (*a - *b - *c) ^ (*c >> 43)
|
|
*b = (*b - *c - *a) ^ (*a << 9)
|
|
*c = (*c - *a - *b) ^ (*b >> 8)
|
|
*a = (*a - *b - *c) ^ (*c >> 38)
|
|
*b = (*b - *c - *a) ^ (*a << 23)
|
|
*c = (*c - *a - *b) ^ (*b >> 5)
|
|
*a = (*a - *b - *c) ^ (*c >> 35)
|
|
*b = (*b - *c - *a) ^ (*a << 49)
|
|
*c = (*c - *a - *b) ^ (*b >> 11)
|
|
*a = (*a - *b - *c) ^ (*c >> 12)
|
|
*b = (*b - *c - *a) ^ (*a << 18)
|
|
*c = (*c - *a - *b) ^ (*b >> 22)
|
|
}
|
|
|
|
/*
|
|
--------------------------------------------------------------------
|
|
hash() -- hash a variable-length key into a 64-bit value
|
|
k : the key (the unaligned variable-length array of bytes)
|
|
len : the length of the key, counting by bytes
|
|
level : can be any 8-byte value
|
|
Returns a 64-bit value. Every bit of the key affects every bit of
|
|
the return value. No funnels. Every 1-bit and 2-bit delta achieves
|
|
avalanche. About 41+5len instructions.
|
|
|
|
The best hash table sizes are powers of 2. There is no need to do
|
|
mod a prime (mod is sooo slow!). If you need less than 64 bits,
|
|
use a bitmask. For example, if you need only 10 bits, do
|
|
h = (h & hashmask(10));
|
|
In which case, the hash table should have hashsize(10) elements.
|
|
|
|
If you are hashing n strings (uint8_t **)k, do it like this:
|
|
for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);
|
|
|
|
By Bob Jenkins, Jan 4 1997. bob_jenkins@burtleburtle.net. You may
|
|
use this code any way you wish, private, educational, or commercial,
|
|
but I would appreciate if you give me credit.
|
|
|
|
See http://burtleburtle.net/bob/hash/evahash.html
|
|
Use for hash table lookup, or anything where one collision in 2^^64
|
|
is acceptable. Do NOT use for cryptographic purposes.
|
|
--------------------------------------------------------------------
|
|
*/
|
|
|
|
func lookup8(k []byte, level uint64) uint64 {
|
|
// uint8_t *k; /* the key */
|
|
// uint64_t length; /* the length of the key */
|
|
// uint64_t level; /* the previous hash, or an arbitrary value */
|
|
var a, b, c uint64
|
|
var length int
|
|
|
|
/* Set up the internal state */
|
|
length = len(k)
|
|
a = level
|
|
b = level /* the previous hash value */
|
|
c = 0x9e3779b97f4a7c13 /* the golden ratio; an arbitrary value */
|
|
p := 0
|
|
/*---------------------------------------- handle most of the key */
|
|
for length >= 24 {
|
|
a += uint64(k[p+0]) + (uint64(k[p+1]) << 8) + (uint64(k[p+2]) << 16) + (uint64(k[p+3]) << 24) + (uint64(k[p+4]) << 32) + (uint64(k[p+5]) << 40) + (uint64(k[p+6]) << 48) + (uint64(k[p+7]) << 56)
|
|
b += uint64(k[p+8]) + (uint64(k[p+9]) << 8) + (uint64(k[p+10]) << 16) + (uint64(k[p+11]) << 24) + (uint64(k[p+12]) << 32) + (uint64(k[p+13]) << 40) + (uint64(k[p+14]) << 48) + (uint64(k[p+15]) << 56)
|
|
c += uint64(k[p+16]) + (uint64(k[p+17]) << 8) + (uint64(k[p+18]) << 16) + (uint64(k[p+19]) << 24) + (uint64(k[p+20]) << 32) + (uint64(k[p+21]) << 40) + (uint64(k[p+22]) << 48) + (uint64(k[p+23]) << 56)
|
|
mix64(&a, &b, &c)
|
|
p += 24
|
|
length -= 24
|
|
}
|
|
|
|
/*------------------------------------- handle the last 23 bytes */
|
|
c += uint64(len(k))
|
|
switch length { /* all the case statements fall through */
|
|
case 23:
|
|
c += (uint64(k[p+22]) << 56)
|
|
fallthrough
|
|
case 22:
|
|
c += (uint64(k[p+21]) << 48)
|
|
fallthrough
|
|
case 21:
|
|
c += (uint64(k[p+20]) << 40)
|
|
fallthrough
|
|
case 20:
|
|
c += (uint64(k[p+19]) << 32)
|
|
fallthrough
|
|
case 19:
|
|
c += (uint64(k[p+18]) << 24)
|
|
fallthrough
|
|
case 18:
|
|
c += (uint64(k[p+17]) << 16)
|
|
fallthrough
|
|
case 17:
|
|
c += (uint64(k[p+16]) << 8)
|
|
fallthrough
|
|
/* the first byte of c is reserved for the length */
|
|
case 16:
|
|
b += (uint64(k[p+15]) << 56)
|
|
fallthrough
|
|
case 15:
|
|
b += (uint64(k[p+14]) << 48)
|
|
fallthrough
|
|
case 14:
|
|
b += (uint64(k[p+13]) << 40)
|
|
fallthrough
|
|
case 13:
|
|
b += (uint64(k[p+12]) << 32)
|
|
fallthrough
|
|
case 12:
|
|
b += (uint64(k[p+11]) << 24)
|
|
fallthrough
|
|
case 11:
|
|
b += (uint64(k[p+10]) << 16)
|
|
fallthrough
|
|
case 10:
|
|
b += (uint64(k[p+9]) << 8)
|
|
fallthrough
|
|
case 9:
|
|
b += (uint64(k[p+8]))
|
|
fallthrough
|
|
case 8:
|
|
a += (uint64(k[p+7]) << 56)
|
|
fallthrough
|
|
case 7:
|
|
a += (uint64(k[p+6]) << 48)
|
|
fallthrough
|
|
case 6:
|
|
a += (uint64(k[p+5]) << 40)
|
|
fallthrough
|
|
case 5:
|
|
a += (uint64(k[p+4]) << 32)
|
|
fallthrough
|
|
case 4:
|
|
a += (uint64(k[p+3]) << 24)
|
|
fallthrough
|
|
case 3:
|
|
a += (uint64(k[p+2]) << 16)
|
|
fallthrough
|
|
case 2:
|
|
a += (uint64(k[p+1]) << 8)
|
|
fallthrough
|
|
case 1:
|
|
a += uint64(k[p+0])
|
|
/* case 0: nothing left to add */
|
|
}
|
|
mix64(&a, &b, &c)
|
|
/*-------------------------------------------- report the result */
|
|
return c
|
|
}
|
|
|
|
// The check bytes are used to reject strings that aren't in the table
|
|
// without paging in the table's cstring data. This checkbyte calculation
|
|
// catches 4785/4815 rejects when launching Safari; a perfect checkbyte
|
|
// would catch 4796/4815.
|
|
func checkbyte(key []byte) uint8 {
|
|
return ((key[0] & 0x7) << 5) | (uint8(len(key)) & 0x1f)
|
|
}
|
|
|
|
func (s StringHash) hash(key []byte) uint32 {
|
|
val := lookup8(key, s.Salt())
|
|
if s.Shift() == 64 {
|
|
return uint32(0)
|
|
}
|
|
index := (val >> uint64(s.Shift())) ^ uint64(s.Scramble()[s.Tab[(val&uint64(s.Mask()))]])
|
|
return uint32(index)
|
|
}
|
|
|
|
func (s StringHash) getIndex(keyStr string) (uint32, error) {
|
|
key := []byte(keyStr)
|
|
|
|
h := s.hash(key)
|
|
|
|
// Use check byte to reject without paging in the table's cstrings
|
|
hCheck := s.CheckBytes[h]
|
|
keyCheck := checkbyte(key)
|
|
if hCheck != keyCheck {
|
|
return 0, fmt.Errorf("INDEX_NOT_FOUND")
|
|
}
|
|
|
|
offset := s.Offsets[h]
|
|
if offset == 0 {
|
|
return 0, fmt.Errorf("INDEX_NOT_FOUND")
|
|
}
|
|
// result = (const char *)this + offset
|
|
// TODO: fix me
|
|
// result := "FIX ME"
|
|
// if result != string(key) {
|
|
// return 0, fmt.Errorf("INDEX_NOT_FOUND")
|
|
// }
|
|
|
|
return h, nil
|
|
}
|