2017-06-23 23:08:46 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-06-29 21:38:04 +02:00
|
|
|
"debug/elf"
|
|
|
|
"debug/macho"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2017-06-28 23:13:56 +02:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2017-06-29 21:38:04 +02:00
|
|
|
"runtime"
|
2017-06-28 23:13:56 +02:00
|
|
|
"strings"
|
2017-06-23 23:08:46 +02:00
|
|
|
)
|
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
type extractionArgs struct {
|
2017-06-29 21:38:04 +02:00
|
|
|
InputFile string
|
|
|
|
InputType int
|
|
|
|
OutputFile string
|
|
|
|
LinkerName string
|
|
|
|
ArchiverName string
|
|
|
|
ArArgs []string
|
|
|
|
ObjectTypeInArchive int // Type of file that can be put into an archive
|
|
|
|
Extractor func(string) []string
|
|
|
|
IsVerbose bool
|
|
|
|
IsWriteManifest bool
|
2017-06-28 23:13:56 +02:00
|
|
|
IsBuildBitcodeArchive bool
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func extract(args []string) {
|
2017-06-30 02:46:30 +02:00
|
|
|
ea := parseExtractionArgs(args)
|
2017-06-28 23:13:56 +02:00
|
|
|
|
|
|
|
switch ea.InputType {
|
2017-06-30 03:06:31 +02:00
|
|
|
case ftELFEXECUTABLE,
|
|
|
|
ftELFSHARED,
|
|
|
|
ftELFOBJECT,
|
|
|
|
ftMACHEXECUTABLE,
|
|
|
|
ftMACHSHARED,
|
|
|
|
ftMACHOBJECT:
|
2017-06-28 23:13:56 +02:00
|
|
|
handleExecutable(ea)
|
2017-06-30 03:01:03 +02:00
|
|
|
case ftARCHIVE:
|
2017-06-28 23:13:56 +02:00
|
|
|
handleArchive(ea)
|
2017-06-29 21:38:04 +02:00
|
|
|
default:
|
2017-06-28 23:13:56 +02:00
|
|
|
log.Fatal("Incorrect input file type.")
|
|
|
|
}
|
2017-06-23 23:08:46 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
func parseExtractionArgs(args []string) extractionArgs {
|
2017-06-28 23:13:56 +02:00
|
|
|
// Initializing args to defaults
|
2017-06-30 02:46:30 +02:00
|
|
|
ea := extractionArgs{
|
2017-06-29 21:38:04 +02:00
|
|
|
LinkerName: "llvm-link",
|
2017-06-28 23:13:56 +02:00
|
|
|
ArchiverName: "llvm-ar",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checking environment variables
|
2017-06-30 03:06:31 +02:00
|
|
|
if ln := os.Getenv(envLINKERNAME); ln != "" {
|
|
|
|
if toolsPath := os.Getenv(envTOOLSPATH); toolsPath != "" {
|
2017-06-28 23:13:56 +02:00
|
|
|
ea.LinkerName = toolsPath + ln
|
|
|
|
} else {
|
|
|
|
ea.LinkerName = ln
|
|
|
|
}
|
|
|
|
}
|
2017-06-30 03:06:31 +02:00
|
|
|
if an := os.Getenv(envARNAME); an != "" {
|
|
|
|
if toolsPath := os.Getenv(envTOOLSPATH); toolsPath != "" {
|
2017-06-28 23:13:56 +02:00
|
|
|
ea.ArchiverName = toolsPath + an
|
|
|
|
} else {
|
|
|
|
ea.ArchiverName = an
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-30 03:19:07 +02:00
|
|
|
// Parsing cli input. FIXME: "get-bc -mb libfoo.a" should work just like "get-bc -m -b libfoo.a"
|
2017-06-28 23:13:56 +02:00
|
|
|
for len(args) > 0 {
|
|
|
|
switch arg := args[0]; arg {
|
|
|
|
case "-b":
|
|
|
|
ea.IsBuildBitcodeArchive = true
|
|
|
|
args = args[1:]
|
|
|
|
case "-v":
|
|
|
|
ea.IsVerbose = true
|
|
|
|
args = args[1:]
|
|
|
|
case "-m":
|
|
|
|
ea.IsWriteManifest = true
|
|
|
|
args = args[1:]
|
|
|
|
case "-o":
|
|
|
|
if len(args) < 2 {
|
|
|
|
log.Fatal("There was an error parsing the arguments.")
|
|
|
|
}
|
|
|
|
ea.OutputFile = args[1]
|
|
|
|
args = args[2:]
|
|
|
|
default:
|
|
|
|
ea.InputFile = arg
|
|
|
|
args = args[1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sanity-check the parsed arguments
|
|
|
|
if len(ea.InputFile) == 0 {
|
|
|
|
log.Fatal("No input file was given.")
|
|
|
|
}
|
|
|
|
if _, err := os.Stat(ea.InputFile); os.IsNotExist(err) {
|
|
|
|
log.Fatal("The input file ", ea.InputFile, " does not exist.")
|
|
|
|
}
|
|
|
|
realPath, err := filepath.EvalSymlinks(ea.InputFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("There was an error getting the real path of ", ea.InputFile, ".")
|
|
|
|
}
|
|
|
|
ea.InputFile = realPath
|
|
|
|
ea.InputType = getFileType(realPath)
|
|
|
|
|
|
|
|
// Set arguments according to runtime OS
|
|
|
|
switch platform := runtime.GOOS; platform {
|
|
|
|
case "freebsd", "linux":
|
|
|
|
ea.Extractor = extractSectionUnix
|
|
|
|
if ea.IsVerbose {
|
|
|
|
ea.ArArgs = append(ea.ArArgs, "xv")
|
|
|
|
} else {
|
|
|
|
ea.ArArgs = append(ea.ArArgs, "x")
|
|
|
|
}
|
2017-06-30 03:06:31 +02:00
|
|
|
ea.ObjectTypeInArchive = ftELFOBJECT
|
2017-06-28 23:13:56 +02:00
|
|
|
case "darwin":
|
|
|
|
ea.Extractor = extractSectionDarwin
|
|
|
|
ea.ArArgs = append(ea.ArArgs, "-x")
|
|
|
|
if ea.IsVerbose {
|
|
|
|
ea.ArArgs = append(ea.ArArgs, "-v")
|
|
|
|
}
|
2017-06-30 03:06:31 +02:00
|
|
|
ea.ObjectTypeInArchive = ftMACHOBJECT
|
2017-06-28 23:13:56 +02:00
|
|
|
default:
|
|
|
|
log.Fatal("Unsupported platform: ", platform)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create output filename if not given
|
|
|
|
if ea.OutputFile == "" {
|
2017-06-30 03:01:03 +02:00
|
|
|
if ea.InputType == ftARCHIVE {
|
2017-06-28 23:13:56 +02:00
|
|
|
var ext string
|
|
|
|
if ea.IsBuildBitcodeArchive {
|
|
|
|
ext = ".a.bc"
|
|
|
|
} else {
|
|
|
|
ext = ".bca"
|
|
|
|
}
|
|
|
|
ea.OutputFile = strings.TrimSuffix(ea.InputFile, ".a") + ext
|
|
|
|
} else {
|
|
|
|
ea.OutputFile = ea.InputFile + ".bc"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ea
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
func handleExecutable(ea extractionArgs) {
|
2017-06-28 23:13:56 +02:00
|
|
|
artifactPaths := ea.Extractor(ea.InputFile)
|
|
|
|
filesToLink := make([]string, len(artifactPaths))
|
|
|
|
for i, artPath := range artifactPaths {
|
|
|
|
filesToLink[i] = resolveBitcodePath(artPath)
|
|
|
|
}
|
|
|
|
extractTimeLinkFiles(ea, filesToLink)
|
|
|
|
|
|
|
|
// Write manifest
|
|
|
|
if ea.IsWriteManifest {
|
|
|
|
writeManifest(ea, filesToLink, artifactPaths)
|
|
|
|
}
|
2017-06-26 21:42:47 +02:00
|
|
|
}
|
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
func handleArchive(ea extractionArgs) {
|
2017-06-28 23:13:56 +02:00
|
|
|
// List bitcode files to link
|
|
|
|
var bcFiles []string
|
|
|
|
var artifactFiles []string
|
|
|
|
|
|
|
|
// Create tmp dir
|
|
|
|
tmpDirName, err := ioutil.TempDir("", "gllvm")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("The temporary directory in which to extract object files could not be created.")
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpDirName)
|
|
|
|
|
|
|
|
// Extract objects to tmpDir
|
|
|
|
arArgs := ea.ArArgs
|
|
|
|
inputAbsPath, _ := filepath.Abs(ea.InputFile)
|
|
|
|
arArgs = append(arArgs, inputAbsPath)
|
|
|
|
if execCmd("ar", arArgs, tmpDirName) {
|
|
|
|
log.Fatal("Failed to extract object files from ", ea.InputFile, " to ", tmpDirName, ".")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define object file handling closure
|
|
|
|
var walkHandlingFunc = func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err == nil && !info.IsDir() {
|
|
|
|
ft := getFileType(path)
|
|
|
|
if ft == ea.ObjectTypeInArchive {
|
|
|
|
artifactPaths := ea.Extractor(path)
|
|
|
|
for _, artPath := range artifactPaths {
|
|
|
|
bcPath := resolveBitcodePath(artPath)
|
|
|
|
bcFiles = append(bcFiles, bcPath)
|
|
|
|
}
|
|
|
|
artifactFiles = append(artifactFiles, artifactPaths...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle object files
|
|
|
|
filepath.Walk(tmpDirName, walkHandlingFunc)
|
|
|
|
|
|
|
|
// Build archive
|
|
|
|
if ea.IsBuildBitcodeArchive {
|
|
|
|
extractTimeLinkFiles(ea, bcFiles)
|
|
|
|
} else {
|
|
|
|
archiveBcFiles(ea, bcFiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write manifest
|
|
|
|
if ea.IsWriteManifest {
|
|
|
|
writeManifest(ea, bcFiles, artifactFiles)
|
|
|
|
}
|
2017-06-26 21:42:47 +02:00
|
|
|
}
|
2017-06-23 23:08:46 +02:00
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
func archiveBcFiles(ea extractionArgs, bcFiles []string) {
|
2017-06-28 23:13:56 +02:00
|
|
|
// We do not want full paths in the archive, so we need to chdir into each
|
|
|
|
// bitcode's folder. Handle this by calling llvm-ar once for all bitcode
|
|
|
|
// files in the same directory
|
|
|
|
dirToBcMap := make(map[string][]string)
|
|
|
|
for _, bcFile := range bcFiles {
|
|
|
|
dirName, baseName := path.Split(bcFile)
|
|
|
|
dirToBcMap[dirName] = append(dirToBcMap[dirName], baseName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call llvm-ar from each directory
|
|
|
|
absOutputFile, _ := filepath.Abs(ea.OutputFile)
|
|
|
|
for dir, bcFilesInDir := range dirToBcMap {
|
|
|
|
var args []string
|
|
|
|
args = append(args, "rs", absOutputFile)
|
|
|
|
args = append(args, bcFilesInDir...)
|
|
|
|
if execCmd(ea.ArchiverName, args, dir) {
|
|
|
|
log.Fatal("There was an error creating the bitcode archive.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Println("Built bitcode archive", ea.OutputFile)
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
func extractTimeLinkFiles(ea extractionArgs, filesToLink []string) {
|
2017-06-28 23:13:56 +02:00
|
|
|
var linkArgs []string
|
|
|
|
if ea.IsVerbose {
|
|
|
|
linkArgs = append(linkArgs, "-v")
|
|
|
|
}
|
|
|
|
linkArgs = append(linkArgs, "-o", ea.OutputFile)
|
|
|
|
linkArgs = append(linkArgs, filesToLink...)
|
|
|
|
if execCmd(ea.LinkerName, linkArgs, "") {
|
|
|
|
log.Fatal("There was an error linking input files into ", ea.OutputFile, ".")
|
|
|
|
}
|
|
|
|
fmt.Println("Bitcode file extracted to", ea.OutputFile)
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
|
|
|
|
2017-06-28 19:05:19 +02:00
|
|
|
func extractSectionDarwin(inputFile string) (contents []string) {
|
2017-06-28 23:13:56 +02:00
|
|
|
machoFile, err := macho.Open(inputFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Mach-O file ", inputFile, " could not be read.")
|
|
|
|
}
|
2017-06-30 03:06:31 +02:00
|
|
|
section := machoFile.Section(darwinSECTIONNAME)
|
2017-06-28 23:13:56 +02:00
|
|
|
sectionContents, errContents := section.Data()
|
|
|
|
if errContents != nil {
|
2017-06-30 03:06:31 +02:00
|
|
|
log.Fatal("Error reading the ", darwinSECTIONNAME, " section of Mach-O file ", inputFile, ".")
|
2017-06-28 23:13:56 +02:00
|
|
|
}
|
|
|
|
contents = strings.Split(strings.TrimSuffix(string(sectionContents), "\n"), "\n")
|
|
|
|
return
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
|
|
|
|
2017-06-28 19:05:19 +02:00
|
|
|
func extractSectionUnix(inputFile string) (contents []string) {
|
2017-06-28 23:13:56 +02:00
|
|
|
elfFile, err := elf.Open(inputFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("ELF file ", inputFile, " could not be read.")
|
|
|
|
}
|
2017-06-30 03:06:31 +02:00
|
|
|
section := elfFile.Section(elfSECTIONNAME)
|
2017-06-28 23:13:56 +02:00
|
|
|
sectionContents, errContents := section.Data()
|
|
|
|
if errContents != nil {
|
2017-06-30 03:06:31 +02:00
|
|
|
log.Fatal("Error reading the ", elfSECTIONNAME, " section of ELF file ", inputFile, ".")
|
2017-06-28 23:13:56 +02:00
|
|
|
}
|
|
|
|
contents = strings.Split(strings.TrimSuffix(string(sectionContents), "\n"), "\n")
|
|
|
|
return
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
|
|
|
|
2017-06-26 21:42:47 +02:00
|
|
|
// Return the actual path to the bitcode file, or an empty string if it does not exist
|
|
|
|
func resolveBitcodePath(bcPath string) string {
|
2017-06-28 23:13:56 +02:00
|
|
|
if _, err := os.Stat(bcPath); os.IsNotExist(err) {
|
|
|
|
// If the bitcode file does not exist, try to find it in the store
|
2017-06-30 03:06:31 +02:00
|
|
|
if bcStorePath := os.Getenv(envBCSTOREPATH); bcStorePath != "" {
|
2017-06-28 23:13:56 +02:00
|
|
|
// Compute absolute path hash
|
|
|
|
absBcPath, _ := filepath.Abs(bcPath)
|
|
|
|
storeBcPath := path.Join(bcStorePath, getHashedPath(absBcPath))
|
|
|
|
if _, err := os.Stat(storeBcPath); os.IsNotExist(err) {
|
|
|
|
return ""
|
|
|
|
}
|
2017-06-29 23:19:12 +02:00
|
|
|
return storeBcPath
|
2017-06-28 23:13:56 +02:00
|
|
|
}
|
2017-06-29 23:25:55 +02:00
|
|
|
return ""
|
2017-06-29 23:44:39 +02:00
|
|
|
}
|
2017-06-29 23:25:55 +02:00
|
|
|
return bcPath
|
2017-06-26 21:42:47 +02:00
|
|
|
}
|
|
|
|
|
2017-06-23 23:08:46 +02:00
|
|
|
func getFileType(realPath string) (fileType int) {
|
2017-06-28 23:13:56 +02:00
|
|
|
// We need the file command to guess the file type
|
|
|
|
cmd := exec.Command("file", realPath)
|
|
|
|
out, err := cmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("There was an error getting the type of ", realPath, ". Make sure that the 'file' command is installed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test the output
|
|
|
|
if fo := string(out); strings.Contains(fo, "ELF") && strings.Contains(fo, "executable") {
|
2017-06-30 03:06:31 +02:00
|
|
|
fileType = ftELFEXECUTABLE
|
2017-06-28 23:13:56 +02:00
|
|
|
} else if strings.Contains(fo, "Mach-O") && strings.Contains(fo, "executable") {
|
2017-06-30 03:06:31 +02:00
|
|
|
fileType = ftMACHEXECUTABLE
|
2017-06-28 23:13:56 +02:00
|
|
|
} else if strings.Contains(fo, "ELF") && strings.Contains(fo, "shared") {
|
2017-06-30 03:06:31 +02:00
|
|
|
fileType = ftELFSHARED
|
2017-06-28 23:13:56 +02:00
|
|
|
} else if strings.Contains(fo, "Mach-O") && strings.Contains(fo, "dynamically linked shared") {
|
2017-06-30 03:06:31 +02:00
|
|
|
fileType = ftMACHSHARED
|
2017-06-28 23:13:56 +02:00
|
|
|
} else if strings.Contains(fo, "current ar archive") {
|
2017-06-30 03:01:03 +02:00
|
|
|
fileType = ftARCHIVE
|
2017-06-28 23:13:56 +02:00
|
|
|
} else if strings.Contains(fo, "ELF") && strings.Contains(fo, "relocatable") {
|
2017-06-30 03:06:31 +02:00
|
|
|
fileType = ftELFOBJECT
|
2017-06-28 23:13:56 +02:00
|
|
|
} else if strings.Contains(fo, "Mach-O") && strings.Contains(fo, "object") {
|
2017-06-30 03:06:31 +02:00
|
|
|
fileType = ftMACHOBJECT
|
2017-06-28 23:13:56 +02:00
|
|
|
} else {
|
2017-06-30 03:01:03 +02:00
|
|
|
fileType = ftUNDEFINED
|
2017-06-28 23:13:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2017-06-23 23:08:46 +02:00
|
|
|
}
|
2017-06-26 21:42:47 +02:00
|
|
|
|
2017-06-30 02:46:30 +02:00
|
|
|
func writeManifest(ea extractionArgs, bcFiles []string, artifactFiles []string) {
|
2017-06-28 23:13:56 +02:00
|
|
|
section1 := "Physical location of extracted files:\n" + strings.Join(bcFiles, "\n") + "\n\n"
|
|
|
|
section2 := "Build-time location of extracted files:\n" + strings.Join(artifactFiles, "\n")
|
|
|
|
contents := []byte(section1 + section2)
|
|
|
|
manifestFilename := ea.OutputFile + ".llvm.manifest"
|
|
|
|
if err := ioutil.WriteFile(manifestFilename, contents, 0644); err != nil {
|
|
|
|
log.Fatal("There was an error while writing the manifest file: ", err)
|
|
|
|
}
|
|
|
|
fmt.Println("Manifest file written to", manifestFilename)
|
2017-06-26 21:42:47 +02:00
|
|
|
}
|