module Unison.Runtime.Bundle.MachO
( isMachO,
programSection,
setProgramSection,
)
where
import Control.Monad (unless, when)
import Data.Bits (shiftL, shiftR, (.&.))
import Data.ByteString qualified as B
import Data.List (find, sortOn)
import Data.Word (Word32, Word64)
isMachO :: B.ByteString -> Bool
isMachO :: ByteString -> Bool
isMachO ByteString
bytes = ByteString -> Int
B.length ByteString
bytes Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
headerSize Bool -> Bool -> Bool
&& ByteString -> Int -> Word32
u32 ByteString
bytes Int
0 Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
mhMagic64
programSection :: B.ByteString -> Maybe B.ByteString
programSection :: ByteString -> Maybe ByteString
programSection ByteString
bytes = do
(Segment
_, Int
sect) <- ByteString -> Maybe (Segment, Int)
findSection ByteString
bytes
let off :: Int
off = Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionOffset))
size :: Int
size = Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word64
u64 ByteString
bytes (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionSize))
ByteString -> Maybe ByteString
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Int -> ByteString -> ByteString
B.take Int
size (Int -> ByteString -> ByteString
B.drop Int
off ByteString
bytes))
setProgramSection :: B.ByteString -> B.ByteString -> Either String B.ByteString
setProgramSection :: ByteString -> ByteString -> Either String ByteString
setProgramSection ByteString
bytes ByteString
contents = do
Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (ByteString -> Bool
isMachO ByteString
bytes) (String -> Either String ()
forall a b. a -> Either a b
Left String
"not a 64-bit Mach-O executable")
(Segment
uni, Int
sect) <- Either String (Segment, Int)
-> ((Segment, Int) -> Either String (Segment, Int))
-> Maybe (Segment, Int)
-> Either String (Segment, Int)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Either String (Segment, Int)
forall a b. a -> Either a b
Left String
"no __UNISON,__uc section: this unisonrt was linked without one") (Segment, Int) -> Either String (Segment, Int)
forall a b. b -> Either a b
Right (ByteString -> Maybe (Segment, Int)
findSection ByteString
bytes)
Segment
linkEdit <- Either String Segment
-> (Segment -> Either String Segment)
-> Maybe Segment
-> Either String Segment
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Either String Segment
forall a b. a -> Either a b
Left String
"no __LINKEDIT segment") Segment -> Either String Segment
forall a b. b -> Either a b
Right (ByteString -> ByteString -> Maybe Segment
findSegment ByteString
"__LINKEDIT" ByteString
bytes)
let inFile :: [Segment]
inFile = (Segment -> Word64) -> [Segment] -> [Segment]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn Segment -> Word64
segFileOff [Segment
s | Segment
s <- ByteString -> [Segment]
segments ByteString
bytes, Segment -> Word64
segFileSize Segment
s Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
> Word64
0]
Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Segment -> ByteString) -> [Segment] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map Segment -> ByteString
segName (Int -> [Segment] -> [Segment]
forall a. Int -> [a] -> [a]
drop ([Segment] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Segment]
inFile Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2) [Segment]
inFile) [ByteString] -> [ByteString] -> Bool
forall a. Eq a => a -> a -> Bool
/= [ByteString
"__UNISON", ByteString
"__LINKEDIT"]) (Either String () -> Either String ())
-> Either String () -> Either String ()
forall a b. (a -> b) -> a -> b
$
String -> Either String ()
forall a b. a -> Either a b
Left String
"the __UNISON segment is not the one before __LINKEDIT in the file"
Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Segment -> Bool) -> [Segment] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Segment
s -> Segment -> ByteString
segName Segment
s ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
/= ByteString
"__LINKEDIT" Bool -> Bool -> Bool
&& Segment -> Word64
segVmAddr Segment
s Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
> Segment -> Word64
segVmAddr Segment
uni) (ByteString -> [Segment]
segments ByteString
bytes)) (Either String () -> Either String ())
-> Either String () -> Either String ()
forall a b. (a -> b) -> a -> b
$
String -> Either String ()
forall a b. a -> Either a b
Left String
"the __UNISON segment is not the one before __LINKEDIT in memory"
let
sectStart :: Word64
sectStart = Word32 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionOffset)) Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Segment -> Word64
segFileOff Segment
uni
newSize :: Word64
newSize = Word64 -> Word64
roundUpToPage (Word64
sectStart Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
B.length ByteString
contents))
newLinkEditOff :: Word64
newLinkEditOff = Segment -> Word64
segFileOff Segment
uni Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
newSize
newLinkEditAddr :: Word64
newLinkEditAddr = Segment -> Word64
segVmAddr Segment
uni Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
newSize
moved :: Integer
moved = Word64 -> Integer
forall a. Integral a => a -> Integer
toInteger Word64
newLinkEditOff Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Word64 -> Integer
forall a. Integral a => a -> Integer
toInteger (Segment -> Word64
segFileOff Segment
linkEdit)
patches :: [ByteString -> ByteString]
patches =
[ Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
uni Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentVmSize) Word64
newSize,
Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
uni Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileSize) Word64
newSize,
Int -> Word64 -> ByteString -> ByteString
put64 (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionSize) (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
B.length ByteString
contents)),
Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
linkEdit Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentVmAddr) Word64
newLinkEditAddr,
Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
linkEdit Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileOff) Word64
newLinkEditOff
]
[ByteString -> ByteString]
-> [ByteString -> ByteString] -> [ByteString -> ByteString]
forall a. [a] -> [a] -> [a]
++ [ Int -> Word32 -> ByteString -> ByteString
put32 Int
field (Integer -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Integer
forall a. Integral a => a -> Integer
toInteger Word32
old Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
moved))
| (Int
at, Word32
cmd) <- ByteString -> [(Int, Word32)]
loadCommands ByteString
bytes,
Int
field <- (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+) (Int -> Int) -> [Int] -> [Int]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Word32 -> [Int]
linkEditOffsetFields Word32
cmd,
let old :: Word32
old = ByteString -> Int -> Word32
u32 ByteString
bytes Int
field,
Word32
old Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word32
0
]
before :: ByteString
before = (ByteString -> (ByteString -> ByteString) -> ByteString)
-> ByteString -> [ByteString -> ByteString] -> ByteString
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\ByteString
bs ByteString -> ByteString
patch -> ByteString -> ByteString
patch ByteString
bs) (Int -> ByteString -> ByteString
B.take (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Segment -> Word64
segFileOff Segment
uni)) ByteString
bytes) [ByteString -> ByteString]
patches
segment :: ByteString
segment =
Int -> ByteString -> ByteString
B.take (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
sectStart) (Int -> ByteString -> ByteString
B.drop (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Segment -> Word64
segFileOff Segment
uni)) ByteString
bytes)
ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
contents
ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> Word8 -> ByteString
B.replicate (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
newSize Int -> Int -> Int
forall a. Num a => a -> a -> a
- Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
sectStart Int -> Int -> Int
forall a. Num a => a -> a -> a
- ByteString -> Int
B.length ByteString
contents) Word8
0
pure (ByteString
before ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
segment ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString -> ByteString
B.drop (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Segment -> Word64
segFileOff Segment
linkEdit)) ByteString
bytes)
linkEditOffsetFields :: Word32 -> [Int]
linkEditOffsetFields :: Word32 -> [Int]
linkEditOffsetFields Word32
cmd
| Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcSymtab = [Int
8, Int
16]
| Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcDysymtab = [Int
32, Int
40, Int
48, Int
56, Int
64, Int
72]
| Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcDyldInfo Bool -> Bool -> Bool
|| Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcDyldInfoOnly = [Int
8, Int
16, Int
24, Int
32, Int
40]
| Word32
cmd Word32 -> [Word32] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Word32]
linkEditDataCommands = [Int
8]
| Bool
otherwise = []
data Segment = Segment
{ Segment -> ByteString
segName :: B.ByteString,
Segment -> Int
segCmd :: Int,
Segment -> Word64
segVmAddr :: Word64,
Segment -> Word64
segFileOff :: Word64,
Segment -> Word64
segFileSize :: Word64,
Segment -> [(ByteString, Int)]
segSections :: [(B.ByteString, Int)]
}
findSection :: B.ByteString -> Maybe (Segment, Int)
findSection :: ByteString -> Maybe (Segment, Int)
findSection ByteString
bytes = do
Segment
uni <- ByteString -> ByteString -> Maybe Segment
findSegment ByteString
"__UNISON" ByteString
bytes
Int
sect <- ByteString -> [(ByteString, Int)] -> Maybe Int
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup ByteString
"__uc" (Segment -> [(ByteString, Int)]
segSections Segment
uni)
pure (Segment
uni, Int
sect)
findSegment :: B.ByteString -> B.ByteString -> Maybe Segment
findSegment :: ByteString -> ByteString -> Maybe Segment
findSegment ByteString
name ByteString
bytes = (Segment -> Bool) -> [Segment] -> Maybe Segment
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
name) (ByteString -> Bool) -> (Segment -> ByteString) -> Segment -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Segment -> ByteString
segName) (ByteString -> [Segment]
segments ByteString
bytes)
segments :: B.ByteString -> [Segment]
segments :: ByteString -> [Segment]
segments ByteString
bytes = [ByteString -> Int -> Segment
segmentAt ByteString
bytes Int
at | ByteString -> Bool
isMachO ByteString
bytes, (Int
at, Word32
cmd) <- ByteString -> [(Int, Word32)]
loadCommands ByteString
bytes, Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcSegment64]
segmentAt :: B.ByteString -> Int -> Segment
segmentAt :: ByteString -> Int -> Segment
segmentAt ByteString
bytes Int
at =
Segment
{ segName :: ByteString
segName = Int -> ByteString
name (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8),
segCmd :: Int
segCmd = Int
at,
segVmAddr :: Word64
segVmAddr = ByteString -> Int -> Word64
u64 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentVmAddr),
segFileOff :: Word64
segFileOff = ByteString -> Int -> Word64
u64 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileOff),
segFileSize :: Word64
segFileSize = ByteString -> Int -> Word64
u64 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileSize),
segSections :: [(ByteString, Int)]
segSections =
[ (Int -> ByteString
name Int
sect, Int
sect)
| Int
k <- [Int
0 .. Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentNSects)) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1],
let sect :: Int
sect = Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentCommandSize Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionHeaderSize Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
k
]
}
where
name :: Int -> ByteString
name Int
field = (Word8 -> Bool) -> ByteString -> ByteString
B.takeWhile (Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word8
0) (Int -> ByteString -> ByteString
B.take Int
16 (Int -> ByteString -> ByteString
B.drop Int
field ByteString
bytes))
loadCommands :: B.ByteString -> [(Int, Word32)]
loadCommands :: ByteString -> [(Int, Word32)]
loadCommands ByteString
bytes = Int -> Word32 -> [(Int, Word32)]
go Int
headerSize (ByteString -> Int -> Word32
u32 ByteString
bytes Int
16)
where
go :: Int -> Word32 -> [(Int, Word32)]
go :: Int -> Word32 -> [(Int, Word32)]
go Int
_ Word32
0 = []
go Int
at Word32
n = (Int
at, ByteString -> Int -> Word32
u32 ByteString
bytes Int
at) (Int, Word32) -> [(Int, Word32)] -> [(Int, Word32)]
forall a. a -> [a] -> [a]
: Int -> Word32 -> [(Int, Word32)]
go (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4))) (Word32
n Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
- Word32
1)
pageSize :: Word64
pageSize :: Word64
pageSize = Word64
0x4000
roundUpToPage :: Word64 -> Word64
roundUpToPage :: Word64 -> Word64
roundUpToPage Word64
n = (Word64
n Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
pageSize Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Word64
1) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.&. Word64 -> Word64
forall a. Num a => a -> a
negate Word64
pageSize
mhMagic64 :: Word32
mhMagic64 :: Word32
mhMagic64 = Word32
0xfeedfacf
headerSize :: Int
= Int
32
lcSegment64, lcSymtab, lcDysymtab, lcDyldInfo, lcDyldInfoOnly :: Word32
lcSegment64 :: Word32
lcSegment64 = Word32
0x19
lcSymtab :: Word32
lcSymtab = Word32
0x2
lcDysymtab :: Word32
lcDysymtab = Word32
0xb
lcDyldInfo :: Word32
lcDyldInfo = Word32
0x22
lcDyldInfoOnly :: Word32
lcDyldInfoOnly = Word32
0x80000022
linkEditDataCommands :: [Word32]
linkEditDataCommands :: [Word32]
linkEditDataCommands = [Word32
0x1d, Word32
0x1e, Word32
0x26, Word32
0x29, Word32
0x2b, Word32
0x2e, Word32
0x80000033, Word32
0x80000034]
segmentVmAddr, segmentVmSize, segmentFileOff, segmentFileSize, segmentNSects, segmentCommandSize :: Int
segmentVmAddr :: Int
segmentVmAddr = Int
24
segmentVmSize :: Int
segmentVmSize = Int
32
segmentFileOff :: Int
segmentFileOff = Int
40
segmentFileSize :: Int
segmentFileSize = Int
48
segmentNSects :: Int
segmentNSects = Int
64
segmentCommandSize :: Int
segmentCommandSize = Int
72
sectionSize, sectionOffset, sectionHeaderSize :: Int
sectionSize :: Int
sectionSize = Int
40
sectionOffset :: Int
sectionOffset = Int
48
= Int
80
u32 :: B.ByteString -> Int -> Word32
u32 :: ByteString -> Int -> Word32
u32 ByteString
bytes Int
at = Word64 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Word64
littleEndian (Int -> ByteString -> ByteString
B.take Int
4 (Int -> ByteString -> ByteString
B.drop Int
at ByteString
bytes)))
u64 :: B.ByteString -> Int -> Word64
u64 :: ByteString -> Int -> Word64
u64 ByteString
bytes Int
at = ByteString -> Word64
littleEndian (Int -> ByteString -> ByteString
B.take Int
8 (Int -> ByteString -> ByteString
B.drop Int
at ByteString
bytes))
littleEndian :: B.ByteString -> Word64
littleEndian :: ByteString -> Word64
littleEndian = (Word8 -> Word64 -> Word64) -> Word64 -> ByteString -> Word64
forall a. (Word8 -> a -> a) -> a -> ByteString -> a
B.foldr' (\Word8
b Word64
acc -> Word64
acc Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
8 Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b) Word64
0
put32 :: Int -> Word32 -> B.ByteString -> B.ByteString
put32 :: Int -> Word32 -> ByteString -> ByteString
put32 Int
at Word32
v ByteString
bs = Int -> ByteString -> ByteString
B.take Int
at ByteString
bs ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> [Word8] -> ByteString
B.pack [Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32
v Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftR` (Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
k)) | Int
k <- [Int
0 .. Int
3]] ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString -> ByteString
B.drop (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4) ByteString
bs
put64 :: Int -> Word64 -> B.ByteString -> B.ByteString
put64 :: Int -> Word64 -> ByteString -> ByteString
put64 Int
at Word64
v ByteString
bs = Int -> ByteString -> ByteString
B.take Int
at ByteString
bs ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> [Word8] -> ByteString
B.pack [Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
v Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftR` (Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
k)) | Int
k <- [Int
0 .. Int
7]] ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString -> ByteString
B.drop (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8) ByteString
bs