gllvm/shared/extractor.go
2017-07-05 17:42:55 -07:00

321 lines
9.0 KiB
Go

package shared
import (
"debug/elf"
"debug/macho"
"flag"
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"strings"
)
type extractionArgs struct {
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
Verbose bool
WriteManifest bool
BuildBitcodeArchive bool
}
//Extract extracts the LLVM bitcode according to the argumets it is passed.
func Extract(args []string) {
ea := parseSwitches()
// Set arguments according to runtime OS
switch platform := runtime.GOOS; platform {
case "freebsd", "linux":
ea.Extractor = extractSectionUnix
if ea.Verbose {
ea.ArArgs = append(ea.ArArgs, "xv")
} else {
ea.ArArgs = append(ea.ArArgs, "x")
}
ea.ObjectTypeInArchive = fileTypeELFOBJECT
case "darwin":
ea.Extractor = extractSectionDarwin
ea.ArArgs = append(ea.ArArgs, "-x")
if ea.Verbose {
ea.ArArgs = append(ea.ArArgs, "-v")
}
ea.ObjectTypeInArchive = fileTypeMACHOBJECT
default:
LogFatal("Unsupported platform: %s.", platform)
}
// Create output filename if not given
if ea.OutputFile == "" {
if ea.InputType == fileTypeARCHIVE {
var ext string
if ea.BuildBitcodeArchive {
ext = ".a.bc"
} else {
ext = ".bca"
}
ea.OutputFile = strings.TrimSuffix(ea.InputFile, ".a") + ext
} else {
ea.OutputFile = ea.InputFile + ".bc"
}
}
switch ea.InputType {
case fileTypeELFEXECUTABLE,
fileTypeELFSHARED,
fileTypeELFOBJECT,
fileTypeMACHEXECUTABLE,
fileTypeMACHSHARED,
fileTypeMACHOBJECT:
handleExecutable(ea)
case fileTypeARCHIVE:
handleArchive(ea)
default:
LogFatal("Incorrect input file type %v.", ea.InputType)
}
}
func parseSwitches() (ea extractionArgs) {
ea = extractionArgs{
LinkerName: "llvm-link",
ArchiverName: "llvm-ar",
}
verbosePtr := flag.Bool("v", false, "verbose mode")
writeManifestPtr := flag.Bool("m", false, "write the manifest")
buildBitcodeArchive := flag.Bool("b", false, "build a bitcode module(FIXME? should this be archive)")
outputFilePtr := flag.String("o", "", "the output file")
archiverNamePtr := flag.String("a", "", "the llvm archiver")
linkerNamePtr := flag.String("l", "", "the llvm linker")
flag.Parse()
ea.Verbose = *verbosePtr
ea.WriteManifest = *writeManifestPtr
ea.BuildBitcodeArchive = *buildBitcodeArchive
if *archiverNamePtr != "" {
ea.ArchiverName = *archiverNamePtr
} else {
if LLVMARName != "" {
ea.ArchiverName = filepath.Join(LLVMToolChainBinDir, LLVMARName)
}
}
if *linkerNamePtr != "" {
ea.LinkerName = *linkerNamePtr
} else {
if LLVMLINKName != "" {
ea.LinkerName = filepath.Join(LLVMToolChainBinDir, LLVMLINKName)
}
}
ea.OutputFile = *outputFilePtr
inputFiles := flag.Args()
LogInfo("ea.Verbose: %v\n", ea.Verbose)
LogInfo("ea.WriteManifest: %v\n", ea.WriteManifest)
LogInfo("ea.BuildBitcodeArchive: %v\n", ea.BuildBitcodeArchive)
LogInfo("ea.ArchiverName: %v\n", ea.ArchiverName)
LogInfo("ea.LinkerName: %v\n", ea.LinkerName)
LogInfo("ea.OutputFile: %v\n", ea.OutputFile)
if len(inputFiles) != 1 {
LogFatal("Can currently only deal with exactly one input file, sorry. You gave me %v\n.", len(inputFiles))
}
ea.InputFile = inputFiles[0]
LogInfo("ea.InputFile: %v\n", ea.InputFile)
if _, err := os.Stat(ea.InputFile); os.IsNotExist(err) {
LogFatal("The input file %s does not exist.", ea.InputFile)
}
realPath, err := filepath.EvalSymlinks(ea.InputFile)
if err != nil {
LogFatal("There was an error getting the real path of %s.", ea.InputFile)
}
ea.InputFile = realPath
ea.InputType = getFileType(realPath)
LogInfo("ea.InputFile real path: %v\n", ea.InputFile)
return
}
func handleExecutable(ea extractionArgs) {
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.WriteManifest {
writeManifest(ea, filesToLink, artifactPaths)
}
}
func handleArchive(ea extractionArgs) {
// List bitcode files to link
var bcFiles []string
var artifactFiles []string
// Create tmp dir
tmpDirName, err := ioutil.TempDir("", "gllvm")
if err != nil {
LogFatal("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)
success, err := execCmd("ar", arArgs, tmpDirName)
if !success {
LogFatal("Failed to extract object files from %s to %s because: %v.\n", ea.InputFile, tmpDirName, err)
}
// Define object file handling closure
var walkHandlingFunc = func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
fileType := getFileType(path)
if fileType == 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.BuildBitcodeArchive {
extractTimeLinkFiles(ea, bcFiles)
} else {
archiveBcFiles(ea, bcFiles)
}
// Write manifest
if ea.WriteManifest {
writeManifest(ea, bcFiles, artifactFiles)
}
}
func archiveBcFiles(ea extractionArgs, bcFiles []string) {
// 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...)
success, err := execCmd(ea.ArchiverName, args, dir)
LogInfo("ea.ArchiverName = %s, args = %v, dir = %s\n", ea.ArchiverName, args, dir)
if !success {
LogFatal("There was an error creating the bitcode archive: %v.\n", err)
}
}
LogInfo("Built bitcode archive: %s.", ea.OutputFile)
}
func extractTimeLinkFiles(ea extractionArgs, filesToLink []string) {
var linkArgs []string
if ea.Verbose {
linkArgs = append(linkArgs, "-v")
}
linkArgs = append(linkArgs, "-o", ea.OutputFile)
linkArgs = append(linkArgs, filesToLink...)
success, err := execCmd(ea.LinkerName, linkArgs, "")
if !success {
LogFatal("There was an error linking input files into %s because %v.\n", ea.OutputFile, err)
}
LogInfo("Bitcode file extracted to: %s.", ea.OutputFile)
}
func extractSectionDarwin(inputFile string) (contents []string) {
machoFile, err := macho.Open(inputFile)
if err != nil {
LogFatal("Mach-O file %s could not be read.", inputFile)
}
section := machoFile.Section(DarwinSectionName)
sectionContents, errContents := section.Data()
if errContents != nil {
LogFatal("Error reading the %s section of Mach-O file %s.", DarwinSectionName, inputFile)
}
contents = strings.Split(strings.TrimSuffix(string(sectionContents), "\n"), "\n")
return
}
func extractSectionUnix(inputFile string) (contents []string) {
elfFile, err := elf.Open(inputFile)
if err != nil {
LogFatal("ELF file %s could not be read.", inputFile)
}
section := elfFile.Section(ELFSectionName)
sectionContents, errContents := section.Data()
if errContents != nil {
LogFatal("Error reading the %s section of ELF file %s.", ELFSectionName, inputFile)
}
contents = strings.Split(strings.TrimSuffix(string(sectionContents), "\n"), "\n")
return
}
// Return the actual path to the bitcode file, or an empty string if it does not exist
func resolveBitcodePath(bcPath string) string {
if _, err := os.Stat(bcPath); os.IsNotExist(err) {
// If the bitcode file does not exist, try to find it in the store
if BitcodeStorePath != "" {
// Compute absolute path hash
absBcPath, _ := filepath.Abs(bcPath)
storeBcPath := path.Join(BitcodeStorePath, getHashedPath(absBcPath))
if _, err := os.Stat(storeBcPath); os.IsNotExist(err) {
return ""
}
return storeBcPath
}
return ""
}
return bcPath
}
func writeManifest(ea extractionArgs, bcFiles []string, artifactFiles []string) {
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 {
LogFatal("There was an error while writing the manifest file: ", err)
}
LogInfo("Manifest file written to %s.", manifestFilename)
}