Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ md5CheckGenericPackageDescription proxy = md5Check proxy

md5CheckLocalBuildInfo :: Proxy LocalBuildInfo -> Assertion
md5CheckLocalBuildInfo proxy = md5Check proxy
0x3398bd7f316ecb8f535cbe78498a89a4
0x82d22c1d21566e21c9b384cc63bd93bf
5 changes: 2 additions & 3 deletions Cabal/src/Distribution/Simple/GHC/Build/Link.hs
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,10 @@ linkOrLoadComponent
[ "-static"
| withFullyStaticExe lbi
]
-- Pass extra `ld-options` given
-- through to GHC's linker.
-- Pass extra `ld-options` given through to GHC's linker.
++ maybe
[]
programOverrideArgs
programDriverArgs
(lookupProgram ldProgram (withPrograms lbi))
, ghcOptLinkLibs =
if withFullyStaticExe lbi
Expand Down
82 changes: 78 additions & 4 deletions Cabal/src/Distribution/Simple/GHC/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -209,15 +209,61 @@ configureToolchain _implInfo ghcProg ghcInfo =
| otherwise -> tokenizeQuotedWords flags

configureGcc :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
configureGcc _v gccProg = do
configureGcc v gccProg = do
gccProg' <- configureGcc' v gccProg
return
gccProg
gccProg'
{ programDefaultArgs =
programDefaultArgs gccProg
programDefaultArgs gccProg'
++ ccFlags
++ gccLinkerFlags
}

