{-# LINE 1 "./Data/Array/Accelerate/LLVM/Native/Link/MachO.chs" #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module Data.Array.Accelerate.LLVM.Native.Link.MachO (
loadObject,
) where
import Data.Array.Accelerate.Error
import Data.Array.Accelerate.LLVM.Native.Link.Object
import Data.Array.Accelerate.Lifetime
import qualified Data.Array.Accelerate.Debug as Debug
import Control.Applicative
import Control.Monad
import Data.Bits
import Data.ByteString ( ByteString )
import Data.Maybe ( catMaybes )
import Data.Serialize.Get
import Data.Vector ( Vector )
import Data.Word
import Foreign.C
import Foreign.ForeignPtr
import Foreign.ForeignPtr.Unsafe
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable
import GHC.ForeignPtr ( mallocPlainForeignPtrAlignedBytes )
import GHC.Prim ( addr2Int#, int2Word# )
import GHC.Ptr ( Ptr(..) )
import GHC.Word ( Word64(..) )
import System.IO.Unsafe
import System.Posix.DynamicLinker
import Text.Printf
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Internal as B
import qualified Data.ByteString.Short as BS
import qualified Data.ByteString.Unsafe as B
import qualified Data.Vector as V
import Prelude as P
loadObject :: ByteString -> IO (FunctionTable, ObjectCode)
loadObject obj =
case parseObject obj of
Left err -> $internalError "loadObject" err
Right (symtab, lcs) -> loadSegments obj symtab lcs
loadSegments :: ByteString -> Vector Symbol -> Vector LoadSegment -> IO (FunctionTable, ObjectCode)
loadSegments obj symtab lcs = do
segs <- V.mapM (loadSegment obj symtab) lcs
let extern Symbol{..} = sym_extern && sym_segment > 0
resolve Symbol{..} =
let Segment _ fp = segs V.! (fromIntegral (sym_segment-1))
name = BS.toShort (B8.take (B8.length sym_name - 17) sym_name)
addr = castPtrToFunPtr (unsafeForeignPtrToPtr fp `plusPtr` fromIntegral sym_value)
in
(name, addr)
funtab = FunctionTable $ V.toList $ V.map resolve (V.filter extern symtab)
objectcode = V.toList segs
objectcode' <- newLifetime objectcode
addFinalizer objectcode' $ do
Debug.traceIO Debug.dump_gc ("gc: unload module: " ++ show funtab)
forM_ objectcode $ \(Segment vmsize oc_fp) -> do
withForeignPtr oc_fp $ \oc_p -> do
mprotect oc_p vmsize (0x1 .|. 0x2)
return (funtab, objectcode')
loadSegment :: ByteString -> Vector Symbol -> LoadSegment -> IO Segment
loadSegment obj symtab seg@LoadSegment{..} = do
let
pagesize = fromIntegral c_getpagesize
pad align n = (n + align - 1) .&. (complement (align - 1))
seg_vmsize' = pad 16 seg_vmsize
segsize = pad pagesize (seg_vmsize' + (V.length symtab * 16))
seg_fp <- mallocPlainForeignPtrAlignedBytes segsize pagesize
_ <- withForeignPtr seg_fp $ \seg_p -> do
fillBytes seg_p 0 segsize
let jump_p = seg_p `plusPtr` seg_vmsize'
V.imapM_ (makeJumpIsland jump_p) symtab
V.mapM_ (loadSection obj symtab seg seg_p jump_p) seg_sections
mprotect seg_p segsize (0x1 .|. 0x4)
return (Segment segsize seg_fp)
makeJumpIsland :: Ptr Word8 -> Int -> Symbol -> IO ()
makeJumpIsland jump_p symbolnum Symbol{..} = do
when (sym_extern && sym_segment == 0) $ do
let
target = jump_p `plusPtr` (symbolnum * 16) :: Ptr Word64
instr = target `plusPtr` 8 :: Ptr Word8
poke target sym_value
pokeArray instr [ 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF ]
return ()
loadSection :: ByteString -> Vector Symbol -> LoadSegment -> Ptr Word8 -> Ptr Word8 -> LoadSection -> IO ()
loadSection obj symtab seg seg_p jump_p sec@LoadSection{..} = do
let (obj_fp, obj_offset, _) = B.toForeignPtr obj
withForeignPtr obj_fp $ \obj_p -> do
let src = obj_p `plusPtr` (obj_offset + sec_offset)
dst = seg_p `plusPtr` sec_addr
copyBytes dst src sec_size
V.mapM_ (processRelocation symtab seg seg_p jump_p sec) sec_relocs
processRelocation :: Vector Symbol -> LoadSegment -> Ptr Word8 -> Ptr Word8 -> LoadSection -> RelocationInfo -> IO ()
processRelocation symtab LoadSegment{..} seg_p jump_p sec RelocationInfo{..}
| ri_type == X86_64_RELOC_GOT ||
ri_type == X86_64_RELOC_GOT_LOAD
= $internalError "processRelocation" "Global offset table relocations not handled yet"
| ri_extern
= let value = sym_value (symtab V.! ri_symbolnum)
value_rel = value - pc' - 2 ^ ri_length
in
case ri_pcrel of
False -> relocate value
True -> if (fromIntegral (fromIntegral value_rel::Word32) :: Word64) == value_rel
then relocate value_rel
else do
let value' = castPtrToWord64 (jump_p `plusPtr` (ri_symbolnum * 16 + 8))
value'_rel = value' - pc' - 2 ^ ri_length
relocate value'_rel
| otherwise
= return ()
where
pc :: Ptr Word8
pc = seg_p `plusPtr` (sec_addr sec + ri_address)
pc' = castPtrToWord64 pc
addend :: (Integral a, Storable a) => Ptr a -> Word64 -> IO a
addend p x = do
base <- peek p
case ri_type of
X86_64_RELOC_SUBTRACTOR -> return $ fromIntegral (fromIntegral base - x)
_ -> return $ fromIntegral (fromIntegral base + x)
relocate :: Word64 -> IO ()
relocate x =
case ri_length of
0 -> let p' = castPtr pc :: Ptr Word8 in poke p' =<< addend p' x
1 -> let p' = castPtr pc :: Ptr Word16 in poke p' =<< addend p' x
2 -> let p' = castPtr pc :: Ptr Word32 in poke p' =<< addend p' x
_ -> $internalError "processRelocation" "unhandled relocation size"
data Peek = Peek
{ is64Bit :: !Bool
, getWord16 :: !(Get Word16)
, getWord32 :: !(Get Word32)
, getWord64 :: !(Get Word64)
}
data LoadCommand
= LC_Segment {-# UNPACK #-} !LoadSegment
| LC_SymbolTable {-# UNPACK #-} !(Vector Symbol)
data LoadSegment = LoadSegment
{ seg_name :: {-# UNPACK #-} !ByteString
, seg_vmaddr :: {-# UNPACK #-} !Int
, seg_vmsize :: {-# UNPACK #-} !Int
, seg_fileoff :: {-# UNPACK #-} !Int
, seg_filesize :: {-# UNPACK #-} !Int
, seg_sections :: {-# UNPACK #-} !(Vector LoadSection)
}
deriving Show
data LoadSection = LoadSection
{ sec_secname :: {-# UNPACK #-} !ByteString
, sec_segname :: {-# UNPACK #-} !ByteString
, sec_addr :: {-# UNPACK #-} !Int
, sec_size :: {-# UNPACK #-} !Int
, sec_offset :: {-# UNPACK #-} !Int
, sec_align :: {-# UNPACK #-} !Int
, sec_relocs :: {-# UNPACK #-} !(Vector RelocationInfo)
}
deriving Show
data RelocationInfo = RelocationInfo
{ ri_address :: {-# UNPACK #-} !Int
, ri_symbolnum :: {-# UNPACK #-} !Int
, ri_length :: {-# UNPACK #-} !Int
, ri_pcrel :: !Bool
, ri_extern :: !Bool
, ri_type :: !RelocationType
}
deriving Show
data Symbol = Symbol
{ sym_name :: {-# UNPACK #-} !ByteString
, sym_value :: {-# UNPACK #-} !Word64
, sym_segment :: {-# UNPACK #-} !Word8
, sym_extern :: !Bool
}
deriving Show
data RelocationType = X86_64_RELOC_UNSIGNED
| X86_64_RELOC_SIGNED
| X86_64_RELOC_BRANCH
| X86_64_RELOC_GOT_LOAD
| X86_64_RELOC_GOT
| X86_64_RELOC_SUBTRACTOR
| X86_64_RELOC_SIGNED_1
| X86_64_RELOC_SIGNED_2
| X86_64_RELOC_SIGNED_4
| X86_64_RELOC_TLV
deriving (Enum,Eq,Show)
{-# LINE 346 "./Data/Array/Accelerate/LLVM/Native/Link/MachO.chs" #-}
parseObject :: ByteString -> Either String (Vector Symbol, Vector LoadSegment)
parseObject obj = do
((p, ncmd, _), rest) <- runGetState readHeader obj 0
cmds <- catMaybes <$> runGet (replicateM ncmd (readLoadCommand p obj)) rest
let
lc = [ x | LC_Segment x <- cmds ]
st = [ x | LC_SymbolTable x <- cmds ]
return (V.concat st, V.fromListN ncmd lc)
readHeader :: Get (Peek, Int, Int)
readHeader = do
magic <- getWord32le
p@Peek{..} <- case magic of
0xfeedface -> return $ Peek False getWord16le getWord32le getWord64le
0xcefaedfe -> return $ Peek False getWord16be getWord32be getWord64be
0xfeedfacf -> return $ Peek True getWord16le getWord32le getWord64le
0xcffaedfe -> return $ Peek True getWord16be getWord32be getWord64be
m -> fail (printf "unknown magic: %x" m)
cpu_type <- getWord32
when (cpu_type /= 0x1000007) $ fail "expected x86_64 object file"
skip 4
{-# LINE 404 "./Data/Array/Accelerate/LLVM/Native/Link/MachO.chs" #-}
filetype <- getWord32
case filetype of
0x1 -> return ()
_ -> fail "expected object file"
ncmds <- fromIntegral <$> getWord32
sizeofcmds <- fromIntegral <$> getWord32
skip $ case is64Bit of
True -> 8
False -> 4
return (p, ncmds, sizeofcmds)
readLoadCommand :: Peek -> ByteString -> Get (Maybe LoadCommand)
readLoadCommand p@Peek{..} obj = do
cmd <- getWord32
cmdsize <- fromIntegral <$> getWord32
let required = toBool $ cmd .&. 0x80000000
{-# LINE 433 "./Data/Array/Accelerate/LLVM/Native/Link/MachO.chs" #-}
case cmd .&. (complement 0x80000000) of
0x1 -> Just . LC_Segment <$> readLoadSegment p obj
0x19 -> Just . LC_Segment <$> readLoadSegment p obj
0x2 -> Just . LC_SymbolTable <$> readLoadSymbolTable p obj
0xb -> const Nothing <$> readDynamicSymbolTable p obj
0xc -> fail "unhandled LC_LOAD_DYLIB"
this -> do if required
then fail (printf "unknown load command required for execution: 0x%x" this)
else message (printf "skipping load command: 0x%x" this)
skip (cmdsize - 8)
return Nothing
readLoadSegment :: Peek -> ByteString -> Get LoadSegment
readLoadSegment p@Peek{..} obj =
if is64Bit
then readLoadSegment64 p obj
else readLoadSegment32 p obj
readLoadSegment32 :: Peek -> ByteString -> Get LoadSegment
readLoadSegment32 p@Peek{..} obj = do
name <- B.takeWhile (/= 0) <$> getBytes 16
vmaddr <- fromIntegral <$> getWord32
vmsize <- fromIntegral <$> getWord32
fileoff <- fromIntegral <$> getWord32
filesize <- fromIntegral <$> getWord32
skip (2 * 4)
nsect <- fromIntegral <$> getWord32
skip 4
message (printf "LC_SEGMENT: Mem: 0x%09x-0x09%x" vmaddr (vmaddr + vmsize))
secs <- V.replicateM nsect (readLoadSection32 p obj)
return LoadSegment
{ seg_name = name
, seg_vmaddr = vmaddr
, seg_vmsize = vmsize
, seg_fileoff = fileoff
, seg_filesize = filesize
, seg_sections = secs
}
readLoadSegment64 :: Peek -> ByteString -> Get LoadSegment
readLoadSegment64 p@Peek{..} obj = do
name <- B.takeWhile (/= 0) <$> getBytes 16
vmaddr <- fromIntegral <$> getWord64
vmsize <- fromIntegral <$> getWord64
fileoff <- fromIntegral <$> getWord64
filesize <- fromIntegral <$> getWord64
skip (2 * 4)
nsect <- fromIntegral <$> getWord32
skip 4
message (printf "LC_SEGMENT_64: Mem: 0x%09x-0x%09x" vmaddr (vmaddr + vmsize))
secs <- V.replicateM nsect (readLoadSection64 p obj)
return LoadSegment
{ seg_name = name
, seg_vmaddr = vmaddr
, seg_vmsize = vmsize
, seg_fileoff = fileoff
, seg_filesize = filesize
, seg_sections = secs
}
readLoadSection32 :: Peek -> ByteString -> Get LoadSection
readLoadSection32 p@Peek{..} obj = do
secname <- B.takeWhile (/= 0) <$> getBytes 16
segname <- B.takeWhile (/= 0) <$> getBytes 16
addr <- fromIntegral <$> getWord32
size <- fromIntegral <$> getWord32
offset <- fromIntegral <$> getWord32
align <- fromIntegral <$> getWord32
reloff <- fromIntegral <$> getWord32
nreloc <- fromIntegral <$> getWord32
skip 12
message (printf " Mem: 0x%09x-0x%09x %s.%s" addr (addr+size) (B8.unpack segname) (B8.unpack secname))
relocs <- either fail return $ runGet (V.replicateM nreloc (loadRelocation p)) (B.drop reloff obj)
return LoadSection
{ sec_secname = secname
, sec_segname = segname
, sec_addr = addr
, sec_size = size
, sec_offset = offset
, sec_align = align
, sec_relocs = relocs
}
readLoadSection64 :: Peek -> ByteString -> Get LoadSection
readLoadSection64 p@Peek{..} obj = do
secname <- B.takeWhile (/= 0) <$> getBytes 16
segname <- B.takeWhile (/= 0) <$> getBytes 16
addr <- fromIntegral <$> getWord64
size <- fromIntegral <$> getWord64
offset <- fromIntegral <$> getWord32
align <- fromIntegral <$> getWord32
reloff <- fromIntegral <$> getWord32
nreloc <- fromIntegral <$> getWord32
skip 16
message (printf " Mem: 0x%09x-0x%09x %s.%s" addr (addr+size) (B8.unpack segname) (B8.unpack secname))
relocs <- either fail return $ runGet (V.replicateM nreloc (loadRelocation p)) (B.drop reloff obj)
return LoadSection
{ sec_secname = secname
, sec_segname = segname
, sec_addr = addr
, sec_size = size
, sec_offset = offset
, sec_align = align
, sec_relocs = relocs
}
loadRelocation :: Peek -> Get RelocationInfo
loadRelocation Peek{..} = do
addr <- fromIntegral <$> getWord32
val <- getWord32
let symbol = val .&. 0xFFFFFF
pcrel = testBit val 24
extern = testBit val 27
len = (val `shiftR` 25) .&. 0x3
rtype = (val `shiftR` 28) .&. 0xF
rtype' = toEnum (fromIntegral rtype)
when (toBool $ addr .&. 0x80000000) $ fail "unhandled scatted relocation info"
message (printf " Reloc: 0x%04x to %s %d: length=%d, pcrel=%s, type=%s" addr (if extern then "symbol" else "section") symbol len (show pcrel) (show rtype'))
return RelocationInfo
{ ri_address = addr
, ri_symbolnum = fromIntegral symbol
, ri_pcrel = pcrel
, ri_extern = extern
, ri_length = fromIntegral len
, ri_type = rtype'
}
readLoadSymbolTable :: Peek -> ByteString -> Get (Vector Symbol)
readLoadSymbolTable p@Peek{..} obj = do
symoff <- fromIntegral <$> getWord32
nsyms <- fromIntegral <$> getWord32
stroff <- fromIntegral <$> getWord32
strsize <- getWord32
message "LC_SYMTAB"
message (printf " symbol table is at offset 0x%x (%d), %d entries" symoff symoff nsyms)
message (printf " string table is at offset 0x%x (%d), %d bytes" stroff stroff strsize)
let symbols = B.drop symoff obj
strtab = B.drop stroff obj
either fail return $ runGet (V.replicateM nsyms (loadSymbol p strtab)) symbols
readDynamicSymbolTable :: Peek -> ByteString -> Get ()
readDynamicSymbolTable Peek{..} _obj = do
skip (80 - 8)
return ()
loadSymbol :: Peek -> ByteString -> Get Symbol
loadSymbol Peek{..} strtab = do
n_strx <- fromIntegral <$> getWord32
n_flag <- getWord8
n_sect <- getWord8
skip 2
n_value <- case is64Bit of
True -> fromIntegral <$> getWord64
False -> fromIntegral <$> getWord32
let
str | n_strx == 0 = B.empty
| otherwise = B.takeWhile (/= 0) (B.drop n_strx strtab)
name
| B.length str > 0 && B8.head str == '_' = B.tail str
| otherwise = str
n_stab = n_flag .&. 0xe0
n_type = n_flag .&. 0xe
n_ext = n_flag .&. 0x1
unless (n_stab == 0) $ fail "unhandled symbolic debugging entry (stab)"
case n_type of
0x0 -> do
funptr <- resolveSymbol name
message (printf " %s: external symbol found at %s" (B8.unpack name) (show funptr))
return Symbol
{ sym_name = name
, sym_extern = toBool n_ext
, sym_segment = n_sect
, sym_value = castPtrToWord64 (castFunPtrToPtr funptr)
}
0xe -> do
message (printf " %s: local symbol in section %d at 0x%02x" (B8.unpack name) n_sect n_value)
return Symbol
{ sym_name = name
, sym_extern = toBool n_ext
, sym_segment = n_sect
, sym_value = n_value
}
0x2 -> fail "unhandled absolute symbol"
0xc -> fail "unhandled prebound (dylib) symbol"
0xa -> fail "unhandled indirect symbol"
_ -> fail "unknown symbol type"
resolveSymbol :: ByteString -> Get (FunPtr ())
resolveSymbol name
= unsafePerformIO
$ B.unsafeUseAsCString name $ \c_name -> do
addr <- c_dlsym (packDL Next) c_name
if addr == nullFunPtr
then do
err <- dlerror
return (fail $ printf "failed to resolve symbol %s: %s" (B8.unpack name) err)
else do
return (return addr)
castPtrToWord64 :: Ptr a -> Word64
castPtrToWord64 (Ptr addr#) = W64# (int2Word# (addr2Int# addr#))
mprotect :: Ptr Word8 -> Int -> Int -> IO ()
mprotect addr len prot
= throwErrnoIfMinus1_ "mprotect"
$ c_mprotect (castPtr addr) (fromIntegral len) (fromIntegral prot)
foreign import ccall unsafe "mprotect"
c_mprotect :: Ptr () -> CSize -> CInt -> IO CInt
foreign import ccall unsafe "getpagesize"
c_getpagesize :: CInt
{-# INLINE trace #-}
trace :: String -> a -> a
trace msg = Debug.trace Debug.dump_ld ("ld: " ++ msg)
{-# INLINE message #-}
message :: Monad m => String -> m ()
message msg = trace msg (return ())