-- This file is part of Bindings-bfd.
--
-- 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 .
-- | 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 )
#include
-- 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) (#offset struct bfd, filename)
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) (#offset struct bfd, flags)
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) (#offset struct bfd, format)
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) (#offset struct bfd, my_archive)
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) (#offset struct bfd, xvec)
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) (#offset struct bfd, start_address)
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) (#offset struct bfd, symcount)
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) (#offset struct bfd, section_count)
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) (#offset struct bfd, sections)
getSections' first []
where
getSections' sect rs
| sect == nullPtr = return $ reverse rs
| otherwise =
do
next <- getNext sect
getSections' next (sect : rs)
#if 0
createSections
:: Bfd
-> IO [(Section, Vma)]
createSections bfd =
do
sects <- getSections bfd
extSect <- Section.mk "externs" 5
(_, sectList) <- foldM f (0,[]) $ sects ++ [extSect]
return $ reverse sectList
where
f (vma,xs') sect =
do
(vma',snvma) <- createSection sect vma
return (vma', snvma : xs')
#endif
-- Symbol Tables ---------------------------------------------------------------
getSymbolTable
:: Bfd
-> IO SymbolTable
getSymbolTable bfd =
do
xvec <- getTarget bfd
bound <- getSymtabUpperBound xvec bfd
let
ptrs = bound `quot` (#const sizeof(struct bfd_symbol *))
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` (#const sizeof(struct bfd_symbol *))
pps <- mallocArray ptrs
count <- canonicalizeDynamicSymtab xvec bfd pps
return $ SymbolTable.mk pps count
#if 0
getSyntheticSymbolTable
:: Bfd
-> SymbolTable -- static
-> SymbolTable -- dynamic
-> IO SymbolTable
getSyntheticSymbolTable bfd sst dst =
do
xvec <- getTarget bfd
ssyms <- malloc
count <- getSyntheticSymtab xvec bfd sst dst ssyms
return $ SymbolTable.mk ssyms count
#endif
-- Relocations -----------------------------------------------------------------
getDynamicRelocations
:: Bfd
-> SymbolTable
-> IO [Relocation]
getDynamicRelocations bfd st =
do
xvec <- getTarget bfd
bound <- getDynamicRelocUpperBound xvec bfd
let
ptrs = fromIntegral bound `quot` (#const sizeof(arelent *))
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 _ = #size struct bfd
alignment = sizeOf
peekByteOff buf off
| off == (#offset struct bfd, filename) =
do
val <- (#peek struct bfd, filename) buf :: IO CString
str <- peekCString val
return $ Filename str
| off == (#offset struct bfd, xvec) =
do
val <- (#peek struct bfd, xvec) buf :: IO Word
return $ XVec $ wordPtrToPtr $ fromIntegral val
| off == (#offset struct bfd, format) =
do
val <- (#peek struct bfd, format) buf :: IO CUInt
return $ Format $ toEnum $ fromIntegral val
| off == (#offset struct bfd, flags) =
do
val <- (#peek struct bfd, flags) buf :: IO CUInt
return $ Bindings.Bfd.Flags $ fromIntegral val
| off == (#offset struct bfd, sections) =
do
val <- (#peek struct bfd, sections) buf
return $ Sections val
| off == (#offset struct bfd, section_count) =
do
val <- (#peek struct bfd, section_count) buf :: IO CUInt
return $ SectionCount $ fromIntegral val
| off == (#offset struct bfd, start_address) =
do
val <- (#peek struct bfd, start_address) buf :: IO Vma'
return $ StartAddress $ fromIntegral val
| off == (#offset struct bfd, symcount) =
do
val <- (#peek struct bfd, symcount) buf :: IO CUInt
return $ SymbolCount $ fromIntegral val
| off == (#offset struct bfd, my_archive) =
do
val <- (#peek struct bfd, my_archive) buf :: IO (Ptr Bfd')
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