Skip to content

Commit

Permalink
C/C++: add a static PIC link style for binaries
Browse files Browse the repository at this point in the history
Summary:
This adds a new static PIC link style for C/C++ binaries which compile
all linked objects with PIC.  This can be used as a base to generate
PIEs (https://securityblog.redhat.com/2012/11/28/position-independent-executables-pie/)
or standalone statically linked shared libs.

Test Plan: added unittests
  • Loading branch information
andrewjcg authored and sdwilsh committed Jul 21, 2015
1 parent 9c52ca0 commit 0a1d56c
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 39 deletions.
7 changes: 1 addition & 6 deletions src/com/facebook/buck/cxx/CxxBinaryDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,9 @@ public boolean hasFlavors(ImmutableSet<Flavor> inputFlavors) {
return flavors.isEmpty();
}

public enum LinkStyle {
STATIC,
SHARED,
}

@SuppressFieldNotInitialized
public static class Arg extends CxxConstructorArg {
public Optional<LinkStyle> linkStyle;
public Optional<Linker.LinkableDepType> linkStyle;
}

}
49 changes: 24 additions & 25 deletions src/com/facebook/buck/cxx/CxxDescriptionEnhancer.java
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,10 @@ public static CxxLinkAndCompileRules createBuildRulesForCxxBinaryDescriptionArg(
ImmutableMap<String, SourcePath> yaccSrcs = parseYaccSources(params, resolver, args);

SourcePathResolver sourcePathResolver = new SourcePathResolver(resolver);
Linker.LinkableDepType linkStyle = args.linkStyle.or(Linker.LinkableDepType.STATIC);
Path output = getOutputPath(params.getBuildTarget());
ImmutableList.Builder<String> extraLdFlagsBuilder = ImmutableList.builder();
CommandTool.Builder executableBuilder = new CommandTool.Builder();

// Setup the rules to run lex/yacc.
CxxHeaderSourceSpec lexYaccSources =
Expand Down Expand Up @@ -798,13 +802,9 @@ public static CxxLinkAndCompileRules createBuildRulesForCxxBinaryDescriptionArg(
cxxPlatform),
preprocessMode,
sources,
CxxSourceRuleFactory.PicType.PDC);

Path output = getOutputPath(params.getBuildTarget());
CxxBinaryDescription.LinkStyle linkStyle =
args.linkStyle.or(CxxBinaryDescription.LinkStyle.STATIC);
ImmutableList.Builder<String> extraLdFlagsBuilder = ImmutableList.builder();
CommandTool.Builder executableBuilder = new CommandTool.Builder();
linkStyle == Linker.LinkableDepType.STATIC ?
CxxSourceRuleFactory.PicType.PDC :
CxxSourceRuleFactory.PicType.PIC);

// Build up the linker flags.
extraLdFlagsBuilder.addAll(
Expand All @@ -814,7 +814,7 @@ public static CxxLinkAndCompileRules createBuildRulesForCxxBinaryDescriptionArg(
cxxPlatform));

