Add binary analyser code to the toolchain

This commit adds the binary analyser code to the toolchain. This
binary analyser adds various features such as gathering ELF
information (e.g., functions, symbols, ...), micro-libs inspection,
ELF comparison, ELF pages division, ... Note that it was developped
with Unikraft structure in mind.

The binary analyser can be used as an external tool with the
'--binary' argument. A specific json file should be provided which
contains specific fields (see 'bin_analysis_example.json'). Please,
see the wiki for further information.

Signed-off-by: Gaulthier Gain <gaulthier.gain@uliege.be>
This commit is contained in:
Gaulthier Gain 2021-01-30 12:37:27 +01:00
parent 81e5677e1a
commit f25637cdb7
21 changed files with 2331 additions and 1 deletions

40
bin_analysis_example.json Normal file
View file

@ -0,0 +1,40 @@
{
"unikernels": [
{
"buildPath": "/Users/unicore/buildCompare/lib-sqlite",
"ignoredPlats": [
"linuxu",
"xen"
],
"kernel": "lib-sqlite3_kvm-x86_64.dbg",
"splitSection": ".text",
"displayMapping": true,
"displayStatSize": false,
"displayElfFile": [
"sections"
],
"displaySectionInfo": [],
"findSectionByAddress": [
"0x43a74e"
],
"compareGroup": 1
},
{
"buildPath": "/Users/unicore/buildCompare/app-helloworld",
"ignoredPlats": [
"linuxu",
"xen"
],
"kernel": "lib-redis_kvm-x86_64.dbg",
"splitSection": ".text",
"displayElfFile": [],
"displayMapping": true,
"displayStatSize": true,
"displaySectionInfo": [],
"findSectionByAddress": [
"0x427836"
],
"compareGroup": 1
}
]
}

38
srcs/binarytool/args.go Normal file
View file

@ -0,0 +1,38 @@
// 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 binarytool
import (
"github.com/akamensky/argparse"
"os"
u "tools/srcs/common"
)
const (
filesArg = "file"
mappingArg = "mapping"
listArg = "list"
)
// ParseArguments parses arguments of the application.
//
// It returns an error if any, otherwise it returns nil.
func parseLocalArguments(p *argparse.Parser, args *u.Arguments) error {
args.InitArgParse(p, args, u.BOOL, "m", mappingArg,
&argparse.Options{Required: false, Default: false,
Help: "Display libraries mapping (required -l argument)"})
args.InitArgParse(p, args, u.STRING, "l", listArg,
&argparse.Options{Required: false, Help: "A list of build directories " +
"to analyse (sep: ,)"})
args.InitArgParse(p, args, u.STRING, "f", filesArg,
&argparse.Options{Required: false, Help: "Json file that contains " +
"the information for the binary analyser"})
return u.ParserWrapper(p, os.Args)
}

View file