-- We probe whether the linker driver (the C compiler) supports -no-pie
-- with a test file, like GHC's build system does
-- (FP_GCC_SUPPORTS_NO_PIE). GHC passes -no-pie to the linker driver
-- when joining objects or linking, but with a custom linker (see
-- -pgml, 'ghcOptLinkerProgram') it does not know whether the flag is
-- supported and refuses to pass it, unless told otherwise with
-- -pgml-supports-no-pie (GHC #15319). Note that some compilers only
-- warn on unknown flags, hence -Werror and the output check, mirroring
-- GHC's own probe.
configureGcc' :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
configureGcc' verbosity gccProg = do
supportsNoPie <- withTempFile ".c" $ \testcfile testchnd ->
withTempFile ".o" $ \testofile testohnd -> do
hPutStrLn testchnd "int main() { return 0; }"
hClose testchnd
hClose testohnd
runProgram
verbosity
ghcProg
[ "-hide-all-packages"
, "-c"
, testcfile
, "-o"
, testofile
]
withTempFile "" $ \testbinfile testbinhnd ->
do
hClose testbinhnd
output <-
getProgramOutput
verbosity
gccProg
["-no-pie", "-Werror", testofile, "-o", testbinfile]
return (not ("unrecognized" `isInfixOf` map toLower output))
`catchIO` (\_ -> return False)
`catchExit` (\_ -> return False)
return
gccProg
{ programProperties =
Map.insert
"Supports -no-pie"
(if supportsNoPie then "YES" else "NO")
(programProperties gccProg)
}

configureGpp :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
configureGpp _v gppProg = do
return
Expand Down Expand Up @@ -258,7 +304,7 @@ configureToolchain _implInfo ghcProg ghcInfo =
_ <-
getProgramOutput
verbosity
ldProg
(suppressOverrideArgs ldProg)
["-x", "-r", testofile, "-o", testofile']
return True
`catchIO` (\_ -> return False)
Expand Down Expand Up @@ -597,6 +643,34 @@ linkGhcOptions verbosity lbi bi clbi =
(mkVersion [9, 4])
(compiler lbi)
(maybeToFlag $ programPath <$> lookupProgram gppProgram (withPrograms lbi))
, -- Use -pgml to ensure GHC drives the final link with the C compiler
-- Cabal resolved (as with -pgmc above, so that cc-options and
-- ld-options are interpreted by the selected toolchain, #4435,
-- #9801). Note that GHC's linker does not follow -pgmc: without
-- -pgml the final link is driven by the C compiler from GHC's
-- settings file.
-- As with -pgmc, we can only do this on GHC >= 9.4: with a custom
-- linker GHC stops passing -no-pie
-- (https://gitlab.haskell.org/ghc/ghc/-/issues/15319), which
-- breaks linking on toolchains that default to PIE. Whether the
-- resolved C compiler supports -no-pie is probed when the
-- compiler is configured (see 'configureGcc''), and passed along
-- as -pgml-supports-no-pie by renderGhcOptions.
-- see example in cabal-testsuite/PackageTests/FFI/ForeignOptsPgml
ghcOptLinkerProgram =
ghcOptionsSince
(mkVersion [9, 4])
(compiler lbi)
(maybeToFlag $ programPath <$> lookupProgram gccProgram (withPrograms lbi))
, ghcOptLinkerSupportsNoPie =
ghcOptionsSince
(mkVersion [9, 4])
(compiler lbi)
( maybeToFlag $ do
gccProg <- lookupProgram gccProgram (withPrograms lbi)
guard (Map.lookup "Supports -no-pie" (programProperties gccProg) == Just "YES")
pure True
)
}
where
exe_paths =
Expand Down
2 changes: 1 addition & 1 deletion Cabal/src/Distribution/Simple/Program/Builtin.hs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ ldProgram =
ldHelpOutput <-
getProgramInvocationOutput
verbosity
(programInvocation ldProg ["--help"])
(programInvocation (suppressOverrideArgs ldProg) ["--help"])
-- In case the linker does not support '--help'. Eg the LLVM linker,
-- `lld` only accepts `-help`.
`catchIO` (\_ -> return "")
Expand Down
25 changes: 24 additions & 1 deletion Cabal/src/Distribution/Simple/Program/Db.hs
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,31 @@ userSpecifyArgs name args' =
prog
{ programOverrideArgs =
programOverrideArgs prog
++ interpretUserOptions name args'
, programDriverArgs =
programDriverArgs prog
++ args'
}
)

-- | Interpret options written for the linker driver (the form documented
-- for @ld-options@, see Note [ld-options and the linker driver]) into the
-- options @ld@ itself accepts (#10789).
--
-- A driver option @-Wl,a,b@ means \"pass @a@ and @b@ to the linker\", so it
-- turns into @[a, b]@. All other options are passed through unchanged.
interpretUserOptions :: String -> [String] -> [String]
interpretUserOptions "ld" = concatMap ldOptionToLdFlag
where
ldOptionToLdFlag opt
| "-Wl," `isPrefixOf` opt = splitOnCommas (drop 4 opt)
| otherwise = [opt]

splitOnCommas s = case break (== ',') s of
(w, []) -> [w]
(w, _ : rest) -> w : splitOnCommas rest
interpretUserOptions _ = id

-- | Like 'userSpecifyPath' but for a list of progs and their paths.
userSpecifyPaths
:: [(String, FilePath)]
Expand Down Expand Up @@ -439,7 +460,9 @@ configureUnconfiguredProgram verbosity prog progdb = do
{ programId = name
, programVersion = version
, programDefaultArgs = []
, programOverrideArgs = userSpecifiedArgs prog progdb
, programOverrideArgs =
interpretUserOptions name (userSpecifiedArgs prog progdb)
, programDriverArgs = userSpecifiedArgs prog progdb
, programOverrideEnv = [("PATH", Just newPath)] ++ progOverrideEnv progdb
, programProperties = Map.empty
, programLocation = location
Expand Down
25 changes: 25 additions & 0 deletions Cabal/src/Distribution/Simple/Program/GHC.hs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,23 @@ data GhcOptions = GhcOptions
-- ^ Program to use for the C compiler; the @ghc -pgmc@ flag.
, ghcOptGppProgram :: Flag FilePath
-- ^ Program to use for the C++ compiler; the @ghc -pgmcxx@ flag.
, ghcOptLinkerProgram :: Flag FilePath
-- ^ Program to use as the linker; the @ghc -pgml@ flag.
--
-- Note that GHC drives the final link with C compiler style options
-- (@-Wl,..@, @-no-pie@): the linker program is a C compiler driver, which
-- by default is the C compiler from GHC's settings. Since a custom
-- linker's support for @-no-pie@ is unknown to GHC, it stops passing the
-- flag altogether (see @-pgml-supports-no-pie@ and GHC issue #15319);
-- renderGhcOptions therefore passes @-pgml-supports-no-pie@ along with
-- this option depending on 'ghcOptLinkerSupportsNoPie', as the linker
-- Cabal selects is the probed C compiler.
, ghcOptLinkerSupportsNoPie :: Flag Bool
-- ^ Whether the linker program (see 'ghcOptLinkerProgram') supports the
-- @-no-pie@ flag; the @ghc -pgml-supports-no-pie@ flag. GHC refuses to
-- pass @-no-pie@ to a custom linker (see GHC issue #15319) unless this
-- flag is given, so Cabal probes the resolved C compiler when it is
-- configured.
, ----------------------------
-- Language and extensions

Expand Down Expand Up @@ -889,6 +906,14 @@ renderGhcOptions comp _platform@(Platform _arch os) opts
, ["-opta" ++ opt | opt <- ghcOptAsmOptions opts]
, concat [["-pgmc", cc] | cc <- flag ghcOptCcProgram]
, concat [["-pgmcxx", cxx] | cxx <- flag ghcOptGppProgram]
, -- -pgml-supports-no-pie has to be passed after -pgml: GHC refuses
-- to pass -no-pie to a custom linker (see 'ghcOptLinkerProgram'
-- and GHC issue #15319) unless the probe of the resolved compiler
-- found support for it.
concat
[ ["-pgml", ld] ++ ["-pgml-supports-no-pie" | flagBool ghcOptLinkerSupportsNoPie]
| ld <- flag ghcOptLinkerProgram
]
, -----------------
-- Linker stuff

Expand Down
23 changes: 21 additions & 2 deletions Cabal/src/Distribution/Simple/Program/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,21 @@ data ConfiguredProgram = ConfiguredProgram
, programOverrideArgs :: [String]
-- ^ Override command-line args for this program.
-- These flags will appear last on the command line, so they override
-- all earlier flags.
-- all earlier flags. They hold the user's options for this program
-- ('programDriverArgs') interpreted for the program's own command
-- line, which for @ld@ differs from the form the user gives them in
, programDriverArgs :: [String]
-- ^ The user's options for this program, as given by the user (e.g.
-- from @--ld-options@), before they are interpreted for the program's
-- own command line.
--
-- For most programs these are the options for the program itself, and
-- equal to 'programOverrideArgs'. The exception is @ld@: its options
-- are documented as options for GHC's linking phase (GHC receives them
-- as @-optl@ arguments, which forwards them to the C compiler driver
-- acting as the linker), so options for the linker proper have to be
-- given with a @-Wl,@ prefix — see
-- They are not passed to @ld@ when Cabal invokes it directly.
, programOverrideEnv :: [(String, Maybe String)]
-- ^ Override environment variables for this program.
-- These env vars will extend\/override the prevailing environment of
Expand Down Expand Up @@ -164,7 +178,11 @@ programPath = locationPath . programLocation

-- | Suppress any extra arguments added by the user.
suppressOverrideArgs :: ConfiguredProgram -> ConfiguredProgram
suppressOverrideArgs prog = prog{programOverrideArgs = []}
suppressOverrideArgs prog =
prog
{ programOverrideArgs = []
, programDriverArgs = []
}

-- | Make a simple 'ConfiguredProgram'.
--
Expand All @@ -176,6 +194,7 @@ simpleConfiguredProgram name loc =
, programVersion = Nothing
, programDefaultArgs = []
, programOverrideArgs = []
, programDriverArgs = []
, programOverrideEnv = []
, programProperties = Map.empty
, programLocation = loc
Expand Down
18 changes: 18 additions & 0 deletions cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{-# LANGUAGE ForeignFunctionInterface #-}

module Main where

import Foreign.C (CInt (..))

foreign import ccall "pgmllib.h meaning_of_life_pgml"
meaning_of_life_pgml :: IO CInt

main :: IO ()
main = do
secret <- meaning_of_life_pgml
-- The value 66 comes from __wrap_meaning_of_life_pgml: it is only
-- linked in when GHC's linker is driven by scripts/cc-wrapper.sh,
-- which Cabal passes to GHC as -pgml (--with-gcc selects it).
if secret == 66
then putStrLn ("The secret is " ++ show secret)
else error ("Expected value 66, got " ++ show secret)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages: .
26 changes: 26 additions & 0 deletions cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.test.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Test.Cabal.Prelude

-- ForeignOptsPgml, cabal (v2) variant: an explicitly passed -pgml has to
-- drive GHC's final link, taking precedence over the -pgml that Cabal
-- injects itself (user ghc-options come last on the GHC command line).
-- The wrapper redirects an FFI call with -Wl,--wrap; if GHC's link is not
-- driven by the wrapper, the executable fails at runtime.
--
-- Furthermore, whether the linker supports -no-pie is probed by Cabal
-- (like GHC's build system probes its own C compiler): the probe of the
-- resolved C compiler succeeds, so -pgml-supports-no-pie is passed after
-- -pgml.
main = do
skipIfWindows "requires a POSIX shell script as the compiler wrapper"
skipIfOSX "ld64 does not support --wrap"
cabalTest $ recordMode DoNotRecord $ do
-- Cabal only passes -pgml (like -pgmc) on GHC >= 9.4, see the
-- ForeignOptsPgmc test for the rationale.
skipUnlessGhcVersion ">= 9.4"
env <- getTestEnv
let wrapper = testCurrentDir env </> "scripts" </> "cc-wrapper.sh"
res <- cabal' "v2-build" ["-v2", "--ghc-options=-pgml " ++ wrapper, "foreign-opts-pgml-exe"]
assertOutputContains "-pgml-supports-no-pie" res
withPlan $ do
resExe <- runPlanExe' "foreign-opts-pgml" "foreign-opts-pgml-exe" []
assertOutputContains "The secret is 66" resExe
16 changes: 16 additions & 0 deletions cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "pgmllib.h"

/* The "real" implementation - returns 0, the wrong value.
* When the linker is driven by scripts/cc-wrapper.sh (passed to GHC by
* Cabal as -pgml, because the wrapper is the --with-gcc compiler), the
* linker option -Wl,--wrap=meaning_of_life_pgml redirects all calls to
* this function to __wrap_meaning_of_life_pgml below. */
int meaning_of_life_pgml(void) {
return 0;
}

/* The wrapper the linker substitutes in place of the real function.
* Returns 66 - see scripts/cc-wrapper.sh. */
int __wrap_meaning_of_life_pgml(void) {
return 66;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef PGMLLIB_H
#define PGMLLIB_H

int meaning_of_life_pgml(void);

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
cabal-version: 2.2
name: foreign-opts-pgml
version: 0.1
build-type: Simple

executable foreign-opts-pgml-exe
main-is: Main.hs
build-depends: base
default-language: Haskell2010
include-dirs: cbits
c-sources: cbits/pgmllib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh
# Used as the linker driver of GHC's final link:
#
# - in the cabal (v2) test, via the user's explicit -pgml (--ghc-options);
# - in the setup (v1) test, via Cabal's own -pgml: it points GHC's linker at
# the C compiler resolved with --with-gcc.
#
# GHC drives the link with C compiler style options (-Wl,...), so the
# wrapper delegates to `cc`. It adds -no-pie (GHC does not pass -no-pie to a
# custom linker, see GHC #15319) and -Wl,--wrap=meaning_of_life_pgml, which
# redirects all calls to the "real" function to
# __wrap_meaning_of_life_pgml. Compilation invocations (which carry -c) are
# passed through unchanged.
for arg in "$@"; do
if [ "$arg" = "-c" ]; then
exec cc "$@"
fi
done
exec cc -no-pie -Wl,--wrap=meaning_of_life_pgml "$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
# Emulates a C compiler driver without -no-pie support (like GCC before
# version 6) whose default output is not PIE, so linking GHC's objects
# works without the flag: it fails if it ever sees -no-pie (which is the
# shape of Cabal's support probe), and asks the linker itself for non-PIE
# output instead. Used by the ForeignOptsPgml test to assert that Cabal
# probes the resolved C compiler and does not pass -pgml-supports-no-pie
# to GHC when the probe fails.
for arg in "$@"; do
if [ "$arg" = "-no-pie" ]; then
echo "unsupported option -no-pie" >&2
exit 1
fi
done
exec cc -Wl,--no-pie "$@"
Loading
Loading