// Special handling for dynamically linked binaries.
if (linkStyle == CxxBinaryDescription.LinkStyle.SHARED) {
if (linkStyle == Linker.LinkableDepType.SHARED) {

// Create a symlink tree with for all shared libraries needed by this binary.
SymlinkTree sharedLibraries =
Expand All @@ -841,23 +841,22 @@ public static CxxLinkAndCompileRules createBuildRulesForCxxBinaryDescriptionArg(

// Generate the final link rule. We use the top-level target as the link rule's
// target, so that it corresponds to the actual binary we build.
CxxLink cxxLink = CxxLinkableEnhancer.createCxxLinkableBuildRule(
cxxPlatform,
params,
sourcePathResolver,
/* extraCxxLdFlags */ ImmutableList.<String>of(),
extraLdFlagsBuilder.build(),
createCxxLinkTarget(params.getBuildTarget()),
Linker.LinkType.EXECUTABLE,
Optional.<String>absent(),
output,
objects.values(),
linkStyle == CxxBinaryDescription.LinkStyle.STATIC ?
Linker.LinkableDepType.STATIC :
Linker.LinkableDepType.SHARED,
params.getDeps(),
args.cxxRuntimeType,
Optional.<SourcePath>absent());
CxxLink cxxLink =
CxxLinkableEnhancer.createCxxLinkableBuildRule(
cxxPlatform,
params,
sourcePathResolver,
/* extraCxxLdFlags */ ImmutableList.<String>of(),
extraLdFlagsBuilder.build(),
createCxxLinkTarget(params.getBuildTarget()),
Linker.LinkType.EXECUTABLE,
Optional.<String>absent(),
output,
objects.values(),
linkStyle,
params.getDeps(),
args.cxxRuntimeType,
Optional.<SourcePath>absent());
resolver.addToIndex(cxxLink);

// Add the output of the link as the lone argument needed to invoke this binary as a tool.
Expand Down
2 changes: 1 addition & 1 deletion src/com/facebook/buck/cxx/CxxLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public NativeLinkableInput getNativeLinkableInput(
final BuildRule libraryRule;
ImmutableList.Builder<String> linkerArgsBuilder = ImmutableList.builder();
linkerArgsBuilder.addAll(exportedLinkerFlags.apply(cxxPlatform));
if (type == Linker.LinkableDepType.STATIC || linkage == Linkage.STATIC) {
if (type != Linker.LinkableDepType.SHARED || linkage == Linkage.STATIC) {
libraryRule = CxxDescriptionEnhancer.requireBuildRule(
params,
ruleResolver,
Expand Down
4 changes: 4 additions & 0 deletions src/com/facebook/buck/cxx/Linker.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ enum LinkableDepType {
// static libraries, libfoo.a).
STATIC,

// Provide input suitable for statically linking this linkable using PIC-enabled binaries
// (e.g. return references to static libraries, libfoo_pic.a).
STATIC_PIC,

// Provide input suitable for dynamically linking this linkable (e.g. return references to
// shared libraries, libfoo.so).
SHARED
Expand Down
39 changes: 33 additions & 6 deletions src/com/facebook/buck/cxx/PrebuiltCxxLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.facebook.buck.rules.SourcePathResolver;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
Expand Down Expand Up @@ -121,6 +122,30 @@ private SourcePath requireSharedLibrary(CxxPlatform cxxPlatform) {
return new BuildTargetSourcePath(sharedLibrary.getBuildTarget());
}

/**
* @return the {@link Path} representing the actual static PIC library.
*/
private Path getStaticPicLibrary(CxxPlatform cxxPlatform) {
Path staticPicLibraryPath =
PrebuiltCxxLibraryDescription.getStaticPicLibraryPath(
getBuildTarget(),
cxxPlatform,
libDir,
libName);

// If a specific static-pic variant isn't available, then just use the static variant.
if (!params.getProjectFilesystem().exists(staticPicLibraryPath)) {
staticPicLibraryPath =
PrebuiltCxxLibraryDescription.getStaticLibraryPath(
getBuildTarget(),
cxxPlatform,
libDir,
libName);
}

return staticPicLibraryPath;
}

@Override
public CxxPreprocessorInput getCxxPreprocessorInput(
CxxPlatform cxxPlatform,
Expand Down Expand Up @@ -183,19 +208,21 @@ public NativeLinkableInput getNativeLinkableInput(
// {@link NativeLinkable} interface for linking.
ImmutableList.Builder<SourcePath> librariesBuilder = ImmutableList.builder();
ImmutableList.Builder<String> linkerArgsBuilder = ImmutableList.builder();
linkerArgsBuilder.addAll(exportedLinkerFlags.apply(cxxPlatform));
linkerArgsBuilder.addAll(Preconditions.checkNotNull(exportedLinkerFlags.apply(cxxPlatform)));
if (!headerOnly) {
if (provided || type == Linker.LinkableDepType.SHARED) {
SourcePath sharedLibrary = requireSharedLibrary(cxxPlatform);
librariesBuilder.add(sharedLibrary);
linkerArgsBuilder.add(pathResolver.getPath(sharedLibrary).toString());
} else {
Path staticLibraryPath =
PrebuiltCxxLibraryDescription.getStaticLibraryPath(
getBuildTarget(),
cxxPlatform,
libDir,
libName);
type == Linker.LinkableDepType.STATIC_PIC ?
getStaticPicLibrary(cxxPlatform) :
PrebuiltCxxLibraryDescription.getStaticLibraryPath(
getBuildTarget(),
cxxPlatform,
libDir,
libName);
librariesBuilder.add(new PathSourcePath(getProjectFilesystem(), staticLibraryPath));
if (linkWhole) {
Linker linker = cxxPlatform.getLd();
Expand Down
16 changes: 15 additions & 1 deletion src/com/facebook/buck/cxx/PrebuiltCxxLibraryDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ public static Path getStaticLibraryPath(
return getLibraryPath(target, cxxPlatform, libDir, libName, ".a");
}

public static Path getStaticPicLibraryPath(
BuildTarget target,
CxxPlatform cxxPlatform,
Optional<String> libDir,
Optional<String> libName) {
return getLibraryPath(target, cxxPlatform, libDir, libName, "_pic.a");
}

/**
* @return a {@link SymlinkTree} for the exported headers of this prebuilt C/C++ library.
*/
Expand Down Expand Up @@ -246,7 +254,13 @@ private <A extends Arg> BuildRule createSharedLibraryBuildRule(

BuildTarget target = params.getBuildTarget();
String soname = getSoname(target, cxxPlatform, args.soname, args.libName);
Path staticLibraryPath = getStaticLibraryPath(target, cxxPlatform, args.libDir, args.libName);

// Use the static PIC variant, if available.
Path staticLibraryPath =
getStaticPicLibraryPath(target, cxxPlatform, args.libDir, args.libName);
if (!params.getProjectFilesystem().exists(staticLibraryPath)) {
staticLibraryPath = getStaticLibraryPath(target, cxxPlatform, args.libDir, args.libName);
}

// Otherwise, we need to build it from the static lib.
BuildTarget sharedTarget = BuildTarget
Expand Down
13 changes: 13 additions & 0 deletions test/com/facebook/buck/cxx/CxxBinaryDescriptionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.facebook.buck.rules.BuildTargetSourcePath;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.FakeBuildRuleParamsBuilder;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.TargetGraph;
Expand Down Expand Up @@ -278,4 +279,16 @@ public boolean isTestedBy(BuildTarget buildTarget) {
.toSet());
}

@Test
public void staticPicLinkStyle() {
BuildTarget target = BuildTargetFactory.newInstance("//foo:bar");
BuildRuleResolver resolver = new BuildRuleResolver();
ProjectFilesystem filesystem = new FakeProjectFilesystem();
new CxxBinaryBuilder(target)
.setSrcs(
ImmutableList.of(
SourceWithFlags.of(new PathSourcePath(filesystem, Paths.get("test.cpp")))))
.build(resolver, filesystem);
}

}
15 changes: 15 additions & 0 deletions test/com/facebook/buck/cxx/CxxBinaryIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -716,4 +716,19 @@ public void genruleUsingBinaryUsingSharedLinkStyle() throws IOException {
workspace.runBuckBuild("//:gen").assertSuccess();
}

@Test
public void buildBinaryUsingStaticPicLinkStyle() throws IOException {
assumeThat(Platform.detect(), oneOf(Platform.LINUX, Platform.MACOS));
ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "static_pic_link_style", tmp);
workspace.setUp();
workspace.runBuckCommand(
"build",
// This should only work (on some architectures) if PIC was used to build all included
// object files.
"--config", "cxx.cxxldflags=-shared",
"//:bar")
.assertSuccess();
}

}
26 changes: 26 additions & 0 deletions test/com/facebook/buck/cxx/CxxLibraryDescriptionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.hamcrest.Matchers;
import org.junit.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
Expand Down Expand Up @@ -930,4 +931,29 @@ public void supportedPlatforms() {
Matchers.empty());
}

@Test
public void staticPicLibUsedForStaticPicLinkage() throws IOException {
BuildTarget target = BuildTargetFactory.newInstance("//foo:bar");
BuildRuleResolver resolver = new BuildRuleResolver();
SourcePathResolver pathResolver = new SourcePathResolver(resolver);
ProjectFilesystem filesystem = new FakeProjectFilesystem();
CxxLibraryBuilder libBuilder = new CxxLibraryBuilder(target);
libBuilder.setSrcs(
ImmutableList.of(
SourceWithFlags.of(new PathSourcePath(filesystem, Paths.get("test.cpp")))));
CxxLibrary lib =
(CxxLibrary) libBuilder.build(
resolver,
filesystem,
TargetGraphFactory.newInstance(libBuilder.build()));
NativeLinkableInput nativeLinkableInput =
lib.getNativeLinkableInput(
CxxLibraryBuilder.createDefaultPlatform(),
Linker.LinkableDepType.STATIC_PIC);
SourcePath input = nativeLinkableInput.getInputs().get(0);
assertThat(
pathResolver.getPath(input).toString(),
Matchers.containsString("static-pic"));
}

}
52 changes: 52 additions & 0 deletions test/com/facebook/buck/cxx/PrebuiltCxxLibraryDescriptionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.hamcrest.Matchers;
import org.junit.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

Expand All @@ -62,6 +63,13 @@ private static Path getStaticLibraryPath(PrebuiltCxxLibraryDescription.Arg arg)
String.format("lib%s.a", libName));
}

private static Path getStaticPicLibraryPath(PrebuiltCxxLibraryDescription.Arg arg) {
String libDir = arg.libDir.or("lib");
String libName = arg.libName.or(TARGET.getShortName());
return TARGET.getBasePath().resolve(libDir).resolve(
String.format("lib%s_pic.a", libName));
}

private static Path getSharedLibraryPath(PrebuiltCxxLibraryDescription.Arg arg) {
String libDir = arg.libDir.or("lib");
String libName = arg.libName.or(TARGET.getShortName());
Expand Down Expand Up @@ -589,4 +597,48 @@ public void createBuildRuleHeaderNamespace() {
expectedComponents,
lib.getPythonPackageComponents(CXX_PLATFORM));
}

@Test
public void staticPicLibsUseCorrectPath() {
BuildRuleResolver resolver = new BuildRuleResolver();
ProjectFilesystem filesystem = new FakeProjectFilesystem();
PrebuiltCxxLibraryBuilder libBuilder = new PrebuiltCxxLibraryBuilder(TARGET);
PrebuiltCxxLibrary lib =
(PrebuiltCxxLibrary) libBuilder.build(
resolver,
filesystem,
TargetGraphFactory.newInstance(libBuilder.build()));
NativeLinkableInput nativeLinkableInput =
lib.getNativeLinkableInput(
CXX_PLATFORM,
Linker.LinkableDepType.STATIC_PIC);
SourcePath input = nativeLinkableInput.getInputs().get(0);
assertThat(input, Matchers.instanceOf(PathSourcePath.class));
assertThat(
((PathSourcePath) input).getRelativePath(),
Matchers.equalTo(getStaticLibraryPath(libBuilder.build().getConstructorArg())));
}

@Test
public void missingStaticPicLibsUseStaticLibs() throws IOException {
BuildRuleResolver resolver = new BuildRuleResolver();
ProjectFilesystem filesystem = new FakeProjectFilesystem();
PrebuiltCxxLibraryBuilder libBuilder = new PrebuiltCxxLibraryBuilder(TARGET);
filesystem.touch(getStaticPicLibraryPath(libBuilder.build().getConstructorArg()));
PrebuiltCxxLibrary lib =
(PrebuiltCxxLibrary) libBuilder.build(
resolver,
filesystem,
TargetGraphFactory.newInstance(libBuilder.build()));
NativeLinkableInput nativeLinkableInput =
lib.getNativeLinkableInput(
CXX_PLATFORM,
Linker.LinkableDepType.STATIC_PIC);
SourcePath input = nativeLinkableInput.getInputs().get(0);
assertThat(input, Matchers.instanceOf(PathSourcePath.class));
assertThat(
((PathSourcePath) input).getRelativePath(),
Matchers.equalTo(getStaticPicLibraryPath(libBuilder.build().getConstructorArg())));
}

}
20 changes: 20 additions & 0 deletions test/com/facebook/buck/cxx/testdata/static_pic_link_style/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cxx_library(
name = 'foo',
headers = [
'foo.h',
],
srcs = [
'foo.cpp',
],
)

cxx_binary(
name = 'bar',
link_style = 'static_pic',
srcs = [
'bar.cpp',
],
deps = [
':foo',
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <iostream>

#include "foo.h"

int main() {
std::cout << foo << std::endl;
std::cout << foo() << std::endl;
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int foo() {
return 5;
}
Loading

0 comments on commit 0a1d56c

Please sign in to comment.