@ -0,0 +1,251 @@
// 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 elf64analyser
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"tools/srcs/binarytool/elf64core"
u "tools/srcs/common"
)
type ElfAnalyser struct {
ElfLibs []ElfLibs
ElfPage []*ElfPage
}
type ElfLibs struct {
Name string
StartAddr uint64
EndAddr uint64
Size uint64
NbSymbols int
}
func (analyser *ElfAnalyser) DisplayMapping() {
if len(analyser.ElfLibs) == 0 {
fmt.Println("Mapping is empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
_, _ = fmt.Fprintln(w, "Name \tStart \tEnd \tSize \tNbSymbols\tnbDiv")
for _, lib := range analyser.ElfLibs {
var name = lib.Name
if strings.Contains(lib.Name, string(os.PathSeparator)) {
split := strings.Split(lib.Name, string(os.PathSeparator))
name = split[len(split)-1]
}
_, _ = fmt.Fprintf(w, "%s \t0x%x \t0x%x \t0x%x\t%d\t%f\n",
name, lib.StartAddr, lib.EndAddr, lib.Size,
lib.NbSymbols, float32(lib.StartAddr)/float32(pageSize))
}
_ = w.Flush()
}
func filterFunctions(objFuncsAll []elf64core.ELF64Function, elfFuncsAll []elf64core.ELF64Function) []*elf64core.ELF64Function {
i := 0
filteredFuncs := make([]*elf64core.ELF64Function, len(objFuncsAll))
for j, _ := range elfFuncsAll {
if strings.Compare(objFuncsAll[i].Name, elfFuncsAll[j].Name) == 0 {
filteredFuncs[i] = &elfFuncsAll[j]
i++
} else {
// Reset the counter if we do not have consecutive functions
i = 0
// Special case where we can skip a function if we do not check again
if strings.Compare(objFuncsAll[i].Name, elfFuncsAll[j].Name) == 0 {
filteredFuncs[i] = &elfFuncsAll[j]
i++
}
}
if i == len(objFuncsAll) {
return filteredFuncs
}
}
return nil
}
func compareFunctions(elf *elf64core.ELF64File, obj *elf64core.ELF64File) (uint64, uint64, int) {
// Obj: Merge all functions table(s) in one slice for simplicity
objFuncs := make([]elf64core.ELF64Function, 0)
for i := len(obj.FunctionsTables) - 1; i >= 0; i-- {
if strings.Compare(obj.FunctionsTables[i].Name, elf64core.BootTextSection) != 0 {
// Ignore the '.text.boot' section since it can be split through
// different places
objFuncs = append(objFuncs, obj.FunctionsTables[i].Functions...)
}
}
// Elf: Merge all functions table(s) in one slice for simplicity
elfFuncs := make([]elf64core.ELF64Function, 0)
for i := len(elf.FunctionsTables) - 1; i >= 0; i-- {
if strings.Compare(elf.FunctionsTables[i].Name, elf64core.BootTextSection) != 0 {
// Ignore the '.text.boot' section since it can be split through
// different places
elfFuncs = append(elfFuncs, elf.FunctionsTables[i].Functions...)
}
}
// Add functions into a map for better search
mapObjFuncs := make(map[string]*elf64core.ELF64Function)
for i := 0; i < len(objFuncs); i++ {
mapObjFuncs[objFuncs[i].Name] = &objFuncs[i]
}
elfFuncsAll := make([]elf64core.ELF64Function, 0)
mapArrayFuncs := make(map[string]uint64, 0)
for _, elfFunc := range elfFuncs {
if _, ok := mapObjFuncs[elfFunc.Name]; ok {
// Check if the function is already in mapArrayFuncs
val, ok := mapArrayFuncs[elfFunc.Name]
// Do not add duplicate functions (check on addresses)
if !ok {
mapArrayFuncs[elfFunc.Name] = elfFunc.Addr
elfFuncsAll = append(elfFuncsAll, elfFunc)
} else if val != elfFunc.Addr {
elfFuncsAll = append(elfFuncsAll, elfFunc)
}
}
}
if len(elfFuncsAll) == 0 {
u.PrintWarning(fmt.Sprintf("Cannot extract mapping of lib %s: No function", obj.Name))
return 0, 0, 0
}
if len(elfFuncsAll) != len(objFuncs) {
// We do not have the same set of functions, need to filter it.
filteredFuncs := filterFunctions(objFuncs, elfFuncsAll)
if filteredFuncs == nil {
u.PrintWarning(fmt.Sprintf("Cannot extract mapping of lib %s: Different size", obj.Name))
return 0, 0, 0
}
return filteredFuncs[0].Addr, filteredFuncs[len(filteredFuncs)-1].Size +
filteredFuncs[len(filteredFuncs)-1].Addr, len(filteredFuncs)
}
return elfFuncsAll[0].Addr, elfFuncsAll[len(elfFuncsAll)-1].Size +
elfFuncsAll[len(elfFuncsAll)-1].Addr, len(elfFuncsAll)
}
func (analyser *ElfAnalyser) InspectMapping(elf *elf64core.ELF64File, objs ...interface{}) {
if len(objs) == 0 {
return
}
analyser.ElfLibs = make([]ElfLibs, 0)
for _, iobj := range objs {
obj := iobj.(*elf64core.ELF64File)
start, end, nbSymbols := compareFunctions(elf, obj)
analyser.ElfLibs = append(analyser.ElfLibs, ElfLibs{
Name: obj.Name,
StartAddr: start,
EndAddr: end,
Size: end - start,
NbSymbols: nbSymbols,
})
return
}
// sort functions
sort.Slice(analyser.ElfLibs, func(i, j int) bool {
return analyser.ElfLibs[i].StartAddr < analyser.ElfLibs[j].StartAddr
})
}
func (analyser *ElfAnalyser) InspectMappingList(elf *elf64core.ELF64File,
objs []*elf64core.ELF64File) {
if len(objs) == 0 {
return
}
analyser.ElfLibs = make([]ElfLibs, 0)
for _, obj := range objs {
start, end, nbSymbols := compareFunctions(elf, obj)
analyser.ElfLibs = append(analyser.ElfLibs, ElfLibs{
Name: obj.Name,
StartAddr: start,
EndAddr: end,
Size: end - start,
NbSymbols: nbSymbols,
})
}
// sort functions by start address.
sort.Slice(analyser.ElfLibs, func(i, j int) bool {
return analyser.ElfLibs[i].StartAddr < analyser.ElfLibs[j].StartAddr
})
}
func (analyser *ElfAnalyser) SplitIntoPagesBySection(elfFile *elf64core.ELF64File, sectionName string) {
if len(analyser.ElfPage) == 0 {
analyser.ElfPage = make([]*ElfPage, 0)
}
if strings.Contains(sectionName, elf64core.TextSection) {
// An ELF might have several text sections
for _, indexSection := range elfFile.TextSectionIndex {
sectionName := elfFile.SectionsTable.DataSect[indexSection].Name
analyser.computePage(elfFile, sectionName, indexSection)
}
} else if indexSection, ok := elfFile.IndexSections[sectionName]; ok {
analyser.computePage(elfFile, sectionName, indexSection)
} else {
u.PrintWarning(fmt.Sprintf("Cannot split section %s into pages", sectionName))
}
}
func CreateNewPage(startAddress uint64, k int, raw []byte) *ElfPage {
byteArray := make([]byte, pageSize)
b := raw
if cpd := copy(byteArray, b); cpd == 0 {
u.PrintWarning("0 bytes were copied")
}
page := &ElfPage{
number: k,
startAddress: startAddress,
contentByteArray: byteArray,
}
h := sha256.New()
h.Write(page.contentByteArray)
page.hash = hex.EncodeToString(h.Sum(nil))
return page
}
func (analyser *ElfAnalyser) computePage(elfFile *elf64core.ELF64File, section string, indexSection int) {
offsetTextSection := elfFile.SectionsTable.DataSect[indexSection].Elf64section.FileOffset
k := 0
for i := offsetTextSection; i < offsetTextSection+elfFile.SectionsTable.DataSect[indexSection].Elf64section.Size; i += pageSize {
end := i + pageSize
if end >= uint64(len(elfFile.Raw)) {
end = uint64(len(elfFile.Raw) - 1)
}
page := CreateNewPage(i, k, elfFile.Raw[i:end])
page.sectionName = section
analyser.ElfPage = append(analyser.ElfPage, page)
k++
}
}

View file

@ -0,0 +1,124 @@
// 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 elf64analyser
import (
"fmt"
"io"
"os"
"strings"
)
const pageSize = 0x1000
type ElfFileSegment struct {
Filename string
NbPages int
Pages []*ElfPage
}
type ElfPage struct {
number int
startAddress uint64
contentByteArray []byte
hash string
libName string
sectionName string
noNullValues int
cap int
}
func (p *ElfPage) pageContentToString() string {
var builder strings.Builder
for i, entry := range p.contentByteArray {
if i > 0 && i%4 == 0 {
builder.WriteString(" ")
}
if i > 0 && i%16 == 0 {
builder.WriteString("\n")
}
_, _ = builder.WriteString(fmt.Sprintf("%02x", entry))
}
_, _ = builder.WriteString("")
return builder.String()
}
func (p *ElfPage) displayPageContent(mw io.Writer) {
/*
hexStartAddr, err := strconv.ParseInt(p.startAddress, 16, 64);
if err != nil {
panic(err)
}
*/
for i, entry := range p.contentByteArray {
if i > 0 && i%4 == 0 {
_, _ = fmt.Fprintf(mw, " ")
}
if i > 0 && i%16 == 0 {
_, _ = fmt.Fprintf(mw, "\n")
}
_, _ = fmt.Fprintf(mw, "%02x", entry)
}
_, _ = fmt.Fprintln(mw, "")
}
func (p *ElfPage) displayPageContentShort(mw io.Writer) {
entryLine := 0
for i, entry := range p.contentByteArray {
if entry > 0 {
_, _ = fmt.Fprintf(mw, "[%d] %02x ", i, entry)
if entryLine > 0 && entryLine%16 == 0 {
_, _ = fmt.Fprintf(mw, "\n")
}
entryLine++
}
}
_, _ = fmt.Fprintln(mw, "")
}
func SavePagesToFile(pageTables []*ElfPage, filename string, shortView bool) error {
mw := io.MultiWriter(os.Stdout)
if len(filename) > 0 {
file, err := os.Create(filename)
if err != nil {
return err
}
mw = io.MultiWriter(file)
}
for i, p := range pageTables {
_, _ = fmt.Fprintln(mw, "----------------------------------------------------")
_, _ = fmt.Fprintf(mw, "Page: %d\n", i+1)
_, _ = fmt.Fprintf(mw, "LibName: %s\n", p.libName)
_, _ = fmt.Fprintf(mw, "Section: %s\n", p.sectionName)
_, _ = fmt.Fprintf(mw, "StartAddr: %x (%d)\n", p.startAddress, p.startAddress)
_, _ = fmt.Fprintf(mw, "Non-Null value: %d\n", p.noNullValues)
_, _ = fmt.Fprintf(mw, "Hash: %s\n", p.hash)
if shortView {
p.displayPageContentShort(mw)
} else {
p.displayPageContent(mw)
}
_, _ = fmt.Fprintln(mw, "----------------------------------------------------")
}
return nil
}

View file

@ -0,0 +1,227 @@
// 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 elf64analyser
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
"tools/srcs/binarytool/elf64core"
u "tools/srcs/common"
)
type ComparisonElf struct {
GroupFileSegment []*ElfFileSegment
dictSamePage map[string]int
dictFile map[string]map[string]int
}
func (comparison *ComparisonElf) processDictName(filename, hash string) {
m := comparison.dictFile[hash]
if _, ok := m[filename]; !ok {
m[filename] = 1
} else {
t := m[filename]
t += 1
m[filename] = t
}
comparison.dictFile[hash] = m
}
func (comparison *ComparisonElf) ComparePageTables() {
comparison.dictSamePage = make(map[string]int)
comparison.dictFile = make(map[string]map[string]int)
for _, file := range comparison.GroupFileSegment {
for _, p := range file.Pages {
if _, ok := comparison.dictSamePage[p.hash]; !ok {
comparison.dictSamePage[p.hash] = 1
comparison.dictFile[p.hash] = make(map[string]int)
} else {
comparison.dictSamePage[p.hash] += 1
}
comparison.processDictName(file.Filename, p.hash)
}
}
}
func (comparison *ComparisonElf) DisplayComparison() {
fmt.Println("\n\nHash comparison:")
for key, value := range comparison.dictFile {
fmt.Println(key, ";", value)
}
fmt.Println("---------------------------")
fmt.Println("\n\nStats:")
countSamePage := 0
singlePage := 0
for _, value := range comparison.dictSamePage {
if value > 1 {
countSamePage += value
} else {
singlePage++
}
}
totalNbPages := 0
for i, _ := range comparison.GroupFileSegment {
totalNbPages += comparison.GroupFileSegment[i].NbPages
}
ratio := (float64(countSamePage) / float64(totalNbPages)) * 100
fmt.Printf("- Total Nb of pages: %d\n", totalNbPages)
fmt.Printf("- Nb page(s) sharing: %d\n", countSamePage)
fmt.Printf("- Page alone: %d\n", singlePage)
fmt.Printf("- Ratio: %f\n", ratio)
}
func filterOutput(text1, text2 []string) string {
header := "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><title>Diff Pages</title></head><body style=\"font-family:Menlo\">"
footer := "</body></html>"
maxArray := text1
minArray := text2
if len(text1) < len(text2) {
maxArray = text2
minArray = text1
}
var builder strings.Builder
for i := 0; i < len(maxArray); i++ {
builder.WriteString("<span>" + maxArray[i] + "</span><br>")
if i < len(minArray)-1 && maxArray[i] != minArray[i] {
builder.WriteString("<p><del style=\"background:#ffe6e6;\">" + minArray[i] + "</del><br>")
builder.WriteString("<ins style=\"background:#e6ffe6;\">" + maxArray[i] + "</ins></p>")
}
}
return header + builder.String() + footer
}
func (comparison *ComparisonElf) DiffComparison(path string) error {
if len(comparison.GroupFileSegment) != 2 {
return errors.New("multi-comparison (more than 2) is still not supported")
}
minPage := comparison.GroupFileSegment[0].Pages
for _, file := range comparison.GroupFileSegment {
if len(minPage) > len(file.Pages) {
minPage = file.Pages
}
}
println(len(comparison.GroupFileSegment[0].Pages))
println(len(comparison.GroupFileSegment[1].Pages))
for i := 0; i < len(minPage); i++ {
page1 := comparison.GroupFileSegment[0].Pages[i]
page2 := comparison.GroupFileSegment[1].Pages[i]
if page1.hash != page2.hash {
text1String := comparison.GroupFileSegment[0].Pages[i].pageContentToString()
text2String := comparison.GroupFileSegment[1].Pages[i].pageContentToString()
text1 := strings.Split(text1String, "\n")
text2 := strings.Split(text2String, "\n")
str := filterOutput(text1, text2)
file, err := os.Create(path + "page_" + strconv.Itoa(page1.number) + "_diff.html")
if err != nil {
return err
}
if _, err := file.WriteString(str); err != nil {
return err
}
file.Close()
}
}
return nil
}
func (analyser *ElfAnalyser) DisplaySectionInfo(elfFile *elf64core.ELF64File, info []string) {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 10, 8, 0, '\t', 0)
_, _ = fmt.Fprintln(w, "Name\tAddress\tOffset\tSize")
for _, sectionName := range info {
if indexSection, ok := elfFile.IndexSections[sectionName]; ok {
section := elfFile.SectionsTable.DataSect[indexSection].Elf64section
_, _ = fmt.Fprintf(w, "- %s\t0x%.6x\t0x%.6x\t%d\n",
sectionName, section.VirtualAddress, section.FileOffset,
section.Size)
} else {
u.PrintWarning("Wrong section name " + sectionName)
}
}
_ = w.Flush()
}
func (analyser *ElfAnalyser) FindSectionByAddress(elfFile *elf64core.ELF64File, addresses []string) {
if len(elfFile.SectionsTable.DataSect) == 0 {
u.PrintWarning("Sections table is empty")
return
}
for _, addr := range addresses {
hexStr := strings.Replace(addr, "0x", "", -1)
intAddr, err := strconv.ParseUint(hexStr, 16, 64)
if err != nil {
u.PrintWarning(fmt.Sprintf("Error %s: Cannot convert %s to integer. Skip.", err, addr))
} else {
found := false
for _, s := range elfFile.SectionsTable.DataSect {
if s.Elf64section.VirtualAddress <= intAddr && intAddr < s.Elf64section.VirtualAddress+s.Elf64section.Size {
fmt.Printf("Address %s is in section %s\n", addr, s.Name)
found = true
}
}
if !found {
u.PrintWarning(fmt.Sprintf("Cannot find a section for address: %s", addr))
}
}
}
}
func (analyser *ElfAnalyser) DisplayStatSize(elfFile *elf64core.ELF64File) {
if len(elfFile.SectionsTable.DataSect) == 0 {
u.PrintWarning("Sections table is empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
var totalSizeText uint64
var totalSizeElf uint64
_, _ = fmt.Fprintf(w, "Name\tFile size (Bytes/Hex)\n")
for _, s := range elfFile.SectionsTable.DataSect {
if len(s.Name) > 0 {
_, _ = fmt.Fprintf(w, "%s\t%d (0x%x)\n", s.Name, s.Elf64section.Size, s.Elf64section.Size)
}
if strings.Contains(s.Name, elf64core.TextSection) {
totalSizeText += s.Elf64section.Size
}
totalSizeElf += s.Elf64section.Size
}
_, _ = fmt.Fprintf(w, "----------------------\t----------------------\n")
_, _ = fmt.Fprintf(w, "Total Size:\n")
_, _ = fmt.Fprintf(w, "Section .text:\t%d (0x%x)\n", totalSizeText, totalSizeText)
_, _ = fmt.Fprintf(w, "All sections:\t%d (0x%x)\n", totalSizeElf, totalSizeElf)
_, _ = fmt.Fprintf(w, "#Pages (.text):\t%d\n", totalSizeText/pageSize)
_, _ = fmt.Fprintf(w, "#Pages (all sections):\t%d\n", totalSizeElf/pageSize)
_ = w.Flush()
}

View file

@ -0,0 +1,171 @@
// 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 elf64core
const (
TextSection = ".text"
BssSection = ".bss"
DataSection = ".data"
RodataSection = ".rodata"
BootTextSection = ".text.boot"
BootDataSection = ".data.boot"
UkCtorTabSection = ".uk_ctortab"
UkInitTabSection = ".uk_inittab"
)
var rx86_64Strings = map[uint32]string{
0: "R_X86_64_NONE",
1: "R_X86_64_64",
2: "R_X86_64_PC32",
3: "R_X86_64_GOT32",
4: "R_X86_64_PLT32",
5: "R_X86_64_COPY",
6: "R_X86_64_GLOB_DAT",
7: "R_X86_64_JMP_SLOT",
8: "R_X86_64_RELATIVE",
9: "R_X86_64_GOTPCREL",
10: "R_X86_64_32",
11: "R_X86_64_32S",
12: "R_X86_64_16",
13: "R_X86_64_PC16",
14: "R_X86_64_8",
15: "R_X86_64_PC8",
16: "R_X86_64_DTPMOD64",
17: "R_X86_64_DTPOFF64",
18: "R_X86_64_TPOFF64",
19: "R_X86_64_TLSGD",
20: "R_X86_64_TLSLD",
21: "R_X86_64_DTPOFF32",
22: "R_X86_64_GOTTPOFF",
23: "R_X86_64_TPOFF32",
24: "R_X86_64_PC64",
25: "R_X86_64_GOTOFF64",
26: "R_X86_64_GOTPC32",
27: "R_X86_64_GOT64",
28: "R_X86_64_GOTPCREL64",
29: "R_X86_64_GOTPC64",
30: "R_X86_64_GOTPLT64",
31: "R_X86_64_PLTOFF64",
32: "R_X86_64_SIZE32",
33: "R_X86_64_SIZE64",
34: "R_X86_64_GOTPC32_TLSDESC",
35: "R_X86_64_TLSDESC_CALL",
36: "R_X86_64_TLSDESC",
37: "R_X86_64_IRELATIVE",
38: "R_X86_64_RELATIVE64",
39: "R_X86_64_PC32_BND",
40: "R_X86_64_PLT32_BND",
41: "R_X86_64_GOTPCRELX",
42: "R_X86_64_REX_GOTPCRELX",
}
var shtStrings = map[uint32]string{
0: "SHT_NULL",
1: "SHT_PROGBITS",
2: "SHT_SYMTAB",
3: "SHT_STRTAB",
4: "SHT_RELA",
5: "SHT_HASH",
6: "SHT_DYNAMIC",
7: "SHT_NOTE",
8: "SHT_NOBITS",
9: "SHT_REL",
10: "SHT_SHLIB",
11: "SHT_DYNSYM",
14: "SHT_INIT_ARRAY",
15: "SHT_FINI_ARRAY",
16: "SHT_PREINIT_ARRAY",
17: "SHT_GROUP",
18: "SHT_SYMTAB_SHNDX",
0x60000000: "SHT_LOOS",
0x6ffffff5: "SHT_GNU_ATTRIBUTES",
0x6ffffff6: "SHT_GNU_HASH",
0x6ffffff7: "SHT_GNU_LIBLIST",
0x6ffffffd: "SHT_GNU_VERDEF",
0x6ffffffe: "SHT_GNU_VERNEED",
0x6fffffff: "SHT_GNU_VERSYM",
0x70000000: "SHT_LOPROC",
0x7fffffff: "SHT_HIPROC",
0x80000000: "SHT_LOUSER",
0xffffffff: "SHT_HIUSER",
}
var sttStrings = map[byte]string{
0: "NOTYPE",
1: "OBJECT",
2: "FUNC",
3: "SECTION",
4: "FILE",
5: "COMMON",
6: "TLS",
10: "LOOS",
12: "HIOS",
13: "LOPROC",
15: "HIPROC",
}
var ptStrings = map[uint32]string{
0: "PT_NULL",
1: "PT_LOAD",
2: "PT_DYNAMIC",
3: "PT_INTERP",
4: "PT_NOTE",
5: "PT_SHLIB",
6: "PT_PHDR",
7: "PT_TLS",
0x60000000: "PT_LOOS",
0x6fffffff: "PT_HIOS",
0x70000000: "PT_LOPROC",
0x7fffffff: "PT_HIPROC",
0x6474e550: "GNU_EH_FRAME",
0x6474e551: "GNU_STACK",
0x6474e552: "GNU_RELRO",
}
var dtStrings = map[uint64]string{
0: "DT_NULL",
1: "DT_NEEDED",
2: "DT_PLTRELSZ",
3: "DT_PLTGOT",
4: "DT_HASH",
5: "DT_STRTAB",
6: "DT_SYMTAB",
7: "DT_RELA",
8: "DT_RELASZ",
9: "DT_RELAENT",
10: "DT_STRSZ",
11: "DT_SYMENT",
12: "DT_INIT",
13: "DT_FINI",
14: "DT_SONAME",
15: "DT_RPATH",
16: "DT_SYMBOLIC",
17: "DT_REL",
18: "DT_RELSZ",
19: "DT_RELENT",
20: "DT_PLTREL",
21: "DT_DEBUG",
22: "DT_TEXTREL",
23: "DT_JMPREL",
24: "DT_BIND_NOW",
25: "DT_INIT_ARRAY",
26: "DT_FINI_ARRAY",
27: "DT_INIT_ARRAYSZ",
28: "DT_FINI_ARRAYSZ",
29: "DT_RUNPATH",
30: "DT_FLAGS",
32: "DT_PREINIT_ARRAY",
33: "DT_PREINIT_ARRAYSZ",
0x6000000d: "DT_LOOS",
0x6ffff000: "DT_HIOS",
0x6ffffff0: "DT_VERSYM",
0x6ffffffe: "DT_VERNEED",
0x6fffffff: "DT_VERNEEDNUM",
0x70000000: "DT_LOPROC",
0x7fffffff: "DT_HIPROC",
}

View file

@ -0,0 +1,85 @@
// 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 elf64core
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"os"
"text/tabwriter"
u "tools/srcs/common"
)
type DynamicTable struct {
nbEntries int
name string
elf64dyn []Elf64Dynamic
}
type Elf64Dynamic struct {
Tag uint64
Value uint64
}
func (elfFile *ELF64File) addDynamicEntry(index int, dynEntries []Elf64Dynamic) error {
elfFile.DynamicTable = DynamicTable{}
elfFile.DynamicTable.nbEntries = len(dynEntries)
elfFile.DynamicTable.name = elfFile.SectionsTable.DataSect[index].Name
elfFile.DynamicTable.elf64dyn = make([]Elf64Dynamic, 0)
for _, s := range dynEntries {
elfFile.DynamicTable.elf64dyn = append(elfFile.DynamicTable.elf64dyn, s)
if s.Tag == uint64(elf.DT_NULL) {
return nil
}
}
return nil
}
func (elfFile *ELF64File) parseDynamic(index int) error {
content, err := elfFile.GetSectionContent(uint16(index))
if err != nil {
return fmt.Errorf("failed reading relocation table: %s", err)
}
dynEntries := make([]Elf64Dynamic, len(content)/binary.Size(Elf64Dynamic{}))
if err := binary.Read(bytes.NewReader(content),
elfFile.Endianness, dynEntries); err != nil {
return err
}
if err := elfFile.addDynamicEntry(index, dynEntries); err != nil {
return err
}
return nil
}
func (table *DynamicTable) DisplayDynamicEntries() {
if len(table.elf64dyn) == 0 {
u.PrintWarning("Dynamic table is empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
fmt.Printf("%s table contains %d entries:\n\n", table.name, table.nbEntries)
_, _ = fmt.Fprintln(w, "Nr\tTag\tType\tValue")
for i, s := range table.elf64dyn {
_, _ = fmt.Fprintf(w, "%d:\t%.8x\t%s\t%x\n", i, s.Tag,
dtStrings[s.Tag], s.Value)
}
_ = w.Flush()
}

View file

@ -0,0 +1,82 @@
// 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 elf64core
import (
"bufio"
"encoding/binary"
"os"
)
type ELF64File struct {
Header *ELF64Header
SectionsTable SectionsTable
SegmentsTable ProgramTable
DynamicTable DynamicTable
SymbolsTables []SymbolsTables
RelaTables []RelaTables
NotesTables []NotesTables
FunctionsTables []FunctionTables
Raw []byte
IndexSections map[string]int
Name string
Endianness binary.ByteOrder
TextSectionIndex []int // slice since we can have several
}
func (elfFile *ELF64File) ReadElfBinaryFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
stats, statsErr := file.Stat()
if statsErr != nil {
return statsErr
}
var size = stats.Size()
elfFile.Raw = make([]byte, size)
buf := bufio.NewReader(file)
_, err = buf.Read(elfFile.Raw)
return err
}
func (elfFile *ELF64File) ParseAll(path, name string) error {
elfFile.Name = name
if err := elfFile.ReadElfBinaryFile(path + name); err != nil {
return err
}
if err := elfFile.ParseElfHeader(); err != nil {
return err
}
if err := elfFile.ParseSectionHeaders(); err != nil {
return err
}
if err := elfFile.ParseSections(); err != nil {
return err
}
if err := elfFile.ParseProgramHeaders(); err != nil {
return err
}
if err := elfFile.parseFunctions(); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// 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 elf64core
import (
"debug/elf"
"errors"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
u "tools/srcs/common"
)
type FunctionTables struct {
Name string
NbEntries int
Functions []ELF64Function
}
type ELF64Function struct {
Name string
Addr uint64
Size uint64
}
func (elfFile *ELF64File) getIndexFctTable(ndx uint16) int {
if int(ndx) > len(elfFile.SectionsTable.DataSect) {
return -1
}
sectionName := elfFile.SectionsTable.DataSect[ndx].Name
for i, t := range elfFile.FunctionsTables {
if strings.Compare(t.Name, sectionName) == 0 {
return i
}
}
return -1
}
func (elfFile *ELF64File) detectSizeSymbol(symbolsTable []ELF64Function, index int) uint64 {
if index+1 == len(symbolsTable) {
textIndex := elfFile.IndexSections[TextSection]
textSection := elfFile.SectionsTable.DataSect[textIndex]
size := textSection.Elf64section.FileOffset + textSection.Elf64section.Size
return size - symbolsTable[index].Addr
}
return symbolsTable[index+1].Addr - symbolsTable[index].Addr
}
func (elfFile *ELF64File) inspectFunctions() error {
for _, table := range elfFile.SymbolsTables {
for _, s := range table.dataSymbols {
k := elfFile.getIndexFctTable(s.elf64sym.Shndx)
if k != -1 && s.elf64sym.Value > 0 {
if !isInSlice(elfFile.Name, s.name) {
function := ELF64Function{Name: s.name, Addr: s.elf64sym.Value,
Size: s.elf64sym.Size}
elfFile.FunctionsTables[k].Functions =
append(elfFile.FunctionsTables[k].Functions, function)
}
} else if s.TypeSymbol == byte(elf.STT_FUNC) {
// If it is a func where the start address starts at 0
function := ELF64Function{Name: s.name, Addr: s.elf64sym.Value,
Size: s.elf64sym.Size}
elfFile.FunctionsTables[k].Functions =
append(elfFile.FunctionsTables[k].Functions, function)
}
}
}
return nil
}
func (elfFile *ELF64File) parseFunctions() error {
if _, ok := elfFile.IndexSections[TextSection]; ok {
if err := elfFile.inspectFunctions(); err != nil {
return err
}
} else {
return errors.New("no text section detected")
}
for _, table := range elfFile.FunctionsTables {
// sort Functions
sort.Slice(table.Functions, func(i, j int) bool {
return table.Functions[i].Addr < table.Functions[j].Addr
})
for i, f := range table.Functions {
if f.Size == 0 {
f.Size = elfFile.detectSizeSymbol(table.Functions, i)
}
// Special case where symbol of same address can be in different order
// between the ELF and the object file
if i < len(table.Functions)-1 && table.Functions[i].Addr == table.Functions[i+1].Addr {
if strings.Compare(table.Functions[i].Name, table.Functions[i+1].Name) > 0 {
swap(i, table.Functions)
}
}
}
}
return nil
}
func swap(index int, x []ELF64Function) {
x[index], x[index+1] = x[index+1], x[index]
}
func (table *FunctionTables) displayFunctions(w *tabwriter.Writer, fullDisplay bool) {
_, _ = fmt.Fprintf(w, "\nTable section '%s' contains %d entries:\n",
table.Name, table.NbEntries)
_, _ = fmt.Fprintf(w, "Name:\tAddr:\tSize\tRaw:\n")
for _, f := range table.Functions {
_, _ = fmt.Fprintf(w, "%s\t%6.x\t%6.x\t%s\n", f.Name, f.Addr, f.Size)
}
}
func (elfFile *ELF64File) DisplayFunctionsTables(fullDisplay bool) {
if len(elfFile.FunctionsTables) == 0 {
u.PrintWarning("Functions table(s) is/are empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
for _, table := range elfFile.FunctionsTables {
table.displayFunctions(w, fullDisplay)
}
_ = w.Flush()
}

View file

@ -0,0 +1,100 @@
// 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 elf64core
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"os"
"text/tabwriter"
)
const (
identLength = 16
littleEndian = 1
)
type ELF64Header struct {
Ident [identLength]byte
Type uint16
Machine uint16
Version uint32
EntryPoint uint64
ProgramHeaderOffset uint64
SectionHeaderOffset uint64
Flags uint32
HeaderSize uint16
ProgramHeaderEntrySize uint16
ProgramHeaderEntries uint16
SectionHeaderEntrySize uint16
SectionHeaderEntries uint16
SectionNamesTable uint16
}
func (elfFile *ELF64File) ParseElfHeader() error {
elfFile.Header = new(ELF64Header)
// Check the size
if len(elfFile.Raw) < identLength {
return errors.New("invalid size")
}
// Check the magic number
if elfFile.Raw[0] != 0x7f && elfFile.Raw[1] != 0x45 &&
elfFile.Raw[2] != 0x4c && elfFile.Raw[3] != 0x46 {
return errors.New("invalid ELF file")
}
// Check the type
if elfFile.Raw[4] != 0x02 {
return errors.New("elf32 is not supported")
}
if elfFile.Raw[5] == littleEndian {
elfFile.Endianness = binary.LittleEndian
} else {
elfFile.Endianness = binary.BigEndian
}
data := bytes.NewReader(elfFile.Raw)
err := binary.Read(data, elfFile.Endianness, elfFile.Header)
if err != nil {
return err
}
return nil
}
func (elfHeader ELF64Header) DisplayHeader() {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
_, _ = fmt.Fprintf(w, "Magic Header:\t")
for _, hex := range elfHeader.Ident {
_, _ = fmt.Fprintf(w, "%.2x ", hex)
}
_, _ = fmt.Fprintf(w, "\nType:\t%x\n", elfHeader.Type)
_, _ = fmt.Fprintf(w, "Machine:\t%d\n", elfHeader.Machine)
_, _ = fmt.Fprintf(w, "Version:\t0x%x\n", elfHeader.Version)
_, _ = fmt.Fprintf(w, "EntryPoint:\t0x%x\n", elfHeader.EntryPoint)
_, _ = fmt.Fprintf(w, "ProgramHeaderOffset:\t%d\n", elfHeader.ProgramHeaderOffset)
_, _ = fmt.Fprintf(w, "SectionHeaderOffset:\t%d\n", elfHeader.SectionHeaderOffset)
_, _ = fmt.Fprintf(w, "Flags:\t0x%x\n", elfHeader.Flags)
_, _ = fmt.Fprintf(w, "HeaderSize:\t%d\n", elfHeader.HeaderSize)
_, _ = fmt.Fprintf(w, "ProgramHeaderEntrySize:\t%d\n", elfHeader.ProgramHeaderEntrySize)
_, _ = fmt.Fprintf(w, "ProgramHeaderEntries:\t%d\n", elfHeader.ProgramHeaderEntries)
_, _ = fmt.Fprintf(w, "SectionHeaderEntrySize:\t%d\n", elfHeader.SectionHeaderEntrySize)
_, _ = fmt.Fprintf(w, "SectionHeaderEntries:\t%d\n", elfHeader.SectionHeaderEntries)
_, _ = fmt.Fprintf(w, "SectionNamesTable:\t%d\n", elfHeader.SectionNamesTable)
_ = w.Flush()
}

View file

@ -0,0 +1,73 @@
// 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 elf64core
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"text/tabwriter"
u "tools/srcs/common"
)
type NotesTables struct {
name string
dataNote dataNote
}
type dataNote struct {
name string
description string
elf64note ELF64Note
}
type ELF64Note struct {
Namesz uint32
Descsz uint32
TypeNote uint32
}
func (elfFile *ELF64File) parseNote(index int) error {
var notesTable NotesTables
notesTable.name = elfFile.SectionsTable.DataSect[index].Name
content, err := elfFile.GetSectionContent(uint16(index))
if err != nil {
return fmt.Errorf("failed reading note section: %s", err)
}
data := bytes.NewReader(content)
err = binary.Read(data, elfFile.Endianness, &notesTable.dataNote.elf64note)
if err != nil {
return fmt.Errorf("failed reading elf64note: %s", err)
}
return nil
}
func (elfFile *ELF64File) DisplayNotes() {
if len(elfFile.NotesTables) == 0 {
u.PrintWarning("Notes are empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
for _, t := range elfFile.NotesTables {
_, _ = fmt.Fprintf(w, "\nDisplaying notes found in: %s\n", t.name)
_, _ = fmt.Fprintln(w, " Owner\tData size\tDescription")
_, _ = fmt.Fprintf(w, " %s\t0x%.6x\t%x\n", t.dataNote.name,
t.dataNote.elf64note.Descsz, t.dataNote.description)
}
_ = w.Flush()
}

View file

@ -0,0 +1,126 @@
// 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 elf64core
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"os"
"text/tabwriter"
u "tools/srcs/common"
)
type RelaTables struct {
nbEntries int
name string
dataRela []*dataRela
}
type dataRela struct {
name *string
elf64Rela Elf64Rela
}
type Elf64Rela struct {
Offset uint64
Type uint32
SymbolIndex uint32
Addend int64
}
func (elfFile *ELF64File) addRela(index int, relas []Elf64Rela) error {
var relocationTables RelaTables
relocationTables.nbEntries = len(relas)
relocationTables.name = elfFile.SectionsTable.DataSect[index].Name
relocationTables.dataRela = make([]*dataRela, relocationTables.nbEntries)
for i := range relas {
relocationTables.dataRela[i] = &dataRela{
elf64Rela: relas[i],
name: nil,
}
}
elfFile.RelaTables = append(elfFile.RelaTables, relocationTables)
return nil
}
func (elfFile *ELF64File) parseRelocations(index int) error {
content, err := elfFile.GetSectionContent(uint16(index))
if err != nil {
return fmt.Errorf("failed reading relocation table: %s", err)
}
rela := make([]Elf64Rela, len(content)/binary.Size(Elf64Rela{}))
if err := binary.Read(bytes.NewReader(content),
elfFile.Endianness, rela); err != nil {
return err
}
if err := elfFile.addRela(index, rela); err != nil {
return err
}
return nil
}
func (elfFile *ELF64File) resolveRelocSymbolsName() error {
for _, table := range elfFile.RelaTables {
for _, s := range table.dataRela {
t := 0
if s.elf64Rela.Type == uint32(elf.R_X86_64_JMP_SLOT) ||
s.elf64Rela.Type == uint32(elf.R_X86_64_GLOB_DAT) ||
s.elf64Rela.Type == uint32(elf.R_X86_64_COPY) &&
len(elfFile.SymbolsTables) > 1 {
t++
}
symName, err := elfFile.SymbolsTables[t].getSymbolName(s.elf64Rela.SymbolIndex)
if err != nil {
return err
}
s.name = &symName
}
}
return nil
}
func (table *RelaTables) displayRelocations(w *tabwriter.Writer) {
_, _ = fmt.Fprintf(w, "\nRelocation section '%s' contains %d entries:\n",
table.name, table.nbEntries)
_, _ = fmt.Fprintln(w, "Offset\tInfo\tType\tValue")
for _, r := range table.dataRela {
_, _ = fmt.Fprintf(w, "%.6x\t%.6d\t%s\t%s %x\n",
r.elf64Rela.Offset, r.elf64Rela.SymbolIndex,
rx86_64Strings[r.elf64Rela.Type], *r.name,
r.elf64Rela.Addend)
}
}
func (elfFile *ELF64File) DisplayRelocationTables() {
if len(elfFile.RelaTables) == 0 {
u.PrintWarning("Relocation table(s) are empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
for _, table := range elfFile.RelaTables {
table.displayRelocations(w)
}
_ = w.Flush()
}

View file

@ -0,0 +1,187 @@
// 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 elf64core
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"os"
"strings"
"text/tabwriter"
u "tools/srcs/common"
)
type SectionsTable struct {
NbEntries int
Name string
DataSect []*DataSections
}
type DataSections struct {
Name string
Elf64section ELF64SectionHeader
}
type ELF64SectionHeader struct {
Name uint32
Type uint32
Flags uint64
VirtualAddress uint64
FileOffset uint64
Size uint64
LinkedIndex uint32
Info uint32
Align uint64
EntrySize uint64
}
func (elfFile *ELF64File) addSection(sections []ELF64SectionHeader) error {
elfFile.SectionsTable = SectionsTable{}
elfFile.SectionsTable.NbEntries = len(sections)
elfFile.SectionsTable.DataSect = make([]*DataSections,
elfFile.SectionsTable.NbEntries)
for i := len(sections) - 1; i >= 0; i-- {
elfFile.SectionsTable.DataSect[i] = &DataSections{
Elf64section: sections[i],
}
nameString, err := elfFile.GetSectionName(elfFile.SectionsTable.DataSect[i].Elf64section.Name, elfFile.Header.SectionNamesTable)
if err != nil {
return err
}
elfFile.SectionsTable.DataSect[i].Name = nameString
}
return nil
}
func (elfFile *ELF64File) ParseSectionHeaders() error {
offset := elfFile.Header.SectionHeaderOffset
if offset >= uint64(len(elfFile.Raw)) {
return fmt.Errorf("invalid elf64section header offset: 0x%x", offset)
}
data := bytes.NewReader(elfFile.Raw[offset:])
sections := make([]ELF64SectionHeader, elfFile.Header.SectionHeaderEntries)
err := binary.Read(data, elfFile.Endianness, sections)
if err != nil {
return fmt.Errorf("failed reading elf64section header table: %s", err)
}
if err := elfFile.addSection(sections); err != nil {
return err
}
return nil
}
func (elfFile *ELF64File) GetSectionContent(sectionIndex uint16) ([]byte, error) {
sectionTable := elfFile.SectionsTable.DataSect
if int(sectionIndex) > len(sectionTable) {
return nil, fmt.Errorf("invalid elf64section index: %d", sectionIndex)
}
start := sectionTable[sectionIndex].Elf64section.FileOffset
if start > uint64(len(elfFile.Raw)) {
return nil, fmt.Errorf("bad file offset for elf64section %d", sectionIndex)
}
end := start + sectionTable[sectionIndex].Elf64section.Size
if (end > uint64(len(elfFile.Raw))) || (end < start) {
return nil, fmt.Errorf("bad size for elf64section %d", sectionIndex)
}
return elfFile.Raw[start:end], nil
}
func (elfFile *ELF64File) GetSectionName(indexString uint32, indexStringTable uint16) (string, error) {
rawDataStringTable, err := elfFile.GetSectionContent(indexStringTable)
if err != nil {
return "", err
}
rawDataStart := rawDataStringTable[indexString:]
return string(rawDataStart[:bytes.IndexByte(rawDataStart, 0)]), nil
}
func (elfFile *ELF64File) ParseSections() error {
elfFile.IndexSections = make(map[string]int, 0)
elfFile.FunctionsTables = make([]FunctionTables, 0)
elfFile.TextSectionIndex = make([]int, 0)
for i := 0; i < len(elfFile.SectionsTable.DataSect)-1; i++ {
sectionName := elfFile.SectionsTable.DataSect[i].Name
elfFile.IndexSections[sectionName] = i
typeSection := elfFile.SectionsTable.DataSect[i].Elf64section.Type
switch typeSection {
case uint32(elf.SHT_SYMTAB):
if err := elfFile.parseSymbolsTable(i); err != nil {
return err
}
case uint32(elf.SHT_RELA):
if err := elfFile.parseRelocations(i); err != nil {
return err
}
case uint32(elf.SHT_DYNAMIC):
if err := elfFile.parseDynamic(i); err != nil {
return err
}
case uint32(elf.SHT_DYNSYM):
if err := elfFile.parseSymbolsTable(i); err != nil {
return err
}
case uint32(elf.SHT_NOTE):
if err := elfFile.parseNote(i); err != nil {
return err
}
default:
}
if strings.Contains(sectionName, TextSection) {
elfFile.FunctionsTables = append(elfFile.FunctionsTables, FunctionTables{
Name: sectionName,
NbEntries: 0,
Functions: nil,
})
if typeSection != uint32(elf.SHT_RELA) {
elfFile.TextSectionIndex = append(elfFile.TextSectionIndex, i)
}
}
}
return elfFile.resolveRelocSymbolsName()
}
func (table *SectionsTable) DisplaySections() {
if table.DataSect == nil || len(table.DataSect) == 0 {
u.PrintWarning("Section table(s) are empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
_, _ = fmt.Fprintln(w, "Nr\tName\tType\tAddress\tOffset\tSize")
for i, s := range table.DataSect {
_, _ = fmt.Fprintf(w, "[%d]\t%s\t%s\t%.6x\t%.6x\t%.6x\n", i,
s.Name, shtStrings[s.Elf64section.Type],
s.Elf64section.VirtualAddress, s.Elf64section.FileOffset,
s.Elf64section.Size)
}
_ = w.Flush()
}

View file

@ -0,0 +1,134 @@
// 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 elf64core
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"text/tabwriter"
u "tools/srcs/common"
)
type ProgramTable struct {
nbEntries int
name string
dataProgram []*dataProgram
}
type dataProgram struct {
sectionsPtr []*DataSections
elf64program ELF64ProgramHeader
}
type ELF64ProgramHeader struct {
Type uint32
Flags uint32
FileOffset uint64
VirtualAddress uint64
PhysicalAddress uint64
FileSize uint64
MemorySize uint64
Align uint64
}
func (elfFile *ELF64File) addProgram(programs []ELF64ProgramHeader) error {
elfFile.SegmentsTable = ProgramTable{}
elfFile.SegmentsTable.nbEntries = len(programs)
elfFile.SegmentsTable.dataProgram = make([]*dataProgram,
elfFile.SegmentsTable.nbEntries)
for i := range programs {
elfFile.SegmentsTable.dataProgram[i] = &dataProgram{
elf64program: programs[i],
}
}
return nil
}
func (elfFile *ELF64File) mapSectionSegments() {
for _, p := range elfFile.SegmentsTable.dataProgram {
for _, s := range elfFile.SectionsTable.DataSect {
if s.Elf64section.FileOffset >= p.elf64program.FileOffset &&
s.Elf64section.FileOffset+s.Elf64section.Size <= p.elf64program.FileOffset+p.elf64program.FileSize {
p.sectionsPtr = append(p.sectionsPtr, s)
}
}
}
}
func (elfFile *ELF64File) ParseProgramHeaders() error {
if elfFile.Header.ProgramHeaderEntries == 0 {
return nil
}
offset := elfFile.Header.ProgramHeaderOffset
if offset >= uint64(len(elfFile.Raw)) {
return fmt.Errorf("invalid elf64section header offset: 0x%x", offset)
}
data := bytes.NewReader(elfFile.Raw[offset:])
programs := make([]ELF64ProgramHeader, elfFile.Header.ProgramHeaderEntries)
err := binary.Read(data, elfFile.Endianness, programs)
if err != nil {
return fmt.Errorf("failed reading elf64section header table: %s", err)
}
if err := elfFile.addProgram(programs); err != nil {
return err
}
elfFile.mapSectionSegments()
return nil
}
func (table *ProgramTable) DisplayProgramHeader() {
if len(table.dataProgram) == 0 {
fmt.Println("Program header is empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
_, _ = fmt.Fprintln(w, "Nr\tType\tOffset\tVirtAddr\tPhysAddr\tFileSiz\tMemSiz\tFlg\tAlign")
for i, p := range table.dataProgram {
_, _ = fmt.Fprintf(w, "[%.2d]\t%s\t%.6x\t%.6x\t%.6x\t%.6x\t%.6x\t%.6x\t0x%x\n", i,
ptStrings[p.elf64program.Type],
p.elf64program.FileOffset, p.elf64program.VirtualAddress,
p.elf64program.PhysicalAddress, p.elf64program.FileSize,
p.elf64program.MemorySize, p.elf64program.Flags, p.elf64program.Align)
}
_ = w.Flush()
}
func (table *ProgramTable) DisplaySegmentSectionMapping() {
if len(table.dataProgram) == 0 {
u.PrintWarning("Mapping between segments and sections is empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
for i, p := range table.dataProgram {
_, _ = fmt.Fprintf(w, "[%.2d] ", i)
for _, s := range p.sectionsPtr {
_, _ = fmt.Fprintf(w, "%s ", s.Name)
}
_, _ = fmt.Fprintf(w, "\n")
}
_ = w.Flush()
}

View file

@ -0,0 +1,138 @@
// 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 elf64core
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"os"
"text/tabwriter"
u "tools/srcs/common"
)
type SymbolsTables struct {
nbEntries int
name string
dataSymbols []*dataSymbols
}
type dataSymbols struct {
elf64sym ELF64Symbols
name string
TypeSymbol byte
}
type ELF64Symbols struct {
Name uint32
Info byte
Other byte
Shndx uint16
Value uint64
Size uint64
}
func (elfFile *ELF64File) addSymbols(index int, symbols []ELF64Symbols) error {
var symbolsTables SymbolsTables
symbolsTables.nbEntries = len(symbols)
symbolsTables.name = elfFile.SectionsTable.DataSect[index].Name
symbolsTables.dataSymbols = make([]*dataSymbols, symbolsTables.nbEntries)
var nameString string
var err error
for j, s := range symbols {
if s.Info == byte(elf.STT_SECTION) {
// This is a section, save its name
nameString = elfFile.SectionsTable.DataSect[s.Shndx].Name
} else {
nameString, err = elfFile.GetSectionName(s.Name, uint16(index+1))
if err != nil {
return err
}
}
symbolsTables.dataSymbols[j] = &dataSymbols{
elf64sym: s,
name: nameString,
TypeSymbol: (s.Info) & 0xf,
}
}
elfFile.SymbolsTables = append(elfFile.SymbolsTables, symbolsTables)
return nil
}
func (elfFile *ELF64File) parseSymbolsTable(index int) error {
content, err := elfFile.GetSectionContent(uint16(index))
if err != nil {
return fmt.Errorf("failed reading string table: %s", err)
}
if content[len(content)-1] != 0 {
return fmt.Errorf("the string table isn't null-terminated")
}
symbols := make([]ELF64Symbols, len(content)/binary.Size(ELF64Symbols{}))
if err := binary.Read(bytes.NewReader(content),
elfFile.Endianness, symbols); err != nil {
return err
}
if err := elfFile.addSymbols(index, symbols); err != nil {
return err
}
return nil
}
func (table *SymbolsTables) displaySymbols(w *tabwriter.Writer) {
_, _ = fmt.Fprintf(w, "\nSymbol table %s contains %d entries:\n\n",
table.name, table.nbEntries)
_, _ = fmt.Fprintf(w, "\nNum:\tValue\tSize\tName\tType\n")
for i, s := range table.dataSymbols {
_, _ = fmt.Fprintf(w, "%d:\t%.6x\t%d\t%s\t%s (%d)\n", i,
s.elf64sym.Value, s.elf64sym.Size, s.name,
sttStrings[s.TypeSymbol], s.TypeSymbol)
}
}
func (elfFile *ELF64File) DisplaySymbolsTables() {
if len(elfFile.SymbolsTables) == 0 {
u.PrintWarning("Symbols table(s) are empty")
return
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
fmt.Println("-----------------------------------------------------------------------")
for _, table := range elfFile.SymbolsTables {
table.displaySymbols(w)
}
_ = w.Flush()
}
func (table *SymbolsTables) getSymbolName(index uint32) (string, error) {
if table.dataSymbols == nil {
return "", fmt.Errorf("symbol table is empty")
}
if uint32(table.nbEntries) <= index {
return "", fmt.Errorf("invalid index %d", index)
}
return table.dataSymbols[index].name, nil
}

View file

@ -0,0 +1,32 @@
// 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 elf64core
import (
"os"
"strings"
)
var mapList = map[string][]string{"libkvmplat.o": {"haltme"}}
func isInSlice(libName, symbol string) bool {
if strings.Contains(libName, string(os.PathSeparator)) {
libNameSplit := strings.Split(libName, string(os.PathSeparator))
if list, ok := mapList[libNameSplit[len(libNameSplit)-1]]; ok {
for _, b := range list {
if b == symbol {
return true
}
}
}
}
return false
}

View file

@ -0,0 +1,38 @@
// 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 binarytool
import (
"encoding/json"
"io/ioutil"
"os"
"strings"
)
func ReadJsonFile(path string) (*Unikernels, error) {
jsonFile, err := os.Open(path)
if err != nil {
return nil, err
}
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
unikernels := new(Unikernels)
if err := json.Unmarshal(byteValue, unikernels); err != nil {
return nil, err
}
return unikernels, nil
}
func stringInSlice(name string, plats []string) bool {
for _, plat := range plats {
if strings.Contains(name, plat) {
return true
}
}
return false
}

View file

@ -0,0 +1,170 @@
// 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 binarytool
import (
"errors"
"fmt"
"os"
"strings"
"tools/srcs/binarytool/elf64analyser"
u "tools/srcs/common"
)
const diffPath = "diff" + u.SEP
const pagesPath = "pages" + u.SEP
// RunBinaryAnalyser allows to run the binary analyser tool (which is out of the
// UNICORE toolchain).
func RunBinaryAnalyser(homeDir string) {
// Init and parse local arguments
args := new(u.Arguments)
p, err := args.InitArguments()
if err != nil {
u.PrintErr(err)
}
if err := parseLocalArguments(p, args); err != nil {
u.PrintErr(err)
}
// Check if a json file is used or if it is via command line
var unikernels *Unikernels
if len(*args.StringArg[listArg]) > 0 {
unikernels = new(Unikernels)
unikernels.Unikernel = make([]Unikernel, len(*args.StringArg[listArg]))
mapping := false
if *args.BoolArg[mappingArg] {
mapping = true
}
list := strings.Split(*args.StringArg[listArg], ",")
for i, arg := range list {
unikernels.Unikernel[i] = Unikernel{
BuildPath: arg,
DisplayMapping: mapping,
}
}
} else if len(*args.StringArg[filesArg]) > 0 {
var err error
unikernels, err = ReadJsonFile(*args.StringArg[filesArg])
if err != nil {
u.PrintErr(err)
}
} else {
u.PrintErr(errors.New("argument(s) must be provided"))
}
var comparison elf64analyser.ComparisonElf
comparison.GroupFileSegment = make([]*elf64analyser.ElfFileSegment, 0)
for i, uk := range unikernels.Unikernel {
uk.Analyser = new(elf64analyser.ElfAnalyser)
if len(uk.BuildPath) > 0 {
if uk.BuildPath[len(uk.BuildPath)-1] != os.PathSeparator {
uk.BuildPath += u.SEP
}
if err := uk.GetFiles(); err != nil {
u.PrintErr(err)
}
// Perform the inspection of micro-libs since we have the buildPath
uk.Analyser.InspectMappingList(uk.ElfFile, uk.ListObjs)
} else {
if err := uk.GetKernel(); err != nil {
u.PrintErr(err)
}
}
if len(uk.DisplayElfFile) > 0 {
uk.DisplayElfInfo()
}
if uk.DisplayMapping && len(uk.BuildPath) > 0 {
fmt.Printf("==========[(%d): %s]==========\n", i, uk.BuildPath)
uk.Analyser.DisplayMapping()
fmt.Println("=====================================================")
}
if uk.DisplayStatSize {
uk.Analyser.DisplayStatSize(uk.ElfFile)
}
if len(uk.DisplaySectionInfo) > 0 {
uk.Analyser.DisplaySectionInfo(uk.ElfFile, uk.DisplaySectionInfo)
}
if len(uk.FindSectionByAddress) > 0 {
uk.Analyser.FindSectionByAddress(uk.ElfFile, uk.FindSectionByAddress)
}
if uk.CompareGroup > 0 {
foundSection := false
section := uk.SectionSplit
for _, s := range uk.ElfFile.SectionsTable.DataSect {
if s.Name == section {
foundSection = true
break
}
}
if foundSection {
path := homeDir + u.SEP + pagesPath
if _, err := os.Stat(path); os.IsNotExist(err) {
err := os.Mkdir(path, os.ModePerm)
if err != nil {
u.PrintErr(err)
}
}
u.PrintInfo(fmt.Sprintf("Splitting %s section of %s into pages...", section, uk.ElfFile.Name))
uk.Analyser.SplitIntoPagesBySection(uk.ElfFile, section)
out := path + section[1:] + u.SEP
if _, err := os.Stat(out); os.IsNotExist(err) {
err := os.Mkdir(out, os.ModePerm)
if err != nil {
u.PrintErr(err)
}
}
if err := elf64analyser.SavePagesToFile(uk.Analyser.ElfPage, out+uk.ElfFile.Name+".txt", false); err != nil {
u.PrintErr(err)
}
u.PrintOk(fmt.Sprintf("Pages of section %s (%s) are saved into %s", section, uk.ElfFile.Name, out))
comparison.GroupFileSegment = append(comparison.GroupFileSegment,
&elf64analyser.ElfFileSegment{Filename: uk.ElfFile.Name,
NbPages: len(uk.Analyser.ElfPage), Pages: uk.Analyser.ElfPage})
} else {
u.PrintWarning("Section '" + section + "' is not found in the ELF file")
}
}
}
if len(comparison.GroupFileSegment) > 1 {
// Perform the comparison
path := homeDir + u.SEP + diffPath
if _, err := os.Stat(path); os.IsNotExist(err) {
err := os.Mkdir(path, os.ModePerm)
if err != nil {
u.PrintErr(err)
}
}
comparison.ComparePageTables()
if err := comparison.DiffComparison(path); err != nil {
u.PrintWarning(err)
}
comparison.DisplayComparison()
}
}

View file

@ -0,0 +1,152 @@
// 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 binarytool
import (
"io/ioutil"
"path/filepath"
"strings"
"tools/srcs/binarytool/elf64analyser"
"tools/srcs/binarytool/elf64core"
u "tools/srcs/common"
)
const (
makefile = "Makefile"
config = "config"
objExt = ".o"
ldExt = ".ld.o"
dbgExt = ".dbg"
)
type Unikernels struct {
Unikernel []Unikernel `json:"unikernels"`
}
type Unikernel struct {
BuildPath string `json:"buildPath"`
Kernel string `json:"kernel"`
SectionSplit string `json:"splitSection"`
DisplayMapping bool `json:"displayMapping"`
DisplayStatSize bool `json:"displayStatSize"`
IgnoredPlats []string `json:"ignoredPlats"`
DisplayElfFile []string `json:"displayElfFile"`
DisplaySectionInfo []string `json:"displaySectionInfo"`
FindSectionByAddress []string `json:"findSectionByAddress"`
CompareGroup int `json:"compareGroup"`
ElfFile *elf64core.ELF64File
ListObjs []*elf64core.ELF64File
Analyser *elf64analyser.ElfAnalyser
}
func parseFile(path, name string) (*elf64core.ELF64File, error) {
var elfFile *elf64core.ELF64File
elfFile = new(elf64core.ELF64File)
if err := elfFile.ParseAll(path, name); err != nil {
return nil, err
}
return elfFile, nil
}
func (uk *Unikernel) GetKernel() error {
var err error
uk.ElfFile, err = parseFile("", uk.Kernel)
if err != nil {
return err
}
return nil
}
func (uk *Unikernel) GetFiles() error {
files, err := ioutil.ReadDir(uk.BuildPath)
if err != nil {
return err
}
uk.ListObjs = make([]*elf64core.ELF64File, 0)
foundExec := false
for _, f := range files {
if f.IsDir() || strings.Contains(f.Name(), makefile) ||
strings.Contains(f.Name(), config) ||
strings.Contains(f.Name(), ldExt) {
continue
}
if filepath.Ext(strings.TrimSpace(f.Name())) == objExt &&
!stringInSlice(f.Name(), uk.IgnoredPlats) {
objFile, err := parseFile(uk.BuildPath, f.Name())
if err != nil {
return err
}
uk.ListObjs = append(uk.ListObjs, objFile)
} else if filepath.Ext(strings.TrimSpace(f.Name())) == dbgExt &&
!stringInSlice(f.Name(), uk.IgnoredPlats) && !foundExec {
execName := f.Name()
if len(uk.Kernel) > 0 {
execName = uk.Kernel
}
uk.ElfFile, err = parseFile(uk.BuildPath, execName)
if err != nil {
return err
}
foundExec = true
}
}
if len(uk.Kernel) > 0 {
u.PrintInfo("Use specified ELF file: " + uk.ElfFile.Name)
} else {
u.PrintInfo("Use ELF file found in build folder: " + uk.ElfFile.Name)
}
return nil
}
func (uk *Unikernel) displayAllElfInfo() {
uk.ElfFile.Header.DisplayHeader()
uk.ElfFile.SectionsTable.DisplaySections()
uk.ElfFile.DisplayRelocationTables()
uk.ElfFile.DisplaySymbolsTables()
uk.ElfFile.DynamicTable.DisplayDynamicEntries()
uk.ElfFile.SegmentsTable.DisplayProgramHeader()
uk.ElfFile.SegmentsTable.DisplaySegmentSectionMapping()
uk.ElfFile.DisplayNotes()
uk.ElfFile.DisplayFunctionsTables(false)
}
func (uk *Unikernel) DisplayElfInfo() {
if len(uk.DisplayElfFile) == 1 && uk.DisplayElfFile[0] == "all" {
uk.displayAllElfInfo()
} else {
for _, d := range uk.DisplayElfFile {
if d == "header" {
uk.ElfFile.Header.DisplayHeader()
} else if d == "sections" {
uk.ElfFile.SectionsTable.DisplaySections()
} else if d == "relocations" {
uk.ElfFile.DisplayRelocationTables()
} else if d == "symbols" {
uk.ElfFile.DisplaySymbolsTables()
} else if d == "dynamics" {
uk.ElfFile.DynamicTable.DisplayDynamicEntries()
} else if d == "segments" {
uk.ElfFile.SegmentsTable.DisplayProgramHeader()
} else if d == "mapping" {
uk.ElfFile.SegmentsTable.DisplaySegmentSectionMapping()
} else if d == "notes" {
uk.ElfFile.DisplayNotes()
} else if d == "functions" {
uk.ElfFile.DisplayFunctionsTables(false)
} else {
u.PrintWarning("No display configuration found for argument: " + d)
}
}
}
}

View file

@ -27,6 +27,7 @@ const (
BUILD = "build"
VERIF = "verif"
PERF = "perf"
BINARY = "binary"
)
const (
@ -82,6 +83,9 @@ func (*Arguments) ParseMainArguments(p *argparse.Parser, args *Arguments) error
args.InitArgParse(p, args, BOOL, "", CRAWLER,
&argparse.Options{Required: false, Default: false,
Help: "Execute the crawler unikraft tool"})
args.InitArgParse(p, args, BOOL, "", BINARY,
&argparse.Options{Required: false, Default: false,
Help: "Execute the binary analyser tool"})
args.InitArgParse(p, args, BOOL, "", DEP,
&argparse.Options{Required: false, Default: false,
Help: "Execute only the dependency analysis tool"})
@ -98,7 +102,7 @@ func (*Arguments) ParseMainArguments(p *argparse.Parser, args *Arguments) error
// Parse only the two first arguments <program name, [tools]>
if len(os.Args) > 2 {
return ParserWrapper(p, os.Args[:2])
}else{
} else {
p.Parse(os.Args)
}

View file

@ -8,6 +8,7 @@ package main
import (
"os/user"
"tools/srcs/binarytool"
"tools/srcs/buildtool"
u "tools/srcs/common"
"tools/srcs/crawlertool"
@ -50,6 +51,12 @@ func main() {
return
}
if *args.BoolArg[u.BINARY] {
u.PrintHeader1("(*) RUN BINARY UNIKRAFT ANALYSER")
binarytool.RunBinaryAnalyser(usr.HomeDir)
return
}
if all || *args.BoolArg[u.DEP] {
// Initialize data