{-# LINE 1 "src/Bindings/Bfd.hsc" #-}
-- This file is part of Bindings-bfd.
{-# LINE 2 "src/Bindings/Bfd.hsc" #-}
--
-- Copyright (C) 2010,2011 Mick Nelso
--
-- Bindings-bfd is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- Bindings-bfd is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Lesser General Public License for more details.

-- You should have received a copy of the GNU Lesser General Public License
-- along with Bindings-bfd.  If not, see <http://www.gnu.org/licenses/>.

-- | This is the top-level module containing operations that can be performed
-- on a Binary Format Descriptor (BFD).  A BFD is opened on the binary file
-- to be queried\/manipulated and serves an opaque pointer. 

module Bindings.Bfd (
   -- * Types
     Bfd (ptr)
   -- * Initialization
   , initialize
   -- * File Operations
   , targetsAndFormats
   , open
   , close
   , closeAllDone
   -- * Setting the Format
   , checkFormat
   -- * Attributes
   , getDisasm
   , getFilename
   , Bindings.Bfd.getFlags
   , getFormat
   , getHasMap
   , getIsCacheable
   , getIsTargetDefaulted
   , getIsThinArchive
   , getMachine
   , getMyArchive
   , getOctetsPerByte
   , getTarget
   -- ** For Object Files
   , getStartAddress
   , getSymbolCount
   -- ** Sections
   , getSectionCount
   , getSectionByName
   , getSectionByVma
   , getSections
   -- ** Symbol Tables
   , getSymbolTable
   , getDynamicSymbolTable
   -- ** Relocations
   , getDynamicRelocations
   -- *
   , demangle
   -- * Internal
   , Bfd'
   , Bindings.Bfd.mk
) where

import Control.Exception ( catch )
import Control.Monad     ( foldM )

import Data.Bits  ( (.&.), bit )
import Data.Word  ( Word )

import Foreign.C        ( CString, CInt, CUInt, newCString, peekCString
                        , withCString )
import Foreign.Marshal  ( free, mallocArray, peekArray, toBool )
import Foreign.Ptr      ( Ptr, nullPtr, wordPtrToPtr )
import Foreign.Storable ( Storable, alignment, peek, peekByteOff, poke, sizeOf )

import Prelude hiding ( catch )

import Bindings.Bfd.Disasm                        ( Disasm )
import Bindings.Bfd.Exception                     ( BfdException
                                                  , throwExceptionIfFalse
                                                  , throwExceptionIfNull )
import Bindings.Bfd.Flags         as BfdFlags     ( Flags ( HasReloc ) )
import Bindings.Bfd.Format                        ( Format ( Object ) )
import Bindings.Bfd.Misc                          ( Vma, Vma' )
import Bindings.Bfd.Relocation                    ( Relocation )
import Bindings.Bfd.Section       as Section      ( Section, SectionName
                                                  , getNext, getSize, getVma )
import Bindings.Bfd.SymbolTable   as SymbolTable  ( SymbolTable, mk, tablePtr )
import Bindings.Bfd.Target        as Target       ( Target, TargetName
                                                  , canonicalizeDynamicReloc
                                                  , canonicalizeDynamicSymtab
                                                  , canonicalizeSymtab
                                                  , getDynamicRelocUpperBound
                                                  , getDynamicSymtabUpperBound
                                                  , getName, getSymtabUpperBound
                                                  , listSupported )


{-# LINE 102 "src/Bindings/Bfd.hsc" #-}


-- PUBLIC ######################################################################

-- Types =======================================================================

-- | The opaque pointer to the Binary File Descriptor.
data Bfd = Bfd {
                       ptr       :: Ptr Bfd'
                     , filePath  :: CString
                     , target    :: CString
                     , mode      :: CString
           }
     deriving (Show)

-- Initialization ==============================================================

-- | Initialize the library.  You need to call 'initialize' once, before using 
-- any of the functions in this library.
initialize
   :: IO ()
initialize = c_bfd_init

-- File Operations =============================================================

-- | Returns a list of tuples representing the possible combinations of 
-- 'TargetName' and 'Format' that are valid for this file on this platform.
--
-- /Possible Exceptions:/  Same as 'open'.
targetsAndFormats 
   :: FilePath                   -- ^ file to query
   -> IO [(TargetName, Format)]
targetsAndFormats file =
   do
      ts <- Target.listSupported
      let
         perms = [ (t,f) | t <- ts, f <- enumFrom Object ]
      foldM g [] perms
   where
      g xs r@(t,f) =
         do
            bfd   <- open file (Just t) "r"
            xvec1 <- getTarget bfd
            tn1   <- Target.getName xvec1
            ok    <- catch (checkFormat bfd f) ((\_ -> return False) :: BfdException -> IO Bool)
            _     <- close bfd
            case ok of
               True  -> 
                  do
                     xvec2 <- getTarget bfd
                     tn2   <- Target.getName xvec2
                     if tn1 == tn2 
                         then return $ r : xs
                         else return xs
               False -> return xs

-- | Opens the file 'FilePath' with the given target 'TargetName' and open mode 
-- (as defined by the Unix fopen(3) function) and returns a 'Bfd' object on 
-- success. FIXME: and marks it cacheable. 
--
-- If the target is 'Nothing' then the environment variable /GNUTARGET/ is
-- checked for a target name; if this is NULL or not defined then it chooses the
-- the default target if set (see 'setDefault') and sets an internal flag in
-- the 'Bfd' object indicating that the target was defaulted (see 
-- 'isTargetDefaulted'), or if not set, the first entry in the target list for 
-- the platform.  Passing the string \"default\" as the 'TargetName' or setting 
-- the environment variable to \"default\" also causes the above behavior.
--
-- /Important:/ Before you use the returned 'Bfd' object from a file you have
-- opened, you must call 'checkFormat' to 1) validate that the supplied 
-- 'TargetName' is appropriate for the opened file, and 2) set the 'Format' for
-- the 'Bfd'.  So for most intents and purposes, opening a file is a two-step
-- process.
--
-- /Possible Exceptions:/  'NoMemory' (if any allocation fails), 'SystemCall' 
-- (if open failed), and 'InvalidTarget' (if supplied target is unknown).
open
   :: FilePath          -- ^ file to open
   -> Maybe TargetName  -- ^ target
   -> String            -- ^ open mode (\"r\", \"r+\", \"w\", \"w+\", \"a\", \"a+\")
   -> IO Bfd
open fp targ mode0 =
   do
      fp'   <- newCString fp
      targ' <- 
         case targ of
            Just t  -> newCString t
            Nothing -> return nullPtr
      mode' <- newCString mode0
      let
         cmd = c_bfd_fopen fp' targ' mode' (-1)
      bfd <- throwExceptionIfNull "open" fp (show targ) cmd
      return $ Bfd bfd fp' targ' mode'

-- | Close a 'Bfd' and if all went well, return 'True'.  If the 'Bfd' was open
-- for writing, then pending operations are completed and the file written out
-- and closed.  If the created file is executable, then chmod(3) is called to
-- mark it as such.
close
   :: Bfd
   -> IO Bool
close bfd = 
   do
      r <- c_bfd_close $ ptr bfd
      free $ filePath bfd
      free $ target bfd
      free $ mode bfd
      return $ toBool r

-- | Close a 'Bfd' and if all went well, return 'True'.  Differs from 'close'
-- in that it does not complete any pending operations.  This function would be
-- used if the application had just used a 'Bfd' for swapping and didn't want to
-- use any of the writing code.  If the created file is executable, then 
-- chmod(3) is called to mark it as such.
closeAllDone
   :: Bfd
   -> IO Bool
closeAllDone bfd =
   do
      r <- c_bfd_close_all_done $ ptr bfd
      free $ filePath bfd
      free $ target bfd
      free $ mode bfd
      return $ toBool r

-- Setting the Format ==========================================================

-- | The second part of opening a file (see 'open').  Validates that the 
-- 'TargetName' is appropriate for the opened file and if not, silently picks
-- a more suitable 'TargetName', and also sets the 'Format' of the 'Bfd' object
-- representing the opened file.
--
-- /Important:/ You must call this function before using the vast majority of
-- these functions operating on the 'Bfd' as it updates critical data structures.
--
-- /Possible Exceptions:/ 'InvalidOperation' (if the file was opened write-only),
checkFormat
   :: Bfd
   -> Format
   -> IO Bool
checkFormat bfd format =
   do
      res <- c_bfd_check_format (ptr bfd) $ fromIntegral $ fromEnum format
      throwExceptionIfFalse "checkFormat" (show format) (return $ toBool res)
{-
checkFormatMatches
   :: Bfd
   -> Format
   -> IO [TargetName]
-}

-- Attributes ==================================================================

-- | Returns the disassembler associated with the 'Bfd'.
getDisasm
   :: Bfd
   -> IO Disasm
getDisasm bfd = c_disassembler $ ptr bfd

-- | Returns the 'FilePath' of the file associated with the 'Bfd'. 
getFilename
   :: Bfd
   -> IO FilePath
getFilename bfd =
   do
      fn <- peekByteOff (ptr bfd) ((4))
{-# LINE 268 "src/Bindings/Bfd.hsc" #-}
      return $ unBfd'Filename fn

-- | Returns a 'List' of the 'Bfd's set 'Flags'.
getFlags
   :: Bfd
   -> IO [BfdFlags.Flags]
getFlags bfd =
   do
      flags <- peekByteOff (ptr bfd) ((52))
{-# LINE 277 "src/Bindings/Bfd.hsc" #-}
      let
         flags' = filter f $ enumFrom HasReloc
            where
               f e = unBfd'Flags flags .&. (bit $ fromEnum e) /= 0
      return flags'

-- | Returns the 'Format' of the 'Bfd'.
getFormat
   :: Bfd
   -> IO Format
getFormat bfd =
   do
      format <- peekByteOff (ptr bfd) ((44))
{-# LINE 290 "src/Bindings/Bfd.hsc" #-}
      return $ unBfd'Format format

-- | Returns 'True' if the 'Bfd' has an archive map.  Otherwise 'False'.
getHasMap
   :: Bfd
   -> IO Bool
getHasMap bfd =
   do
      hm <- c__bfd_peek_has_armap $ ptr bfd
      return $ toBool hm

-- | Returns 'True' if the 'Bfd' is cacheable.  Otherwise 'False'.
getIsCacheable
   :: Bfd
   -> IO Bool
getIsCacheable bfd =
   do
      c <- c__bfd_peek_cacheable $ ptr bfd
      return $ toBool c

getIsTargetDefaulted
   :: Bfd
   -> IO Bool
getIsTargetDefaulted bfd =
   do
      td <- c__bfd_peek_target_defaulted $ ptr bfd
      return $ toBool td

-- | Returns 'True' if the 'Bfd' is a thin archive.  Otherwise 'False'.
getIsThinArchive
   :: Bfd
   -> IO Bool
getIsThinArchive bfd =
   do
      ita <- c__bfd_peek_is_thin_archive $ ptr bfd
      return $ toBool ita

getMachine
   :: Bfd
   -> IO Int
getMachine bfd = 
   do
      m <- c_bfd_get_mach $ ptr bfd
      return $ fromIntegral m

-- | Returns either a 'Bfd' or 'Nothing'.  FIXME
--
-- /Note:/ Do not pass the returned 'Bfd' to 'close' or 'closeAllDone' or a 
-- memory leak will occur.
getMyArchive
   :: Bfd
   -> IO (Maybe Bfd)
getMyArchive bfd =
   do
      ma <- peekByteOff (ptr bfd) ((140))
{-# LINE 345 "src/Bindings/Bfd.hsc" #-}
      return $ case unBfd'MyArchive ma == nullPtr of
         True  -> Nothing
         False -> Just $ Bfd (unBfd'MyArchive ma) nullPtr nullPtr nullPtr

getOctetsPerByte
   :: Bfd
   -> IO Int
getOctetsPerByte bfd =
   do
      opb <- c_bfd_octets_per_byte $ ptr bfd
      return $ fromIntegral opb

-- | Returns the 'Target' of the 'Bfd'.
getTarget
   :: Bfd
   -> IO Target
getTarget bfd = 
   do
      xv <- peekByteOff (ptr bfd) ((8))
{-# LINE 364 "src/Bindings/Bfd.hsc" #-}
      return $ unBfd'XVec xv

-- For Object Files ------------------------------------------------------------

-- | Return the start address.  Only valid for 'Object' files.
getStartAddress
   :: Bfd
   -> IO Vma
getStartAddress bfd =
   do
      addr <- peekByteOff (ptr bfd) ((112))
{-# LINE 375 "src/Bindings/Bfd.hsc" #-}
      return $ unBfd'StartAddress addr

-- | Return the symbol count used for input and output.  Only valid for 'Object'
-- files.
-- FIXME:  returns 0 when there are symbols and in main/main too!
getSymbolCount
   :: Bfd
   -> IO Int
getSymbolCount bfd =
   do
      sc <- peekByteOff (ptr bfd) ((120))
{-# LINE 386 "src/Bindings/Bfd.hsc" #-}
      return $ unBfd'SymbolCount sc

-- Sections --------------------------------------------------------------------

-- | Returns the number of 'Section's in the 'Bfd'.
getSectionCount
   :: Bfd
   -> IO Int
getSectionCount bfd =
   do 
      c <- peekByteOff (ptr bfd) ((108))
{-# LINE 397 "src/Bindings/Bfd.hsc" #-}
      return $ unBfd'SectionCount c

getSectionByName
   :: Bfd
   -> SectionName
   -> IO Section
getSectionByName bfd sn = withCString sn (\s -> c_bfd_get_section_by_name (ptr bfd) s) 

getSectionByVma
   :: Bfd
   -> Int
   -> IO (Maybe Section)
getSectionByVma bfd vma =
   do
      sects <- getSections bfd
      foldM f Nothing sects
   where
      f xs@(Just _ ) _  = return xs
      f    (Nothing) xi =
         do
            sectVma  <- getVma xi
            sectSize <- getSize xi
            case vma >= sectVma && vma < sectVma + sectSize of
               True  -> return $ Just xi
               False -> return $ Nothing

getSections
   :: Bfd
   -> IO [Section]
getSections bfd =
   do
      (Sections first) <- peekByteOff (ptr bfd) ((100))
{-# LINE 429 "src/Bindings/Bfd.hsc" #-}
      getSections' first []
   where
      getSections' sect rs
         | sect == nullPtr = return $ reverse rs
         | otherwise       =
              do
                 next <- getNext sect
                 getSections' next (sect : rs)


{-# LINE 454 "src/Bindings/Bfd.hsc" #-}

-- Symbol Tables ---------------------------------------------------------------

getSymbolTable
   :: Bfd
   -> IO SymbolTable
getSymbolTable bfd =
   do
      xvec  <- getTarget bfd
      bound <- getSymtabUpperBound xvec bfd
      let
         ptrs = bound `quot` (4)
{-# LINE 466 "src/Bindings/Bfd.hsc" #-}
      pps <- mallocArray ptrs
      count <- canonicalizeSymtab xvec bfd pps
      return $ SymbolTable.mk pps count

getDynamicSymbolTable
   :: Bfd
   -> IO SymbolTable
getDynamicSymbolTable bfd =
   do
      xvec  <- getTarget bfd
      bound <- getDynamicSymtabUpperBound xvec bfd
      let
         ptrs = fromIntegral bound `quot` (4)
{-# LINE 479 "src/Bindings/Bfd.hsc" #-}
      pps <- mallocArray ptrs
      count <- canonicalizeDynamicSymtab xvec bfd pps
      return $ SymbolTable.mk pps count


{-# LINE 496 "src/Bindings/Bfd.hsc" #-}

-- Relocations -----------------------------------------------------------------

getDynamicRelocations
   :: Bfd
   -> SymbolTable
   -> IO [Relocation]
getDynamicRelocations bfd st =
   do
      xvec  <- getTarget bfd
      bound <- getDynamicRelocUpperBound xvec bfd
      let
         ptrs = fromIntegral bound `quot` (4)
{-# LINE 509 "src/Bindings/Bfd.hsc" #-}
      ppr <- mallocArray ptrs
      count <- canonicalizeDynamicReloc xvec bfd ppr $ tablePtr st
      prs <- peekArray count ppr
      mapM peek prs

-- =============================================================================

demangle
   :: Bfd
   -> String
   -> IO String
demangle bfd str =
   do
      s <- withCString str (\s -> c_bfd_demangle (ptr bfd) s 3)
      case s == nullPtr of
         True  -> return ""
         False ->
            do
               s' <- peekCString s
               return s'

-- Internal ====================================================================

data Bfd' = Filename      String
          | XVec          Target
          | Format        Format
          | Flags         Int
          | Sections      Section
          | SectionCount  Int
          | StartAddress  Int
          | SymbolCount   Int
          | MyArchive    (Ptr Bfd')
     deriving (Show)

instance Storable Bfd' where
   sizeOf _ = (180)
{-# LINE 545 "src/Bindings/Bfd.hsc" #-}
   alignment = sizeOf
   peekByteOff buf off
      | off == ((4)) =
{-# LINE 548 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 4)) buf :: IO CString
{-# LINE 550 "src/Bindings/Bfd.hsc" #-}
            str <- peekCString val
            return $ Filename str
      | off == ((8)) = 
{-# LINE 553 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 8)) buf :: IO Word
{-# LINE 555 "src/Bindings/Bfd.hsc" #-}
            return $ XVec $ wordPtrToPtr $ fromIntegral val
      | off == ((44)) =
{-# LINE 557 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 44)) buf :: IO CUInt
{-# LINE 559 "src/Bindings/Bfd.hsc" #-}
            return $ Format $ toEnum $ fromIntegral val
      | off == ((52)) =
{-# LINE 561 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 52)) buf :: IO CUInt
{-# LINE 563 "src/Bindings/Bfd.hsc" #-}
            return $ Bindings.Bfd.Flags $ fromIntegral val
      | off == ((100)) =
{-# LINE 565 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 100)) buf
{-# LINE 567 "src/Bindings/Bfd.hsc" #-}
            return $ Sections val
      | off == ((108)) =
{-# LINE 569 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 108)) buf :: IO CUInt
{-# LINE 571 "src/Bindings/Bfd.hsc" #-}
            return $ SectionCount $ fromIntegral val
      | off == ((112)) =
{-# LINE 573 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 112)) buf :: IO Vma'
{-# LINE 575 "src/Bindings/Bfd.hsc" #-}
            return $ StartAddress $ fromIntegral val
      | off == ((120)) =
{-# LINE 577 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 120)) buf :: IO CUInt
{-# LINE 579 "src/Bindings/Bfd.hsc" #-}
            return $ SymbolCount $ fromIntegral val
      | off == ((140)) =
{-# LINE 581 "src/Bindings/Bfd.hsc" #-}
         do
            val <- ((\hsc_ptr -> peekByteOff hsc_ptr 140)) buf :: IO (Ptr Bfd')
{-# LINE 583 "src/Bindings/Bfd.hsc" #-}
            return $ MyArchive val
      | otherwise = error $ "internal error: Bfd.peekByteOff " ++ show off
   poke _ _ = return ()

mk
   :: Ptr Bfd'
   -> Bfd
mk p = Bfd p nullPtr nullPtr nullPtr

-- PRIVATE #####################################################################

unBfd'Filename
   :: Bfd'
   -> String
unBfd'Filename (Filename fn) = fn
unBfd'Filename _             = error "unBfd'Filename"

unBfd'XVec
   :: Bfd'
   -> Target
unBfd'XVec (XVec p) = p
unBfd'XVec _        = error "unBfd'XVec"

unBfd'Format
   :: Bfd'
   -> Format
unBfd'Format (Format f) = f
unBfd'Format _          = error "unBfd'Format"

unBfd'Flags
   :: Bfd'
   -> Int
unBfd'Flags (Bindings.Bfd.Flags m) = m
unBfd'Flags _                      = error "unBfd'Flags"

unBfd'SectionCount
   :: Bfd'
   -> Int
unBfd'SectionCount (SectionCount c) = c
unBfd'SectionCount _                = error "unBfd'SectionCount"

unBfd'StartAddress
   :: Bfd'
   -> Vma
unBfd'StartAddress (StartAddress a) = a
unBfd'StartAddress _                = error "unBfd'StartAddress"

unBfd'SymbolCount
   :: Bfd'
   -> Int
unBfd'SymbolCount (SymbolCount c) = c
unBfd'SymbolCount _               = error "unBfd'SymbolCount"

unBfd'MyArchive
   :: Bfd'
   -> (Ptr Bfd')
unBfd'MyArchive (MyArchive ma) = ma
unBfd'MyArchive _              = error "unBfd'MyArchive"


foreign import ccall unsafe "bfd.h bfd_init" c_bfd_init
   :: IO ()

foreign import ccall unsafe "bfd.h bfd_fopen" c_bfd_fopen
   :: CString
   -> CString
   -> CString
   -> CInt
   -> IO (Ptr Bfd')

foreign import ccall unsafe "bfd.h bfd_close" c_bfd_close
   :: Ptr Bfd'
   -> IO CInt

foreign import ccall unsafe "bfd.h bfd_close_all_done" c_bfd_close_all_done
   :: Ptr Bfd'
   -> IO CInt

foreign import ccall unsafe "bfd.h bfd_check_format" c_bfd_check_format
   :: Ptr Bfd'
   -> CInt
   -> IO CInt

foreign import ccall unsafe "bfd.h bfd_get_mach" c_bfd_get_mach
   :: Ptr Bfd'
   -> IO CInt

foreign import ccall unsafe "bfd.h bfd_octets_per_byte" c_bfd_octets_per_byte
   :: Ptr Bfd'
   -> IO CUInt

foreign import ccall unsafe "bfd.h bfd_get_section_by_name" c_bfd_get_section_by_name
   :: Ptr Bfd'
   -> CString
   -> IO Section

foreign import ccall unsafe "bfd.h bfd_demangle" c_bfd_demangle
   :: Ptr Bfd'
   -> CString
   -> CInt
   -> IO CString

foreign import ccall unsafe "dis-asm.h disassembler" c_disassembler
   :: Ptr Bfd'
   -> IO Disasm

foreign import ccall unsafe "bfd.h _bfd_peek_target_defaulted" c__bfd_peek_target_defaulted
   :: Ptr Bfd'
   -> IO CInt

foreign import ccall unsafe "bfd.h _bfd_peek_cacheable" c__bfd_peek_cacheable
   :: Ptr Bfd'
   -> IO CInt

foreign import ccall unsafe "bfd.h _bfd_peek_has_armap" c__bfd_peek_has_armap
   :: Ptr Bfd'
   -> IO CInt

foreign import ccall unsafe "bfd.h _bfd_peek_is_thin_archive" c__bfd_peek_is_thin_archive
   :: Ptr Bfd'
   -> IO CInt