Files
ipsw/pkg/dyld/objc_shash.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
}