blob: e11476396416984c5e16942ee8f5b496391c42f2 [file] [log] [blame]
// Copyright 2017 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package descriptor
import (
"bufio"
"bytes"
"debug/elf"
"fmt"
"os/exec"
"path/filepath"
"strings"
"sync"
)
// removeAbsPaths removes paths that start with '/'.
// order will be kept.
func removeAbsPaths(rpaths []string) []string {
var newRpaths []string
for _, rpath := range rpaths {
if strings.HasPrefix(rpath, "/") {
continue
}
newRpaths = append(newRpaths, rpath)
}
return newRpaths
}
// relocateLibraries reads an elf binary to list dependent libraries.
// If a dependent library exists in rpath or runpath, it's considered as
// relocatable. However, rpath or runpath is in absolute form, the dependent
// library is considered as unrelocatable.
func relocatableLibraries(fname string) ([]string, error) {
f, err := elf.Open(fname)
if err != nil {
return nil, err
}
defer f.Close()
tokens := map[string]string{
"ORIGIN": filepath.Dir(fname),
"LIB": libname(f),
"PLATFORM": platform(),
}
rpaths, err := f.DynString(elf.DT_RPATH)
if err != nil {
return nil, err
}
runpaths, err := f.DynString(elf.DT_RUNPATH)
if err != nil {
return nil, err
}
rpaths = append(rpaths, runpaths...)
// When rpath is absolute path, it's not relocatable, so skip it.
// Since $ORIGIN can be absolute, these should be filtered
// before expanding tokens.
rpaths = removeAbsPaths(rpaths)
rpaths = expandRpaths(tokens, rpaths...)
libs, err := f.ImportedLibraries()
if err != nil {
return nil, err
}
var libpaths []string
for _, lib := range libs {
path, err := lookpath(lib, rpaths)
if err != nil {
continue
}
libpaths = append(libpaths, path)
}
return libpaths, nil
}
func libname(f *elf.File) string {
if f.FileHeader.Class == elf.ELFCLASS64 {
return "lib64"
}
return "lib"
}
var (
platformOnce sync.Once
platformName string
)
func platform() string {
platformOnce.Do(func() {
cmd := exec.Command("sleep", "0")
cmd.Env = []string{"LD_SHOW_AUXV=1"}
out, err := cmd.CombinedOutput()
if err != nil {
return
}
s := bufio.NewScanner(bytes.NewReader(out))
for s.Scan() {
cols := strings.Fields(s.Text())
if len(cols) != 2 {
continue
}
if cols[0] == "AT_PLATFORM:" {
platformName = cols[1]
break
}
}
})
return platformName
}
func expandRpaths(tokens map[string]string, rpaths ...string) []string {
var paths []string
for _, rpath := range rpaths {
for _, rpath := range strings.Split(rpath, ":") {
rpath = expand(tokens, rpath)
paths = append(paths, rpath)
}
}
return paths
}
func expand(tokens map[string]string, rpath string) string {
for tok, val := range tokens {
rpath = strings.Replace(rpath, fmt.Sprintf("$%s", tok), val, -1)
rpath = strings.Replace(rpath, fmt.Sprintf("${%s}", tok), val, -1)
}
return rpath
}