Commit 109d7ce8 authored by Sylvain HENRY's avatar Sylvain HENRY Committed by Ben Gamari

Systools: read ELF section without calling readelf

This patch tackles two issues:

1) GHC stores a "link info" string into a ELF section. Initially a
section with type "note" was used but GHC didn't follow the ELF
specification which specifies a record-based format for these sections.
With D1375 we switched to a "progbits" section type for which there
isn't any format constraint. This is an issue for D1242 which use GCC's
--gc-sections which collects "unused" sections, such as our section
containing link info... In this patch, we fall back to a section with
type "note" but we respect the specified format.

2) Reading back the ELF section was done by parsing the result of a
call to "readelf". Calling readelf is problematic because the program
may not be available or it may be renamed on some platforms (see
D1326). Moreover we have no garanty that its output layout will stay
the same in future releases of readelf. Finally we would need to fix
the parsing to support  "note" sections because of 1. Instead, this
patch proposes to use Data.Binary.Get to directly read the "link info"
note into its section. ELF has a specification, hence it should work on
every conforming platform.

This patch "reverts" D1375, hence it supersedes D1432. It makes D1326
not necessary anymore.

Test Plan:
- recomp011 should pass (test that relinking is avoided when both "link
info" match)
- we should add a test for ELF objects with more than 0xff00 sections
=> added test "recomp015"
- we should check that GAS generates 32-bit words with .int on every
supported platform using ELF (or find a place where this is
documented). harbomaster and I (@hsyl20) only tested on x86-64. On
platforms where it is not true, it should make recomp011 fail. =>
tested to work on Linux/amd64, Solaris/i386 and OpenBSD/amd64

Reviewers: olsner, ony, thomie, kgardas, austin, bgamari

Reviewed By: thomie, bgamari

Subscribers: kgardas, rwbarton, thomie

Differential Revision: https://phabricator.haskell.org/D1381

GHC Trac Issues: #10974, #11022
parent f0f9365f
......@@ -48,6 +48,7 @@ Library
directory >= 1 && < 1.3,
process >= 1 && < 1.5,
bytestring >= 0.9 && < 0.11,
binary >= 0.7 && < 0.8,
time < 1.6,
containers >= 0.5 && < 0.6,
array >= 0.1 && < 0.6,
......@@ -332,6 +333,7 @@ Library
StaticFlags
StaticPtrTable
SysTools
Elf
TidyPgm
Ctype
HaddockUtils
......
......@@ -43,6 +43,7 @@ import Packages
import HeaderInfo
import DriverPhases
import SysTools
import Elf
import HscMain
import Finder
import HscTypes hiding ( Hsc )
......@@ -72,7 +73,6 @@ import System.IO
import Control.Monad
import Data.List ( isSuffixOf )
import Data.Maybe
import Data.Char
import Data.Time
import Data.Version
......@@ -447,9 +447,15 @@ checkLinkInfo dflags pkg_deps exe_file
= do
link_info <- getLinkInfo dflags pkg_deps
debugTraceMsg dflags 3 $ text ("Link info: " ++ link_info)
m_exe_link_info <- readElfSection dflags ghcLinkInfoSectionName exe_file
debugTraceMsg dflags 3 $ text ("Exe link info: " ++ show m_exe_link_info)
return (Just link_info /= m_exe_link_info)
m_exe_link_info <- readElfNoteAsString dflags exe_file
ghcLinkInfoSectionName ghcLinkInfoNoteName
let sameLinkInfo = (Just link_info == m_exe_link_info)
debugTraceMsg dflags 3 $ case m_exe_link_info of
Nothing -> text "Exe link info: Not found"
Just s
| sameLinkInfo -> text ("Exe link info is the same")
| otherwise -> text ("Exe link info is different: " ++ s)
return (not sameLinkInfo)
platformSupportsSavingLinkOpts :: OS -> Bool
platformSupportsSavingLinkOpts os
......@@ -461,6 +467,10 @@ ghcLinkInfoSectionName :: String
ghcLinkInfoSectionName = ".debug-ghc-link-info"
-- if we use the ".debug" prefix, then strip will strip it by default
-- Identifier for the note (see Note [LinkInfo section])
ghcLinkInfoNoteName :: String
ghcLinkInfoNoteName = "GHC link info"
findHSLib :: DynFlags -> [String] -> String -> IO (Maybe FilePath)
findHSLib dflags dirs lib = do
let batch_lib_file = if gopt Opt_Static dflags
......@@ -1660,34 +1670,17 @@ mkNoteObjsToLinkIntoBinary dflags dep_packages = do
where
link_opts info = hcat [
-- LinkInfo section must be of type "progbits"
-- See Note [LinkInfo section]
text "\t.section ", text ghcLinkInfoSectionName,
text ",\"\",",
text elfSectionProgBits,
text "\n",
text "\t.ascii \"", info', text "\"\n",
-- "link info" section (see Note [LinkInfo section])
makeElfNote dflags ghcLinkInfoSectionName ghcLinkInfoNoteName 0 info,
-- ALL generated assembly must have this section to disable
-- executable stacks. See also
-- compiler/nativeGen/AsmCodeGen.hs for another instance
-- where we need to do this.
(if platformHasGnuNonexecStack (targetPlatform dflags)
if platformHasGnuNonexecStack (targetPlatform dflags)
then text ".section .note.GNU-stack,\"\",@progbits\n"
else Outputable.empty)
else Outputable.empty
]
where
info' = text $ escape info
escape :: String -> String
escape = concatMap (charToC.fromIntegral.ord)
elfSectionProgBits :: String
elfSectionProgBits = case platformArch (targetPlatform dflags) of
ArchARM _ _ _ -> "%progbits"
_ -> "@progbits"
-- | Return the "link info" string
--
......@@ -1720,8 +1713,8 @@ changed, we use the link info stored in the existing binary to decide whether
to re-link or not.
The "link info" string is stored in a ELF section called ".debug-ghc-link-info"
(see ghcLinkInfoSectionName) with the SHT_PROGBITS type. It used to be of type
SHT_NOTE without following their specified record-based format (see #11022).
(see ghcLinkInfoSectionName) with the SHT_NOTE type. For some time, it used to
not follow the specified record-based format (see #11022).
-}
......
This diff is collapsed.
......@@ -25,7 +25,6 @@ module SysTools (
runLlvmLlc,
runClang,
figureLlvmVersion,
readElfSection,
getLinkerInfo,
getCompilerInfo,
......@@ -78,8 +77,6 @@ import System.Directory
import Data.Char
import Data.List
import qualified Data.Map as Map
import Text.ParserCombinators.ReadP hiding (char)
import qualified Text.ParserCombinators.ReadP as R
#ifndef mingw32_HOST_OS
import qualified System.Posix.Internals
......@@ -1043,31 +1040,7 @@ copyWithHeader dflags purpose maybe_header from to = do
hPutStr h str
hSetBinaryMode h True
-- | read the contents of the named section in an ELF object as a
-- String.
readElfSection :: DynFlags -> String -> FilePath -> IO (Maybe String)
readElfSection _dflags section exe = do
let
prog = "readelf"
args = [Option "-p", Option section, FileOption "" exe]
--
r <- readProcessEnvWithExitCode prog (filter notNull (map showOpt args))
en_locale_env
case r of
(ExitSuccess, out, _err) -> return (doFilter (lines out))
_ -> return Nothing
where
doFilter [] = Nothing
doFilter (s:r) = case readP_to_S parse s of
[(p,"")] -> Just p
_r -> doFilter r
where parse = do
skipSpaces
_ <- R.char '['
skipSpaces
_ <- string "0]"
skipSpaces
munch (const True)
{-
************************************************************************
......
import Control.Monad (forM_)
main :: IO ()
main = forM_ [0..0xffff] $ \i -> do
putStrLn $ ".section s" ++ show i ++ ",\"\",@progbits"
putStrLn $ ".asciz \"Section " ++ show i ++ "\""
putStrLn ""
TOP=../../..
include $(TOP)/mk/boilerplate.mk
include $(TOP)/mk/test.mk
# -fforce-recomp makes lots of driver tests trivially pass, so we
# filter it out from $(TEST_HC_OPTS).
TEST_HC_OPTS_NO_RECOMP = $(filter-out -fforce-recomp,$(TEST_HC_OPTS))
# Recompilation tests
clean:
rm -f *.o *.hi
rm -f ManySections.s Main.hs
rm -f Main$(exeext)
rm -f Generate$(exeext)
recomp015: clean
# Generate a file with more than 0xff00 sections to force different ELF
# fields to be used (ELF header fields are limited to 16-bit).
#
# You can confirm that fields of section 0 entry are used to store the
# number of section (size field of section 0) and the index of the .shstrtab
# section (link field of section 0) with:
# readelf -t ManySections.o | less
# and/or
# readelf -t Main | less
#
# This test checks that GHC can read these fields correctly and avoids
# recompilation (just like recomp011 which does the same thing for a smaller
# number of sections)
'$(TEST_HC)' $(TEST_HC_OPTS_NO_RECOMP) Generate.hs
./Generate > ManySections.s
echo 'main = putStrLn "Running main..."' > Main.hs
'$(TEST_HC)' $(TEST_HC_OPTS_NO_RECOMP) -c ManySections.s
'$(TEST_HC)' $(TEST_HC_OPTS_NO_RECOMP) --make -O Main.hs ManySections.o
./Main
sleep 1
'$(TEST_HC)' $(TEST_HC_OPTS_NO_RECOMP) --make -O Main.hs ManySections.o
./Main
# Test for the ELF parser: more than 0xff00 sections (use different ELF fields)
test('recomp015',
[ clean_cmd('$MAKE -s clean') ],
run_command,
['$MAKE -s --no-print-directory recomp015'])
[1 of 1] Compiling Main ( Generate.hs, Generate.o )
Linking Generate ...
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
Running main...
Running main...
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment