gaulthiergain-tools/srcs/dependtool/static_analyser.go
Gaulthier Gain 597c47c27d Remove nm and readelf subroutines and replace them by elf package API
Signed-off-by: Gaulthier Gain <gaulthier.gain@uliege.be>
2021-05-24 09:54:26 +02:00

265 lines
7.1 KiB
Go

// Copyright 2019 The UNICORE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
//
// Author: Gaulthier Gain <gaulthier.gain@uliege.be>
package dependtool
import (
"bufio"
"debug/elf"
"fmt"
"os"
"runtime"
"strings"
u "tools/srcs/common"
)
// ---------------------------------Gather Data---------------------------------
func addSymbols(symbols []elf.Symbol, data *u.StaticData, systemCalls map[string]int) {
for _, s := range symbols {
if _, isSyscall := systemCalls[s.Name]; isSyscall {
data.SystemCalls[s.Name] = systemCalls[s.Name]
} else {
data.Symbols[s.Name] = s.Library
}
}
}
// gatherStaticSymbols gathers symbols of a given application.
//
// It returns an error if any, otherwise it returns nil.
func gatherStaticSymbols(elfFile *elf.File, dynamicCompiled bool, data *u.StaticData) error {
// Get the list of system calls
systemCalls := initSystemCalls()
symbols, err := elfFile.Symbols()
if err != nil {
if !dynamicCompiled {
// Skip message for dynamically compiled binary
u.PrintWarning(err)
}
} else {
addSymbols(symbols, data, systemCalls)
}
// Additional symbols when dynamically compiled
if dynamicCompiled {
symbols, err = elfFile.DynamicSymbols()
if err != nil {
u.PrintWarning(err)
} else {
addSymbols(symbols, data, systemCalls)
}
importedSymbols, err := elfFile.ImportedSymbols()
if err != nil {
u.PrintWarning(err)
} else {
for _, s := range importedSymbols {
if _, isSyscall := systemCalls[s.Name]; isSyscall {
data.SystemCalls[s.Name] = systemCalls[s.Name]
} else {
data.Symbols[s.Name] = s.Library
}
}
}
}
return nil
}
// gatherStaticSymbols gathers shared libs of a given application.
//
// It returns an error if any, otherwise it returns nil.
func gatherStaticSharedLibsLinux(programPath string, data *u.StaticData,
v bool) error {
// Use 'ldd' to get shared libraries
if output, err := u.ExecutePipeCommand("ldd " + programPath +
" | awk '/ => / { print $1,$3 }'"); err != nil {
return err
} else {
// Init SharedLibs
lddGlMap := make(map[string][]string)
_ = parseLDD(output, data.SharedLibs, lddGlMap, v)
}
return nil
}
func gatherStaticSharedLibsMac(programPath string, data *u.StaticData,
v bool) error {
// Use 'ldd' to get shared libraries
if output, err := u.ExecutePipeCommand("otool -L " + programPath +
" | awk '{ print $1 }'"); err != nil {
return err
} else {
// Init SharedLibs
lddGlMap := make(map[string][]string)
_ = parseLDDMac(output, data.SharedLibs, lddGlMap, v)
}
return nil
}
// gatherDependencies gathers dependencies of a given application.
//
// It returns an error if any, otherwise it returns nil.
func gatherDependencies(programName string, data *u.StaticData, v bool) error {
// Use 'apt-cache pkgnames' to get the name of the package
output, err := u.ExecuteCommand("apt-cache",
[]string{"pkgnames", programName})
if err != nil {
return err
}
// If the name of the package is know, execute apt-cache depends
if len(output) > 0 {
// Parse package name
packageName := parsePackagesName(output)
if len(packageName) > 0 {
return executeDependAptCache(packageName, data, v)
}
} else {
// Enter manually the name of the package
u.PrintWarning(programName + " not found in apt-cache")
var output string
for len(output) == 0 {
fmt.Print("Please enter manually the name of the package " +
"(empty string to exit): ")
scanner := bufio.NewScanner(os.Stdin)
if err := scanner.Err(); err != nil {
return err
}
if scanner.Scan() {
// Get the new package name
input := scanner.Text()
if input == "" {
break
}
output, err = u.ExecuteCommand("apt-cache",
[]string{"pkgnames", input})
if err != nil {
return err
}
}
}
if len(output) == 0 {
u.PrintWarning("Skip dependencies analysis from apt-cache depends")
} else {
packageName := parsePackagesName(output)
return executeDependAptCache(packageName, data, v)
}
}
return nil
}
// executeDependAptCache gathers dependencies by executing 'apt-cache depends'.
//
// It returns an error if any, otherwise it returns nil.
func executeDependAptCache(programName string, data *u.StaticData,
fullDeps bool) error {
// Use 'apt-cache depends' to get dependencies
if output, err := u.ExecutePipeCommand("apt-cache depends " +
programName + " | awk '/Depends/ { print $2 }'"); err != nil {
return err
} else {
// Init Dependencies (from apt cache depends)
data.Dependencies = make(map[string][]string)
dependenciesMap := make(map[string][]string)
printDep := make(map[string][]string)
_ = parseDependencies(output, data.Dependencies, dependenciesMap,
printDep, fullDeps, 0)
}
fmt.Println("----------------------------------------------")
return nil
}
// -------------------------------------Run-------------------------------------
// staticAnalyser runs the static analysis to get shared libraries,
// system calls and library calls of a given application.
//
func staticAnalyser(elfFile *elf.File, dynamicCompiled bool, args u.Arguments, data *u.Data, programPath string) {
programName := *args.StringArg[programArg]
fullDeps := *args.BoolArg[fullDepsArg]
fullStaticAnalysis := *args.BoolArg[fullStaticAnalysis]
staticData := &data.StaticData
// If the program is a binary, runs static analysis tools
if len(programPath) > 0 {
// Gather Data from binary file
// Init symbols members
staticData.Symbols = make(map[string]string)
staticData.SystemCalls = make(map[string]int)
staticData.SharedLibs = make(map[string][]string)
if strings.ToLower(runtime.GOOS) == "linux" {
u.PrintHeader2("(*) Gathering symbols from binary file")
if err := gatherStaticSymbols(elfFile, dynamicCompiled, staticData); err != nil {
u.PrintWarning(err)
}
}
u.PrintHeader2("(*) Gathering shared libraries from binary file")
if strings.ToLower(runtime.GOOS) == "linux" {
// Cannot use "elfFile.ImportedLibraries()" since we need the .so path
if err := gatherStaticSharedLibsLinux(programPath, staticData,
fullDeps); err != nil {
u.PrintWarning(err)
}
} else {
if err := gatherStaticSharedLibsMac(programPath, staticData,
fullDeps); err != nil {
u.PrintWarning(err)
}
}
if err := elfFile.Close(); err != nil {
u.PrintWarning(err)
}
}
// Detect symbols from shared libraries
if fullStaticAnalysis {
u.PrintHeader2("(*) Gathering symbols and system calls of shared libraries from binary file")
for key, path := range staticData.SharedLibs {
if len(path) > 0 {
fmt.Printf("\t-> Analysing %s - %s\n", key, path[0])
libElf, err := getElf(path[0])
if err != nil {
u.PrintWarning(err)
}
if err := gatherStaticSymbols(libElf, true, staticData); err != nil {
u.PrintWarning(err)
}
if err := libElf.Close(); err != nil {
u.PrintWarning(err)
}
}
}
}
if strings.ToLower(runtime.GOOS) == "linux" {
// Gather Data from apt-cache
u.PrintHeader2("(*) Gathering dependencies from apt-cache depends")
if err := gatherDependencies(programName, staticData, fullDeps); err != nil {
u.PrintWarning(err)
}
}
}