mirror of
https://github.com/mandiant/GoReSym
synced 2026-06-08 15:40:15 +00:00
Initial commit
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
.vscode/
|
||||
test/
|
||||
GoReSym
|
||||
Dockerfile.test
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* equal;
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* equal;
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* equal;
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* equal;
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* equal;
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
struct moduledata
|
||||
{
|
||||
GoSlice pclntable;
|
||||
GoSlice ftab;
|
||||
GoSlice filetab;
|
||||
void *findfunctab;
|
||||
void *minpc;
|
||||
void *maxpc;
|
||||
void *text;
|
||||
void *etext;
|
||||
void *noptrdata;
|
||||
void *enoptrdata;
|
||||
void *data;
|
||||
void *edata;
|
||||
void *bss;
|
||||
void *ebss;
|
||||
void *noptrbss;
|
||||
void *enoptrbss;
|
||||
void *end;
|
||||
void *gcdata;
|
||||
void *gcbss;
|
||||
|
||||
GoSlice typelinks;
|
||||
|
||||
GoString modulename;
|
||||
GoSlice modulehashes;
|
||||
|
||||
GoBitVector gcdatamask;
|
||||
GoBitVector gcbssmask;
|
||||
|
||||
moduledata *next;
|
||||
};
|
||||
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
uint8_t unused;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
void* str;
|
||||
void* UncommonType;
|
||||
void* ptrToThis;
|
||||
void* zero;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
struct moduledata
|
||||
{
|
||||
GoSlice pclntable;
|
||||
GoSlice ftab;
|
||||
GoSlice filetab;
|
||||
void *findfunctab;
|
||||
void *minpc;
|
||||
void *maxpc;
|
||||
void *text;
|
||||
void *etext;
|
||||
void *noptrdata;
|
||||
void *enoptrdata;
|
||||
void *data;
|
||||
void *edata;
|
||||
void *bss;
|
||||
void *ebss;
|
||||
void *noptrbss;
|
||||
void *enoptrbss;
|
||||
void *end;
|
||||
void *gcdata;
|
||||
void *gcbss;
|
||||
|
||||
GoSlice typelinks;
|
||||
|
||||
GoString modulename;
|
||||
GoSlice modulehashes;
|
||||
|
||||
GoBitVector gcdatamask;
|
||||
GoBitVector gcbssmask;
|
||||
|
||||
moduledata *next;
|
||||
};
|
||||
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
uint8_t unused;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
void* str;
|
||||
void* UncommonType;
|
||||
void* ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
struct moduledata
|
||||
{
|
||||
GoSlice pclntable;
|
||||
GoSlice ftab;
|
||||
GoSlice filetab;
|
||||
void *findfunctab;
|
||||
void *minpc;
|
||||
void *maxpc;
|
||||
void *text;
|
||||
void *etext;
|
||||
void *noptrdata;
|
||||
void *enoptrdata;
|
||||
void *data;
|
||||
void *edata;
|
||||
void *bss;
|
||||
void *ebss;
|
||||
void *noptrbss;
|
||||
void *enoptrbss;
|
||||
void *end;
|
||||
void *gcdata;
|
||||
void *gcbss;
|
||||
void *types;
|
||||
void *etypes;
|
||||
|
||||
GoSlice typelinks;
|
||||
GoSlice itablinks;
|
||||
|
||||
GoString modulename;
|
||||
GoSlice modulehashes;
|
||||
|
||||
GoBitVector gcdatamask;
|
||||
GoBitVector gcbssmask;
|
||||
void *typemap;
|
||||
|
||||
moduledata *next;
|
||||
};
|
||||
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// https://github.com/golang/go/blob/master/src/reflect/type.go
|
||||
typedef uint8_t tflag;
|
||||
typedef int32_t nameOff;
|
||||
typedef int32_t typeOff;
|
||||
|
||||
struct rtype {
|
||||
size_t size;
|
||||
size_t ptrdata;
|
||||
uint32_t hash;
|
||||
tflag tflag;
|
||||
uint8_t align;
|
||||
uint8_t fieldAlign;
|
||||
uint8_t kind;
|
||||
void* alg; // ptr to struct holding hash and equal functions
|
||||
void* gcdata;
|
||||
nameOff str;
|
||||
typeOff ptrToThis;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
GOLang has two concepts of 'version'. The first is for internal runtime structures,
|
||||
these are usually coarse grained and named after the first version using that representation.
|
||||
For example the pclntab internal structure has 3 versions, 1.2, 1.16, and 1.18. These version numbers
|
||||
cover ranges of go version, >= 1.2, >= 1.16, and >= 1.18, non-overlapping.
|
||||
|
||||
The second concept of 'version' is the goruntime version. This obviously changes much more frequently
|
||||
than the above concept of version, and lots of internal language level features like reflection and
|
||||
type layouts may change with each runtime version.
|
||||
|
||||
I've organized the structures so that most stuff is in pclntab_Go<version>, and these apply to entire ranges of go versions, but when the specific runtime version matters, the structures are labelled in specific folders labelled after the runtime Go<version>. You must consult both folders most of the time for a full listing of internal structures. If a structure appears in both folders, the specific go version takes precedant
|
||||
|
||||
If you look at the go source code on github, the 'master' branch is the current next unreleased go version, and there's a branch for each previously released version named like boringcrypto.go<version>.
|
||||
@@ -0,0 +1,67 @@
|
||||
struct GoSlice
|
||||
{
|
||||
void *data;
|
||||
size_t len;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct GoString
|
||||
{
|
||||
char *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct GoBitVector
|
||||
{
|
||||
uint32_t bitnum;
|
||||
char *bytedata;
|
||||
};
|
||||
|
||||
struct moduledata
|
||||
{
|
||||
void* pcHeader;
|
||||
GoSlice funcnametab;
|
||||
GoSlice cutab;
|
||||
GoSlice filetab;
|
||||
GoSlice pctab;
|
||||
GoSlice pclntable;
|
||||
GoSlice ftab;
|
||||
void *findfunctab;
|
||||
void *minpc;
|
||||
void *maxpc;
|
||||
void *text;
|
||||
void *etext;
|
||||
void *noptrdata;
|
||||
void *enoptrdata;
|
||||
void *data;
|
||||
void *edata;
|
||||
void *bss;
|
||||
void *ebss;
|
||||
void *noptrbss;
|
||||
void *enoptrbss;
|
||||
void *end;
|
||||
void *gcdata;
|
||||
void *gcbss;
|
||||
void *types;
|
||||
void *etypes;
|
||||
GoSlice textsectmap;
|
||||
GoSlice typelinks;
|
||||
GoSlice itablinks;
|
||||
GoSlice ptab;
|
||||
GoString pluginpath;
|
||||
GoSlice pkghashes;
|
||||
GoString modulename;
|
||||
GoSlice modulehashes;
|
||||
char hasmain;
|
||||
GoBitVector gcdatamask;
|
||||
GoBitVector gcbssmask;
|
||||
void *typemap;
|
||||
char badload;
|
||||
moduledata *next;
|
||||
};
|
||||
|
||||
// function table
|
||||
struct functab {
|
||||
void* entryoffset; // relative to runtime.text (i.e VA)
|
||||
void* funcoffset; // relative to ftab table start
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
struct GoSlice
|
||||
{
|
||||
void *data;
|
||||
size_t len;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct GoString
|
||||
{
|
||||
char *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct GoBitVector
|
||||
{
|
||||
uint32_t bitnum;
|
||||
char *bytedata;
|
||||
};
|
||||
|
||||
struct textsect
|
||||
{
|
||||
size_t vaddr; // prelinked section vaddr (can be zero)
|
||||
size_t end; // vaddr + section length
|
||||
size_t baseaddr; // relocated section address (VA)
|
||||
};
|
||||
|
||||
struct moduledata
|
||||
{
|
||||
void* pcHeader;
|
||||
GoSlice funcnametab;
|
||||
GoSlice cutab;
|
||||
GoSlice filetab;
|
||||
GoSlice pctab;
|
||||
GoSlice pclntable;
|
||||
GoSlice ftab;
|
||||
void *findfunctab;
|
||||
void *minpc;
|
||||
void *maxpc;
|
||||
void *text;
|
||||
void *etext;
|
||||
void *noptrdata;
|
||||
void *enoptrdata;
|
||||
void *data;
|
||||
void *edata;
|
||||
void *bss;
|
||||
void *ebss;
|
||||
void *noptrbss;
|
||||
void *enoptrbss;
|
||||
void *end;
|
||||
void *gcdata;
|
||||
void *gcbss;
|
||||
void *types;
|
||||
void *etypes;
|
||||
void *rodata;
|
||||
void *gofunc;
|
||||
GoSlice textsectmap; // textsect []
|
||||
GoSlice typelinks;
|
||||
GoSlice itablinks;
|
||||
GoSlice ptab;
|
||||
GoString pluginpath;
|
||||
GoSlice pkghashes;
|
||||
GoString modulename;
|
||||
GoSlice modulehashes;
|
||||
char hasmain;
|
||||
GoBitVector gcdatamask;
|
||||
GoBitVector gcbssmask;
|
||||
void *typemap;
|
||||
char badload;
|
||||
moduledata *next;
|
||||
};
|
||||
|
||||
// function table
|
||||
struct functab {
|
||||
uint32_t entryoffset; // relative to runtime.text (i.e VA)
|
||||
uint32_t funcoffset; // relative to ftab table start
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
struct GoSlice
|
||||
{
|
||||
void *data;
|
||||
size_t len;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct GoString
|
||||
{
|
||||
char *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct GoBitVector
|
||||
{
|
||||
uint32_t bitnum;
|
||||
char *bytedata;
|
||||
};
|
||||
|
||||
struct moduledata
|
||||
{
|
||||
GoSlice pclntable;
|
||||
GoSlice ftab;
|
||||
GoSlice filetab;
|
||||
void *findfunctab;
|
||||
void *minpc;
|
||||
void *maxpc;
|
||||
void *text;
|
||||
void *etext;
|
||||
void *noptrdata;
|
||||
void *enoptrdata;
|
||||
void *data;
|
||||
void *edata;
|
||||
void *bss;
|
||||
void *ebss;
|
||||
void *noptrbss;
|
||||
void *enoptrbss;
|
||||
void *end;
|
||||
void *gcdata;
|
||||
void *gcbss;
|
||||
void *types;
|
||||
void *etypes;
|
||||
GoSlice textsectmap;
|
||||
GoSlice typelinks;
|
||||
GoSlice itablinks;
|
||||
GoSlice ptab;
|
||||
GoString pluginpath;
|
||||
GoSlice pkghashes;
|
||||
GoString modulename;
|
||||
GoSlice modulehashes;
|
||||
char hasmain;
|
||||
GoBitVector gcdatamask;
|
||||
GoBitVector gcbssmask;
|
||||
void *typemap;
|
||||
char badload;
|
||||
moduledata *next;
|
||||
};
|
||||
|
||||
// function table
|
||||
struct functab {
|
||||
void* entryoffset; // relative to runtime.text (i.e VA)
|
||||
void* funcoffset; // relative to ftab table start
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
|
||||
import idaapi
|
||||
import ida_bytes
|
||||
import ida_kernwin
|
||||
import ida_name
|
||||
import json
|
||||
|
||||
def iterable(obj):
|
||||
if obj is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
iter(obj)
|
||||
except Exception:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
hints = ida_kernwin.ask_file(0, "*.*", "GoReSym output file")
|
||||
with open(hints, "r", encoding="utf-8") as rp:
|
||||
buf = rp.read()
|
||||
|
||||
hints = json.loads(buf)
|
||||
if iterable(hints['UserFunctions']):
|
||||
for func in hints['UserFunctions']:
|
||||
print("Renaming %s to %s" % (hex(func['Start']), func['FullName']))
|
||||
idaapi.add_func(func['Start'], func['End'])
|
||||
idaapi.set_name(func['Start'], func['FullName'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
if iterable(hints['StdFunctions']):
|
||||
for func in hints['StdFunctions']:
|
||||
print("Renaming %s to %s" % (hex(func['Start']), func['FullName']))
|
||||
idaapi.add_func(func['Start'], func['End'])
|
||||
idaapi.set_name(func['Start'], func['FullName'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
if iterable(hints['Types']):
|
||||
for typ in hints['Types']:
|
||||
print("Renaming %s to %s" % (hex(typ['VA']), typ['Str']))
|
||||
idaapi.set_name(typ['VA'], typ['Str'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
# IDA often thinks these are string pointers, lets undefine that, then set the type correctly
|
||||
ida_bytes.del_items(typ['VA'], 0, 4)
|
||||
py_type = idaapi.idc_parse_decl(idaapi.cvar.idati, "void* ptr;", 1)
|
||||
idaapi.apply_type(idaapi.cvar.idati, py_type[1], py_type[2], typ['VA'], idaapi.TINFO_DEFINITE)
|
||||
|
||||
if iterable(hints['Interfaces']):
|
||||
for typ in hints['Interfaces']:
|
||||
print("Renaming %s to %s" % (hex(typ['VA']), typ['Str']))
|
||||
idaapi.set_name(typ['VA'], typ['Str'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
# IDA often thinks these are string pointers, lets undefine that, then set the type correctly
|
||||
ida_bytes.del_items(typ['VA'], 0, 4)
|
||||
py_type = idaapi.idc_parse_decl(idaapi.cvar.idati, "void* ptr;", 1)
|
||||
idaapi.apply_type(idaapi.cvar.idati, py_type[1], py_type[2], typ['VA'], idaapi.TINFO_DEFINITE)
|
||||
|
||||
if hints['TabMeta'] is not None:
|
||||
tabmeta = hints['TabMeta']
|
||||
va = tabmeta['VA']
|
||||
if va is not None and va != 0:
|
||||
idaapi.set_name(va, 'runtime_pclntab', idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
if hints['ModuleMeta'] is not None:
|
||||
modmeta = hints['ModuleMeta']
|
||||
va = modmeta['VA']
|
||||
if va is not None and va != 0:
|
||||
idaapi.set_name(va, 'runtime_firstmoduledata', idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
>>>>>>> 4feec1a (Add licensing in headers)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 MANDIANT
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,122 @@
|
||||
# GoReSym
|
||||
GoReSym is a Go symbol parser that extracts program metadata (such as CPU architecture, OS, endianness, compiler version, etc), function metadata (start & end addresses, names, sources), filename and line number metadata, and embedded structures and types. This cross platform program is based directly on the [open source Go compiler](https://github.com/golang/go/tree/master/src/debug/gosym) and runtime code.
|
||||
|
||||
The upstream Go runtime code is extended to handle:
|
||||
* stripped binaries
|
||||
* malformed unpacked binaries, such as from UPX
|
||||
* binaries that split single data ranges across multiple sections
|
||||
* the location of the `moduledata` structure
|
||||
|
||||
|
||||
# Usage
|
||||
You can download pre-built GoReSym binaries from the [Releases tab](https://github.com/mandiant/GoReSym/releases/) or build from source with a recent Go compiler:
|
||||
|
||||
```
|
||||
go build
|
||||
```
|
||||
|
||||
Invoke GoReSym like this:
|
||||
|
||||
```
|
||||
GoReSym.exe -t -d -p /path/to/input.exe
|
||||
```
|
||||
|
||||
In this example, we ask GoReSym to recover type names (`-t`), user package names, standard Go package names (`-d`), and input file paths (`-p`) embedded within the file `/path/to/input.exe`. The output looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "1.14.15",
|
||||
"BuildId": "Zb9QmokKTiOUgHKmaIwz/wd2rtE3W9PN-um1Ocdzh/qTdqcTY_jVajHy_-TtYv/Z_kJu9M77OjfijEiHMcF",
|
||||
"Arch": "amd64",
|
||||
"TabMeta": {
|
||||
"VA": 5174784,
|
||||
"Version": "1.2",
|
||||
"Endianess": "LittleEndian",
|
||||
"CpuQuantum": 1,
|
||||
"CpuQuantumStr": "x86/x64",
|
||||
"PointerSize": 8
|
||||
},
|
||||
"ModuleMeta": {
|
||||
"VA": 5678816,
|
||||
"Types": 4845568,
|
||||
"ETypes": 5171904,
|
||||
"Typelinks": {
|
||||
"Data": 5171904,
|
||||
"Len": 695,
|
||||
"Capacity": 695
|
||||
},
|
||||
"ITablinks": {
|
||||
"Data": 5174688,
|
||||
"Len": 11,
|
||||
"Capacity": 11
|
||||
},
|
||||
"LegacyTypes": {
|
||||
"Data": 0,
|
||||
"Len": 0,
|
||||
"Capacity": 0
|
||||
}
|
||||
},
|
||||
"Types": [ ... ],
|
||||
"Files": [ ... ],
|
||||
"UserFunctions": [ ... ],
|
||||
"StdFunctions": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
Here are all the available flags:
|
||||
|
||||
* `-d` ("default", optional) flag will print standard Go packages in addition to user packages.
|
||||
* `-p` ("paths", optional) flag will print any file paths embedded in the `pclntab`.
|
||||
* `-t` ("types", optional) flag will print Go type names.
|
||||
* `-m <virtual address>` ("manual", optional) flag will dump the `RTYPE` structure recursively at the given virtual address
|
||||
* `-v <version string>` ("version", optional) flag will override automated version detection and use the provided version. This is needed for some stripped binaries. Type parsing will fail if the version is not accurate.
|
||||
* `-human` (optional) flag will print a flat text listing instead of JSON. Especially useful when printing structure and interface types.
|
||||
|
||||
To import this information into IDA Pro you can run the script found in [https://github.com/mandiant/GoReSym/blob/master/IDAPython/goresym_rename.py](IDAPython/goresym_rename.py). It will read a json file produced by GoReSym and set symbols/labels in IDA.
|
||||
|
||||
# Version Support
|
||||
|
||||
As the Go compiler and runtime have changed, so have the embedded metadata structures. GoReSym supports the following combinations of Go releases & metadata:
|
||||
|
||||
* all combinations of Intel x86/x64 𝒙 MACH-O/ELF/PE 𝒙 big/little endian
|
||||
* `pclntab` parsing: >= Go 1.2
|
||||
* `moduledata` location: >= Go 1.2
|
||||
* `moduledata` type parsing: >= Go 1.5
|
||||
|
||||
The `moduledata` table used to extract types doesn't exist prior to Go 1.5, so this library will never support extracting types from very old Go versions.
|
||||
|
||||
This library current handles legacy `pclntab` (pre Go 1.2), 1.2, 1.16, and 1.18.
|
||||
|
||||
# Contributions
|
||||
Much of the source code from GoReSym is copied from the upstream Go compiler source directory `/internal`. To make this work, we've had to massage the source a bit. If you want to contribute to GoReSym, read on so we can explain this import process.
|
||||
|
||||
Due to the way Go packages work, we needed to remove the `/internal` path from the source file tree. This resulted in a lot of copying of internal Go files, where the directory tree is mostly intact but with small changes to many files' imports: references to `/internal` paths were replaced with `github.com/mandiant/GoReSym/`.
|
||||
|
||||
We also modified many internal structures to export fields and method. These are not exported by upstream Go because users should not rely upon them; however, the purpose of this tool to extract internal information, so we're taking on the task of maintaining these structures. Its not a great situation, but it's not easily avoidable.
|
||||
|
||||
Beyond the trivial changes, new logic exists in `/objfile`. For example, the file `objfile/internals` defines the reversed internal Go structures that GoReSym parses. If you update this repository, you must take care to keep these modifications intact. Its probably better to manually merge in commits from upstream rather than copying upstream files wholesale.
|
||||
|
||||
I am open to suggestions on how to better structure this project to avoid these issues while still compiling with the typical `go build`. There is a previous discussion involving Go maintainers [here](https://github.com/golang/go/issues/46792).
|
||||
|
||||
# References
|
||||
* `pclntab` specification: [golang.org/s/go12symtab](https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub)
|
||||
* `pclntab` magics: [pclntab.go#L169](https://github.com/golang/go/blob/89f687d6dbc11613f715d1644b4983905293dd33/src/debug/gosym/pclntab.go#L169)
|
||||
* `objfile` bug(s):
|
||||
* [golang/go#42954](https://github.com/golang/go/issues/42954)
|
||||
* [golang/go#47981](https://github.com/golang/go/issues/47981)
|
||||
* [golang/go#47852](https://github.com/golang/go/issues/47852)
|
||||
* `buildID` legacy issue: [](https://github.com/golang/go/issues/50809)
|
||||
|
||||
# Changes
|
||||
* `pcln()` functions in `objfile/<fileformat>` have been extended to support byte scanning the `pclntab` magic
|
||||
* file format parsers in `/debug/<fileformat>` have added routines such as `DataAfterSection` to support the signature scan
|
||||
* `debug/gosym/symtab.go`'s `walksymtab` has an added check to bail early when the optional `symtab` section is empty
|
||||
* many members and internal structs have been exported. Go uses capitalization to declare public vs private. The changes here are too many to enumerate
|
||||
* `objfile/objfile.go`'s `PCLineTable()` has had `goobj` liner support removed. This object time is not common to see, and the liner table cannot be signatured for, so `goobj` file support is removed.
|
||||
* extra sanity checks around `loadPeTable` (and other format variants) to avoid panic when symbols are present but malicious modified to be invalid (ref: [golang/go#47981](https://github.com/golang/go/issues/47981))
|
||||
* the signatures of some internal functions have been modified to provide lower level access to information such as section addresses and offsets.
|
||||
* `read_memory` routines for supported file formats implemented to read file data by virtual address
|
||||
* `moduledata` scan routines introduced to help locate moduledata in support of scanning for types and interfaces (via `typelinks`)
|
||||
|
||||
# License
|
||||
MIT
|
||||
@@ -0,0 +1,469 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Package archive implements reading of archive files generated by the Go
|
||||
// toolchain.
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mandiant/GoReSym/bio"
|
||||
"github.com/mandiant/GoReSym/goobj"
|
||||
)
|
||||
|
||||
/*
|
||||
The archive format is:
|
||||
|
||||
First, on a line by itself
|
||||
!<arch>
|
||||
|
||||
Then zero or more file records. Each file record has a fixed-size one-line header
|
||||
followed by data bytes followed by an optional padding byte. The header is:
|
||||
|
||||
%-16s%-12d%-6d%-6d%-8o%-10d`
|
||||
name mtime uid gid mode size
|
||||
|
||||
(note the trailing backquote). The %-16s here means at most 16 *bytes* of
|
||||
the name, and if shorter, space padded on the right.
|
||||
*/
|
||||
|
||||
// A Data is a reference to data stored in an object file.
|
||||
// It records the offset and size of the data, so that a client can
|
||||
// read the data only if necessary.
|
||||
type Data struct {
|
||||
Offset int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
type Archive struct {
|
||||
f *os.File
|
||||
Entries []Entry
|
||||
}
|
||||
|
||||
func (a *Archive) File() *os.File { return a.f }
|
||||
|
||||
type Entry struct {
|
||||
Name string
|
||||
Type EntryType
|
||||
Mtime int64
|
||||
Uid int
|
||||
Gid int
|
||||
Mode os.FileMode
|
||||
Data
|
||||
Obj *GoObj // nil if this entry is not a Go object file
|
||||
}
|
||||
|
||||
type EntryType int
|
||||
|
||||
const (
|
||||
EntryPkgDef EntryType = iota
|
||||
EntryGoObj
|
||||
EntryNativeObj
|
||||
)
|
||||
|
||||
func (e *Entry) String() string {
|
||||
return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
|
||||
(e.Mode & 0777).String(),
|
||||
e.Uid,
|
||||
e.Gid,
|
||||
e.Size,
|
||||
time.Unix(e.Mtime, 0).Format(timeFormat),
|
||||
e.Name)
|
||||
}
|
||||
|
||||
type GoObj struct {
|
||||
TextHeader []byte
|
||||
Arch string
|
||||
Data
|
||||
}
|
||||
|
||||
const (
|
||||
entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
|
||||
// In entryHeader the first entry, the name, is always printed as 16 bytes right-padded.
|
||||
entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
|
||||
timeFormat = "Jan _2 15:04 2006"
|
||||
)
|
||||
|
||||
var (
|
||||
archiveHeader = []byte("!<arch>\n")
|
||||
archiveMagic = []byte("`\n")
|
||||
goobjHeader = []byte("go objec") // truncated to size of archiveHeader
|
||||
|
||||
errCorruptArchive = errors.New("corrupt archive")
|
||||
errTruncatedArchive = errors.New("truncated archive")
|
||||
errCorruptObject = errors.New("corrupt object file")
|
||||
errNotObject = errors.New("unrecognized object file format")
|
||||
)
|
||||
|
||||
// An objReader is an object file reader.
|
||||
type objReader struct {
|
||||
a *Archive
|
||||
b *bio.Reader
|
||||
err error
|
||||
offset int64
|
||||
limit int64
|
||||
tmp [256]byte
|
||||
}
|
||||
|
||||
func (r *objReader) init(f *os.File) {
|
||||
r.a = &Archive{f, nil}
|
||||
r.offset, _ = f.Seek(0, io.SeekCurrent)
|
||||
r.limit, _ = f.Seek(0, io.SeekEnd)
|
||||
f.Seek(r.offset, io.SeekStart)
|
||||
r.b = bio.NewReader(f)
|
||||
}
|
||||
|
||||
// error records that an error occurred.
|
||||
// It returns only the first error, so that an error
|
||||
// caused by an earlier error does not discard information
|
||||
// about the earlier error.
|
||||
func (r *objReader) error(err error) error {
|
||||
if r.err == nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
r.err = err
|
||||
}
|
||||
// panic("corrupt") // useful for debugging
|
||||
return r.err
|
||||
}
|
||||
|
||||
// peek returns the next n bytes without advancing the reader.
|
||||
func (r *objReader) peek(n int) ([]byte, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.offset >= r.limit {
|
||||
r.error(io.ErrUnexpectedEOF)
|
||||
return nil, r.err
|
||||
}
|
||||
b, err := r.b.Peek(n)
|
||||
if err != nil {
|
||||
if err != bufio.ErrBufferFull {
|
||||
r.error(err)
|
||||
}
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
|
||||
// readByte reads and returns a byte from the input file.
|
||||
// On I/O error or EOF, it records the error but returns byte 0.
|
||||
// A sequence of 0 bytes will eventually terminate any
|
||||
// parsing state in the object file. In particular, it ends the
|
||||
// reading of a varint.
|
||||
func (r *objReader) readByte() byte {
|
||||
if r.err != nil {
|
||||
return 0
|
||||
}
|
||||
if r.offset >= r.limit {
|
||||
r.error(io.ErrUnexpectedEOF)
|
||||
return 0
|
||||
}
|
||||
b, err := r.b.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
r.error(err)
|
||||
b = 0
|
||||
} else {
|
||||
r.offset++
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// read reads exactly len(b) bytes from the input file.
|
||||
// If an error occurs, read returns the error but also
|
||||
// records it, so it is safe for callers to ignore the result
|
||||
// as long as delaying the report is not a problem.
|
||||
func (r *objReader) readFull(b []byte) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
if r.offset+int64(len(b)) > r.limit {
|
||||
return r.error(io.ErrUnexpectedEOF)
|
||||
}
|
||||
n, err := io.ReadFull(r.b, b)
|
||||
r.offset += int64(n)
|
||||
if err != nil {
|
||||
return r.error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip skips n bytes in the input.
|
||||
func (r *objReader) skip(n int64) {
|
||||
if n < 0 {
|
||||
r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip"))
|
||||
}
|
||||
if n < int64(len(r.tmp)) {
|
||||
// Since the data is so small, a just reading from the buffered
|
||||
// reader is better than flushing the buffer and seeking.
|
||||
r.readFull(r.tmp[:n])
|
||||
} else if n <= int64(r.b.Buffered()) {
|
||||
// Even though the data is not small, it has already been read.
|
||||
// Advance the buffer instead of seeking.
|
||||
for n > int64(len(r.tmp)) {
|
||||
r.readFull(r.tmp[:])
|
||||
n -= int64(len(r.tmp))
|
||||
}
|
||||
r.readFull(r.tmp[:n])
|
||||
} else {
|
||||
// Seek, giving up buffered data.
|
||||
r.b.MustSeek(r.offset+n, io.SeekStart)
|
||||
r.offset += n
|
||||
}
|
||||
}
|
||||
|
||||
// New writes to f to make a new archive.
|
||||
func New(f *os.File) (*Archive, error) {
|
||||
_, err := f.Write(archiveHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Archive{f: f}, nil
|
||||
}
|
||||
|
||||
// Parse parses an object file or archive from f.
|
||||
func Parse(f *os.File, verbose bool) (*Archive, error) {
|
||||
var r objReader
|
||||
r.init(f)
|
||||
t, err := r.peek(8)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
default:
|
||||
return nil, errNotObject
|
||||
|
||||
case bytes.Equal(t, archiveHeader):
|
||||
if err := r.parseArchive(verbose); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case bytes.Equal(t, goobjHeader):
|
||||
off := r.offset
|
||||
o := &GoObj{}
|
||||
if err := r.parseObject(o, r.limit-off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.a.Entries = []Entry{{
|
||||
Name: f.Name(),
|
||||
Type: EntryGoObj,
|
||||
Data: Data{off, r.limit - off},
|
||||
Obj: o,
|
||||
}}
|
||||
}
|
||||
|
||||
return r.a, nil
|
||||
}
|
||||
|
||||
// trimSpace removes trailing spaces from b and returns the corresponding string.
|
||||
// This effectively parses the form used in archive headers.
|
||||
func trimSpace(b []byte) string {
|
||||
return string(bytes.TrimRight(b, " "))
|
||||
}
|
||||
|
||||
// parseArchive parses a Unix archive of Go object files.
|
||||
func (r *objReader) parseArchive(verbose bool) error {
|
||||
r.readFull(r.tmp[:8]) // consume header (already checked)
|
||||
for r.offset < r.limit {
|
||||
if err := r.readFull(r.tmp[:60]); err != nil {
|
||||
return err
|
||||
}
|
||||
data := r.tmp[:60]
|
||||
|
||||
// Each file is preceded by this text header (slice indices in first column):
|
||||
// 0:16 name
|
||||
// 16:28 date
|
||||
// 28:34 uid
|
||||
// 34:40 gid
|
||||
// 40:48 mode
|
||||
// 48:58 size
|
||||
// 58:60 magic - `\n
|
||||
// We only care about name, size, and magic, unless in verbose mode.
|
||||
// The fields are space-padded on the right.
|
||||
// The size is in decimal.
|
||||
// The file data - size bytes - follows the header.
|
||||
// Headers are 2-byte aligned, so if size is odd, an extra padding
|
||||
// byte sits between the file data and the next header.
|
||||
// The file data that follows is padded to an even number of bytes:
|
||||
// if size is odd, an extra padding byte is inserted betw the next header.
|
||||
if len(data) < 60 {
|
||||
return errTruncatedArchive
|
||||
}
|
||||
if !bytes.Equal(data[58:60], archiveMagic) {
|
||||
return errCorruptArchive
|
||||
}
|
||||
name := trimSpace(data[0:16])
|
||||
var err error
|
||||
get := func(start, end, base, bitsize int) int64 {
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
var v int64
|
||||
v, err = strconv.ParseInt(trimSpace(data[start:end]), base, bitsize)
|
||||
return v
|
||||
}
|
||||
size := get(48, 58, 10, 64)
|
||||
var (
|
||||
mtime int64
|
||||
uid, gid int
|
||||
mode os.FileMode
|
||||
)
|
||||
if verbose {
|
||||
mtime = get(16, 28, 10, 64)
|
||||
uid = int(get(28, 34, 10, 32))
|
||||
gid = int(get(34, 40, 10, 32))
|
||||
mode = os.FileMode(get(40, 48, 8, 32))
|
||||
}
|
||||
if err != nil {
|
||||
return errCorruptArchive
|
||||
}
|
||||
data = data[60:]
|
||||
fsize := size + size&1
|
||||
if fsize < 0 || fsize < size {
|
||||
return errCorruptArchive
|
||||
}
|
||||
switch name {
|
||||
case "__.PKGDEF":
|
||||
r.a.Entries = append(r.a.Entries, Entry{
|
||||
Name: name,
|
||||
Type: EntryPkgDef,
|
||||
Mtime: mtime,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Mode: mode,
|
||||
Data: Data{r.offset, size},
|
||||
})
|
||||
r.skip(size)
|
||||
default:
|
||||
var typ EntryType
|
||||
var o *GoObj
|
||||
offset := r.offset
|
||||
p, err := r.peek(8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(p, goobjHeader) {
|
||||
typ = EntryGoObj
|
||||
o = &GoObj{}
|
||||
r.parseObject(o, size)
|
||||
} else {
|
||||
typ = EntryNativeObj
|
||||
r.skip(size)
|
||||
}
|
||||
r.a.Entries = append(r.a.Entries, Entry{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
Mtime: mtime,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Mode: mode,
|
||||
Data: Data{offset, size},
|
||||
Obj: o,
|
||||
})
|
||||
}
|
||||
if size&1 != 0 {
|
||||
r.skip(1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseObject parses a single Go object file.
|
||||
// The object file consists of a textual header ending in "\n!\n"
|
||||
// and then the part we want to parse begins.
|
||||
// The format of that part is defined in a comment at the top
|
||||
// of src/liblink/objfile.c.
|
||||
func (r *objReader) parseObject(o *GoObj, size int64) error {
|
||||
h := make([]byte, 0, 256)
|
||||
var c1, c2, c3 byte
|
||||
for {
|
||||
c1, c2, c3 = c2, c3, r.readByte()
|
||||
h = append(h, c3)
|
||||
// The new export format can contain 0 bytes.
|
||||
// Don't consider them errors, only look for r.err != nil.
|
||||
if r.err != nil {
|
||||
return errCorruptObject
|
||||
}
|
||||
if c1 == '\n' && c2 == '!' && c3 == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
o.TextHeader = h
|
||||
hs := strings.Fields(string(h))
|
||||
if len(hs) >= 4 {
|
||||
o.Arch = hs[3]
|
||||
}
|
||||
o.Offset = r.offset
|
||||
o.Size = size - int64(len(h))
|
||||
|
||||
p, err := r.peek(8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(p, []byte(goobj.Magic)) {
|
||||
return r.error(errCorruptObject)
|
||||
}
|
||||
r.skip(o.Size)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddEntry adds an entry to the end of a, with the content from r.
|
||||
func (a *Archive) AddEntry(typ EntryType, name string, mtime int64, uid, gid int, mode os.FileMode, size int64, r io.Reader) {
|
||||
off, err := a.f.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
n, err := fmt.Fprintf(a.f, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
|
||||
if err != nil || n != entryLen {
|
||||
log.Fatal("writing entry header: ", err)
|
||||
}
|
||||
n1, _ := io.CopyN(a.f, r, size)
|
||||
if n1 != size {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if (off+size)&1 != 0 {
|
||||
a.f.Write([]byte{0}) // pad to even byte
|
||||
}
|
||||
a.Entries = append(a.Entries, Entry{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
Mtime: mtime,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Mode: mode,
|
||||
Data: Data{off + entryLen, size},
|
||||
})
|
||||
}
|
||||
|
||||
// exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
|
||||
// then pads the result with spaces to be exactly 16 bytes.
|
||||
// Fmt uses runes for its width calculation, but we need bytes in the entry header.
|
||||
func exactly16Bytes(s string) string {
|
||||
for len(s) > 16 {
|
||||
_, wid := utf8.DecodeLastRuneInString(s)
|
||||
s = s[:len(s)-wid]
|
||||
}
|
||||
const sixteenSpaces = " "
|
||||
s += sixteenSpaces[:16-len(s)]
|
||||
return s
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
// Copyright 2015 The Go 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 bio implements common I/O abstractions used within the Go toolchain.
|
||||
package bio
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Reader implements a seekable buffered io.Reader.
|
||||
type Reader struct {
|
||||
f *os.File
|
||||
*bufio.Reader
|
||||
}
|
||||
|
||||
// Writer implements a seekable buffered io.Writer.
|
||||
type Writer struct {
|
||||
f *os.File
|
||||
*bufio.Writer
|
||||
}
|
||||
|
||||
// Create creates the file named name and returns a Writer
|
||||
// for that file.
|
||||
func Create(name string) (*Writer, error) {
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Writer{f: f, Writer: bufio.NewWriter(f)}, nil
|
||||
}
|
||||
|
||||
// Open returns a Reader for the file named name.
|
||||
func Open(name string) (*Reader, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewReader(f), nil
|
||||
}
|
||||
|
||||
// NewReader returns a Reader from an open file.
|
||||
func NewReader(f *os.File) *Reader {
|
||||
return &Reader{f: f, Reader: bufio.NewReader(f)}
|
||||
}
|
||||
|
||||
func (r *Reader) MustSeek(offset int64, whence int) int64 {
|
||||
if whence == 1 {
|
||||
offset -= int64(r.Buffered())
|
||||
}
|
||||
off, err := r.f.Seek(offset, whence)
|
||||
if err != nil {
|
||||
log.Fatalf("seeking in output: %v", err)
|
||||
}
|
||||
r.Reset(r.f)
|
||||
return off
|
||||
}
|
||||
|
||||
func (w *Writer) MustSeek(offset int64, whence int) int64 {
|
||||
if err := w.Flush(); err != nil {
|
||||
log.Fatalf("writing output: %v", err)
|
||||
}
|
||||
off, err := w.f.Seek(offset, whence)
|
||||
if err != nil {
|
||||
log.Fatalf("seeking in output: %v", err)
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
func (r *Reader) Offset() int64 {
|
||||
off, err := r.f.Seek(0, 1)
|
||||
if err != nil {
|
||||
log.Fatalf("seeking in output [0, 1]: %v", err)
|
||||
}
|
||||
off -= int64(r.Buffered())
|
||||
return off
|
||||
}
|
||||
|
||||
func (w *Writer) Offset() int64 {
|
||||
if err := w.Flush(); err != nil {
|
||||
log.Fatalf("writing output: %v", err)
|
||||
}
|
||||
off, err := w.f.Seek(0, 1)
|
||||
if err != nil {
|
||||
log.Fatalf("seeking in output [0, 1]: %v", err)
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
func (r *Reader) Close() error {
|
||||
return r.f.Close()
|
||||
}
|
||||
|
||||
func (w *Writer) Close() error {
|
||||
err := w.Flush()
|
||||
err1 := w.f.Close()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Reader) File() *os.File {
|
||||
return r.f
|
||||
}
|
||||
|
||||
func (w *Writer) File() *os.File {
|
||||
return w.f
|
||||
}
|
||||
|
||||
// Slice reads the next length bytes of r into a slice.
|
||||
//
|
||||
// This slice may be backed by mmap'ed memory. Currently, this memory
|
||||
// will never be unmapped. The second result reports whether the
|
||||
// backing memory is read-only.
|
||||
func (r *Reader) Slice(length uint64) ([]byte, bool, error) {
|
||||
if length == 0 {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
data, ok := r.sliceOS(length)
|
||||
if ok {
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
data = make([]byte, length)
|
||||
_, err := io.ReadFull(r, data)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
// SliceRO returns a slice containing the next length bytes of r
|
||||
// backed by a read-only mmap'd data. If the mmap cannot be
|
||||
// established (limit exceeded, region too small, etc) a nil slice
|
||||
// will be returned. If mmap succeeds, it will never be unmapped.
|
||||
func (r *Reader) SliceRO(length uint64) []byte {
|
||||
data, ok := r.sliceOS(length)
|
||||
if ok {
|
||||
return data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package bio
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// mmapLimit is the maximum number of mmaped regions to create before
|
||||
// falling back to reading into a heap-allocated slice. This exists
|
||||
// because some operating systems place a limit on the number of
|
||||
// distinct mapped regions per process. As of this writing:
|
||||
//
|
||||
// Darwin unlimited
|
||||
// DragonFly 1000000 (vm.max_proc_mmap)
|
||||
// FreeBSD unlimited
|
||||
// Linux 65530 (vm.max_map_count) // TODO: query /proc/sys/vm/max_map_count?
|
||||
// NetBSD unlimited
|
||||
// OpenBSD unlimited
|
||||
var mmapLimit int32 = 1<<31 - 1
|
||||
|
||||
func init() {
|
||||
// Linux is the only practically concerning OS.
|
||||
if runtime.GOOS == "linux" {
|
||||
mmapLimit = 30000
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) sliceOS(length uint64) ([]byte, bool) {
|
||||
// For small slices, don't bother with the overhead of a
|
||||
// mapping, especially since we have no way to unmap it.
|
||||
const threshold = 16 << 10
|
||||
if length < threshold {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Have we reached the mmap limit?
|
||||
if atomic.AddInt32(&mmapLimit, -1) < 0 {
|
||||
atomic.AddInt32(&mmapLimit, 1)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Page-align the offset.
|
||||
off := r.Offset()
|
||||
align := syscall.Getpagesize()
|
||||
aoff := off &^ int64(align-1)
|
||||
|
||||
data, err := syscall.Mmap(int(r.f.Fd()), aoff, int(length+uint64(off-aoff)), syscall.PROT_READ, syscall.MAP_SHARED|syscall.MAP_FILE)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data = data[off-aoff:]
|
||||
r.MustSeek(int64(length), 1)
|
||||
return data, true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
|
||||
|
||||
package bio
|
||||
|
||||
func (r *Reader) sliceOS(length uint64) ([]byte, bool) {
|
||||
return nil, false
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2016 The Go 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 bio
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// MustClose closes Closer c and calls log.Fatal if it returns a non-nil error.
|
||||
func MustClose(c io.Closer) {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// MustWriter returns a Writer that wraps the provided Writer,
|
||||
// except that it calls log.Fatal instead of returning a non-nil error.
|
||||
func MustWriter(w io.Writer) io.Writer {
|
||||
return mustWriter{w}
|
||||
}
|
||||
|
||||
type mustWriter struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (w mustWriter) Write(b []byte) (int, error) {
|
||||
n, err := w.w.Write(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w mustWriter) WriteString(s string) (int, error) {
|
||||
n, err := io.WriteString(w.w, s)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package buildid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mandiant/GoReSym/debug/elf"
|
||||
"github.com/mandiant/GoReSym/io/fs"
|
||||
"github.com/mandiant/GoReSym/xcoff"
|
||||
)
|
||||
|
||||
var (
|
||||
errBuildIDMalformed = fmt.Errorf("malformed object file")
|
||||
|
||||
bangArch = []byte("!<arch>")
|
||||
pkgdef = []byte("__.PKGDEF")
|
||||
goobject = []byte("go object ")
|
||||
buildid = []byte("build id ")
|
||||
)
|
||||
|
||||
// ReadFile reads the build ID from an archive or executable file.
|
||||
func ReadFile(name string) (id string, err error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buf := make([]byte, 8)
|
||||
if _, err := f.ReadAt(buf, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if string(buf) != "!<arch>\n" {
|
||||
if string(buf) == "<bigaf>\n" {
|
||||
return readGccgoBigArchive(name, f)
|
||||
}
|
||||
return readBinary(name, f)
|
||||
}
|
||||
|
||||
// Read just enough of the target to fetch the build ID.
|
||||
// The archive is expected to look like:
|
||||
//
|
||||
// !<arch>
|
||||
// __.PKGDEF 0 0 0 644 7955 `
|
||||
// go object darwin amd64 devel X:none
|
||||
// build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
|
||||
//
|
||||
// The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
|
||||
// Reading the first 1024 bytes should be plenty.
|
||||
data := make([]byte, 1024)
|
||||
n, err := io.ReadFull(f, data)
|
||||
if err != nil && n == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tryGccgo := func() (string, error) {
|
||||
return readGccgoArchive(name, f)
|
||||
}
|
||||
|
||||
// Archive header.
|
||||
for i := 0; ; i++ { // returns during i==3
|
||||
j := bytes.IndexByte(data, '\n')
|
||||
if j < 0 {
|
||||
return tryGccgo()
|
||||
}
|
||||
line := data[:j]
|
||||
data = data[j+1:]
|
||||
switch i {
|
||||
case 0:
|
||||
if !bytes.Equal(line, bangArch) {
|
||||
return tryGccgo()
|
||||
}
|
||||
case 1:
|
||||
if !bytes.HasPrefix(line, pkgdef) {
|
||||
return tryGccgo()
|
||||
}
|
||||
case 2:
|
||||
if !bytes.HasPrefix(line, goobject) {
|
||||
return tryGccgo()
|
||||
}
|
||||
case 3:
|
||||
if !bytes.HasPrefix(line, buildid) {
|
||||
// Found the object header, just doesn't have a build id line.
|
||||
// Treat as successful, with empty build id.
|
||||
return "", nil
|
||||
}
|
||||
id, err := strconv.Unquote(string(line[len(buildid):]))
|
||||
if err != nil {
|
||||
return tryGccgo()
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readGccgoArchive tries to parse the archive as a standard Unix
|
||||
// archive file, and fetch the build ID from the _buildid.o entry.
|
||||
// The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile
|
||||
// in cmd/go/internal/work/exec.go.
|
||||
func readGccgoArchive(name string, f *os.File) (string, error) {
|
||||
bad := func() (string, error) {
|
||||
return "", &fs.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
|
||||
off := int64(8)
|
||||
for {
|
||||
if _, err := f.Seek(off, io.SeekStart); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO(iant): Make a debug/ar package, and use it
|
||||
// here and in cmd/link.
|
||||
var hdr [60]byte
|
||||
if _, err := io.ReadFull(f, hdr[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
// No more entries, no build ID.
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
off += 60
|
||||
|
||||
sizeStr := strings.TrimSpace(string(hdr[48:58]))
|
||||
size, err := strconv.ParseInt(sizeStr, 0, 64)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(string(hdr[:16]))
|
||||
if name == "_buildid.o/" {
|
||||
sr := io.NewSectionReader(f, off, size)
|
||||
e, err := elf.NewFile(sr)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
s := e.Section(".go.buildid")
|
||||
if s == nil {
|
||||
return bad()
|
||||
}
|
||||
data, err := s.Data()
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
off += size
|
||||
if off&1 != 0 {
|
||||
off++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readGccgoBigArchive tries to parse the archive as an AIX big
|
||||
// archive file, and fetch the build ID from the _buildid.o entry.
|
||||
// The _buildid.o entry is written by (*Builder).gccgoBuildIDXCOFFFile
|
||||
// in cmd/go/internal/work/exec.go.
|
||||
func readGccgoBigArchive(name string, f *os.File) (string, error) {
|
||||
bad := func() (string, error) {
|
||||
return "", &fs.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
|
||||
// Read fixed-length header.
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var flhdr [128]byte
|
||||
if _, err := io.ReadFull(f, flhdr[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Read first member offset.
|
||||
offStr := strings.TrimSpace(string(flhdr[68:88]))
|
||||
off, err := strconv.ParseInt(offStr, 10, 64)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
for {
|
||||
if off == 0 {
|
||||
// No more entries, no build ID.
|
||||
return "", nil
|
||||
}
|
||||
if _, err := f.Seek(off, io.SeekStart); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Read member header.
|
||||
var hdr [112]byte
|
||||
if _, err := io.ReadFull(f, hdr[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Read member name length.
|
||||
namLenStr := strings.TrimSpace(string(hdr[108:112]))
|
||||
namLen, err := strconv.ParseInt(namLenStr, 10, 32)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
if namLen == 10 {
|
||||
var nam [10]byte
|
||||
if _, err := io.ReadFull(f, nam[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if string(nam[:]) == "_buildid.o" {
|
||||
sizeStr := strings.TrimSpace(string(hdr[0:20]))
|
||||
size, err := strconv.ParseInt(sizeStr, 10, 64)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
off += int64(len(hdr)) + namLen + 2
|
||||
if off&1 != 0 {
|
||||
off++
|
||||
}
|
||||
sr := io.NewSectionReader(f, off, size)
|
||||
x, err := xcoff.NewFile(sr)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
data := x.CSect(".go.buildid")
|
||||
if data == nil {
|
||||
return bad()
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read next member offset.
|
||||
offStr = strings.TrimSpace(string(hdr[20:40]))
|
||||
off, err = strconv.ParseInt(offStr, 10, 64)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
goBuildPrefix = []byte("\xff Go build ID: \"")
|
||||
goBuildEnd = []byte("\"\n \xff")
|
||||
|
||||
elfPrefix = []byte("\x7fELF")
|
||||
|
||||
machoPrefixes = [][]byte{
|
||||
{0xfe, 0xed, 0xfa, 0xce},
|
||||
{0xfe, 0xed, 0xfa, 0xcf},
|
||||
{0xce, 0xfa, 0xed, 0xfe},
|
||||
{0xcf, 0xfa, 0xed, 0xfe},
|
||||
}
|
||||
)
|
||||
|
||||
var readSize = 32 * 1024 // changed for testing
|
||||
|
||||
// readBinary reads the build ID from a binary.
|
||||
//
|
||||
// ELF binaries store the build ID in a proper PT_NOTE section.
|
||||
//
|
||||
// Other binary formats are not so flexible. For those, the linker
|
||||
// stores the build ID as non-instruction bytes at the very beginning
|
||||
// of the text segment, which should appear near the beginning
|
||||
// of the file. This is clumsy but fairly portable. Custom locations
|
||||
// can be added for other binary types as needed, like we did for ELF.
|
||||
func readBinary(name string, f *os.File) (id string, err error) {
|
||||
// Read the first 32 kB of the binary file.
|
||||
// That should be enough to find the build ID.
|
||||
// In ELF files, the build ID is in the leading headers,
|
||||
// which are typically less than 4 kB, not to mention 32 kB.
|
||||
// In Mach-O files, there's no limit, so we have to parse the file.
|
||||
// On other systems, we're trying to read enough that
|
||||
// we get the beginning of the text segment in the read.
|
||||
// The offset where the text segment begins in a hello
|
||||
// world compiled for each different object format today:
|
||||
//
|
||||
// Plan 9: 0x20
|
||||
// Windows: 0x600
|
||||
//
|
||||
data := make([]byte, readSize)
|
||||
_, err = io.ReadFull(f, data)
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(data, elfPrefix) {
|
||||
return readELF(name, f, data)
|
||||
}
|
||||
for _, m := range machoPrefixes {
|
||||
if bytes.HasPrefix(data, m) {
|
||||
return readMacho(name, f, data)
|
||||
}
|
||||
}
|
||||
return readRaw(name, data)
|
||||
}
|
||||
|
||||
// readRaw finds the raw build ID stored in text segment data.
|
||||
func readRaw(name string, data []byte) (id string, err error) {
|
||||
i := bytes.Index(data, goBuildPrefix)
|
||||
if i < 0 {
|
||||
// Missing. Treat as successful but build ID empty.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
|
||||
if j < 0 {
|
||||
return "", &fs.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
|
||||
quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
|
||||
id, err = strconv.Unquote(string(quoted))
|
||||
if err != nil {
|
||||
return "", &fs.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// HashToString converts the hash h to a string to be recorded
|
||||
// in package archives and binaries as part of the build ID.
|
||||
// We use the first 120 bits of the hash (5 chunks of 24 bits each) and encode
|
||||
// it in base64, resulting in a 20-byte string. Because this is only used for
|
||||
// detecting the need to rebuild installed files (not for lookups
|
||||
// in the object file cache), 120 bits are sufficient to drive the
|
||||
// probability of a false "do not need to rebuild" decision to effectively zero.
|
||||
// We embed two different hashes in archives and four in binaries,
|
||||
// so cutting to 20 bytes is a significant savings when build IDs are displayed.
|
||||
// (20*4+3 = 83 bytes compared to 64*4+3 = 259 bytes for the
|
||||
// more straightforward option of printing the entire h in base64).
|
||||
func HashToString(h [32]byte) string {
|
||||
const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
const chunks = 5
|
||||
var dst [chunks * 4]byte
|
||||
for i := 0; i < chunks; i++ {
|
||||
v := uint32(h[3*i])<<16 | uint32(h[3*i+1])<<8 | uint32(h[3*i+2])
|
||||
dst[4*i+0] = b64[(v>>18)&0x3F]
|
||||
dst[4*i+1] = b64[(v>>12)&0x3F]
|
||||
dst[4*i+2] = b64[(v>>6)&0x3F]
|
||||
dst[4*i+3] = b64[v&0x3F]
|
||||
}
|
||||
return string(dst[:])
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package buildid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mandiant/GoReSym/io/fs"
|
||||
)
|
||||
|
||||
func readAligned4(r io.Reader, sz int32) ([]byte, error) {
|
||||
full := (sz + 3) &^ 3
|
||||
data := make([]byte, full)
|
||||
_, err := io.ReadFull(r, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = data[:sz]
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
|
||||
f, err := elf.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
for _, sect := range f.Sections {
|
||||
if sect.Type != elf.SHT_NOTE {
|
||||
continue
|
||||
}
|
||||
r := sect.Open()
|
||||
for {
|
||||
var namesize, descsize, noteType int32
|
||||
err = binary.Read(r, f.ByteOrder, &namesize)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("read namesize failed: %v", err)
|
||||
}
|
||||
err = binary.Read(r, f.ByteOrder, &descsize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read descsize failed: %v", err)
|
||||
}
|
||||
err = binary.Read(r, f.ByteOrder, ¬eType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read type failed: %v", err)
|
||||
}
|
||||
noteName, err := readAligned4(r, namesize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read name failed: %v", err)
|
||||
}
|
||||
desc, err := readAligned4(r, descsize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read desc failed: %v", err)
|
||||
}
|
||||
if name == string(noteName) && typ == noteType {
|
||||
return desc, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var elfGoNote = []byte("Go\x00\x00")
|
||||
var elfGNUNote = []byte("GNU\x00")
|
||||
|
||||
// The Go build ID is stored in a note described by an ELF PT_NOTE prog
|
||||
// header. The caller has already opened filename, to get f, and read
|
||||
// at least 4 kB out, in data.
|
||||
func readELF(name string, f *os.File, data []byte) (buildid string, err error) {
|
||||
// Assume the note content is in the data, already read.
|
||||
// Rewrite the ELF header to set shnum to 0, so that we can pass
|
||||
// the data to elf.NewFile and it will decode the Prog list but not
|
||||
// try to read the section headers and the string table from disk.
|
||||
// That's a waste of I/O when all we care about is the Prog list
|
||||
// and the one ELF note.
|
||||
switch elf.Class(data[elf.EI_CLASS]) {
|
||||
case elf.ELFCLASS32:
|
||||
data[48] = 0
|
||||
data[49] = 0
|
||||
case elf.ELFCLASS64:
|
||||
data[60] = 0
|
||||
data[61] = 0
|
||||
}
|
||||
|
||||
const elfGoBuildIDTag = 4
|
||||
const gnuBuildIDTag = 3
|
||||
|
||||
ef, err := elf.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", &fs.PathError{Path: name, Op: "parse", Err: err}
|
||||
}
|
||||
var gnu string
|
||||
for _, p := range ef.Progs {
|
||||
if p.Type != elf.PT_NOTE || p.Filesz < 16 {
|
||||
continue
|
||||
}
|
||||
|
||||
var note []byte
|
||||
if p.Off+p.Filesz < uint64(len(data)) {
|
||||
note = data[p.Off : p.Off+p.Filesz]
|
||||
} else {
|
||||
// For some linkers, such as the Solaris linker,
|
||||
// the buildid may not be found in data (which
|
||||
// likely contains the first 16kB of the file)
|
||||
// or even the first few megabytes of the file
|
||||
// due to differences in note segment placement;
|
||||
// in that case, extract the note data manually.
|
||||
_, err = f.Seek(int64(p.Off), io.SeekStart)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
note = make([]byte, p.Filesz)
|
||||
_, err = io.ReadFull(f, note)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
filesz := p.Filesz
|
||||
off := p.Off
|
||||
for filesz >= 16 {
|
||||
nameSize := ef.ByteOrder.Uint32(note)
|
||||
valSize := ef.ByteOrder.Uint32(note[4:])
|
||||
tag := ef.ByteOrder.Uint32(note[8:])
|
||||
nname := note[12:16]
|
||||
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
|
||||
return string(note[16 : 16+valSize]), nil
|
||||
}
|
||||
|
||||
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
|
||||
gnu = string(note[16 : 16+valSize])
|
||||
}
|
||||
|
||||
nameSize = (nameSize + 3) &^ 3
|
||||
valSize = (valSize + 3) &^ 3
|
||||
notesz := uint64(12 + nameSize + valSize)
|
||||
if filesz <= notesz {
|
||||
break
|
||||
}
|
||||
off += notesz
|
||||
align := p.Align
|
||||
alignedOff := (off + align - 1) &^ (align - 1)
|
||||
notesz += alignedOff - off
|
||||
off = alignedOff
|
||||
filesz -= notesz
|
||||
note = note[notesz:]
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a Go note, use a GNU note if available.
|
||||
// This is what gccgo uses.
|
||||
if gnu != "" {
|
||||
return gnu, nil
|
||||
}
|
||||
|
||||
// No note. Treat as successful but build ID empty.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// The Go build ID is stored at the beginning of the Mach-O __text segment.
|
||||
// The caller has already opened filename, to get f, and read a few kB out, in data.
|
||||
// Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
|
||||
// of other junk placed in the file ahead of the main text.
|
||||
func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
|
||||
// If the data we want has already been read, don't worry about Mach-O parsing.
|
||||
// This is both an optimization and a hedge against the Mach-O parsing failing
|
||||
// in the future due to, for example, the name of the __text section changing.
|
||||
if b, err := readRaw(name, data); b != "" && err == nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
mf, err := macho.NewFile(f)
|
||||
if err != nil {
|
||||
return "", &fs.PathError{Path: name, Op: "parse", Err: err}
|
||||
}
|
||||
|
||||
sect := mf.Section("__text")
|
||||
if sect == nil {
|
||||
// Every binary has a __text section. Something is wrong.
|
||||
return "", &fs.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
|
||||
}
|
||||
|
||||
// It should be in the first few bytes, but read a lot just in case,
|
||||
// especially given our past problems on OS X with the build ID moving.
|
||||
// There shouldn't be much difference between reading 4kB and 32kB:
|
||||
// the hard part is getting to the data, not transferring it.
|
||||
n := sect.Size
|
||||
if n > uint64(readSize) {
|
||||
n = uint64(readSize)
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return readRaw(name, buf)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package buildid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FindAndHash reads all of r and returns the offsets of occurrences of id.
|
||||
// While reading, findAndHash also computes and returns
|
||||
// a hash of the content of r, but with occurrences of id replaced by zeros.
|
||||
// FindAndHash reads bufSize bytes from r at a time.
|
||||
// If bufSize == 0, FindAndHash uses a reasonable default.
|
||||
func FindAndHash(r io.Reader, id string, bufSize int) (matches []int64, hash [32]byte, err error) {
|
||||
if bufSize == 0 {
|
||||
bufSize = 31 * 1024 // bufSize+little will likely fit in 32 kB
|
||||
}
|
||||
if len(id) == 0 {
|
||||
return nil, [32]byte{}, fmt.Errorf("buildid.FindAndHash: no id specified")
|
||||
}
|
||||
if len(id) > bufSize {
|
||||
return nil, [32]byte{}, fmt.Errorf("buildid.FindAndHash: buffer too small")
|
||||
}
|
||||
zeros := make([]byte, len(id))
|
||||
idBytes := []byte(id)
|
||||
|
||||
// The strategy is to read the file through buf, looking for id,
|
||||
// but we need to worry about what happens if id is broken up
|
||||
// and returned in parts by two different reads.
|
||||
// We allocate a tiny buffer (at least len(id)) and a big buffer (bufSize bytes)
|
||||
// next to each other in memory and then copy the tail of
|
||||
// one read into the tiny buffer before reading new data into the big buffer.
|
||||
// The search for id is over the entire tiny+big buffer.
|
||||
tiny := (len(id) + 127) &^ 127 // round up to 128-aligned
|
||||
buf := make([]byte, tiny+bufSize)
|
||||
h := sha256.New()
|
||||
start := tiny
|
||||
for offset := int64(0); ; {
|
||||
// The file offset maintained by the loop corresponds to &buf[tiny].
|
||||
// buf[start:tiny] is left over from previous iteration.
|
||||
// After reading n bytes into buf[tiny:], we process buf[start:tiny+n].
|
||||
n, err := io.ReadFull(r, buf[tiny:])
|
||||
if err != io.ErrUnexpectedEOF && err != io.EOF && err != nil {
|
||||
return nil, [32]byte{}, err
|
||||
}
|
||||
|
||||
// Process any matches.
|
||||
for {
|
||||
i := bytes.Index(buf[start:tiny+n], idBytes)
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
matches = append(matches, offset+int64(start+i-tiny))
|
||||
h.Write(buf[start : start+i])
|
||||
h.Write(zeros)
|
||||
start += i + len(id)
|
||||
}
|
||||
if n < bufSize {
|
||||
// Did not fill buffer, must be at end of file.
|
||||
h.Write(buf[start : tiny+n])
|
||||
break
|
||||
}
|
||||
|
||||
// Process all but final tiny bytes of buf (bufSize = len(buf)-tiny).
|
||||
// Note that start > len(buf)-tiny is possible, if the search above
|
||||
// found an id ending in the final tiny fringe. That's OK.
|
||||
if start < len(buf)-tiny {
|
||||
h.Write(buf[start : len(buf)-tiny])
|
||||
start = len(buf) - tiny
|
||||
}
|
||||
|
||||
// Slide ending tiny-sized fringe to beginning of buffer.
|
||||
copy(buf[0:], buf[bufSize:])
|
||||
start -= bufSize
|
||||
offset += int64(bufSize)
|
||||
}
|
||||
h.Sum(hash[:0])
|
||||
return matches, hash, nil
|
||||
}
|
||||
|
||||
func Rewrite(w io.WriterAt, pos []int64, id string) error {
|
||||
b := []byte(id)
|
||||
for _, p := range pos {
|
||||
if _, err := w.WriteAt(b, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
// Copyright 2021 The Go 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 buildinfo provides access to information embedded in a Go binary
|
||||
// about how it was built. This includes the Go toolchain version, and the
|
||||
// set of modules used (for binaries built in module mode).
|
||||
//
|
||||
// Build information is available for the currently running binary in
|
||||
// runtime/debug.ReadBuildInfo.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package buildinfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/mandiant/GoReSym/runtime/debug"
|
||||
"github.com/mandiant/GoReSym/xcoff"
|
||||
)
|
||||
|
||||
// Type alias for build info. We cannot move the types here, since
|
||||
// runtime/debug would need to import this package, which would make it
|
||||
// a much larger dependency.
|
||||
type BuildInfo = debug.BuildInfo
|
||||
|
||||
var (
|
||||
// errUnrecognizedFormat is returned when a given executable file doesn't
|
||||
// appear to be in a known format, or it breaks the rules of that format,
|
||||
// or when there are I/O errors reading the file.
|
||||
errUnrecognizedFormat = errors.New("unrecognized file format")
|
||||
|
||||
// errNotGoExe is returned when a given executable file is valid but does
|
||||
// not contain Go build information.
|
||||
errNotGoExe = errors.New("not a Go executable")
|
||||
|
||||
// The build info blob left by the linker is identified by
|
||||
// a 16-byte header, consisting of buildInfoMagic (14 bytes),
|
||||
// the binary's pointer size (1 byte),
|
||||
// and whether the binary is big endian (1 byte).
|
||||
buildInfoMagic = []byte("\xff Go buildinf:")
|
||||
)
|
||||
|
||||
// ReadFile returns build information embedded in a Go binary
|
||||
// file at the given path. Most information is only available for binaries built
|
||||
// with module support.
|
||||
func ReadFile(name string) (info *BuildInfo, err error) {
|
||||
defer func() {
|
||||
if pathErr := (*fs.PathError)(nil); errors.As(err, &pathErr) {
|
||||
err = fmt.Errorf("could not read Go build info: %w", err)
|
||||
} else if err != nil {
|
||||
err = fmt.Errorf("could not read Go build info from %s: %w", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return Read(f)
|
||||
}
|
||||
|
||||
// Read returns build information embedded in a Go binary file
|
||||
// accessed through the given ReaderAt. Most information is only available for
|
||||
// binaries built with module support.
|
||||
func Read(r io.ReaderAt) (*BuildInfo, error) {
|
||||
vers, mod, err := readRawBuildInfo(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bi := &BuildInfo{}
|
||||
if err := bi.UnmarshalText([]byte(mod)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bi.GoVersion = vers
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
type exe interface {
|
||||
// ReadData reads and returns up to size bytes starting at virtual address addr.
|
||||
ReadData(addr, size uint64) ([]byte, error)
|
||||
|
||||
// DataStart returns the virtual address of the segment or section that
|
||||
// should contain build information. This is either a specially named section
|
||||
// or the first writable non-zero data segment.
|
||||
DataStart() uint64
|
||||
}
|
||||
|
||||
// readRawBuildInfo extracts the Go toolchain version and module information
|
||||
// strings from a Go binary. On success, vers should be non-empty. mod
|
||||
// is empty if the binary was not built with modules enabled.
|
||||
func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) {
|
||||
// Read the first bytes of the file to identify the format, then delegate to
|
||||
// a format-specific function to load segment and section headers.
|
||||
ident := make([]byte, 16)
|
||||
if n, err := r.ReadAt(ident, 0); n < len(ident) || err != nil {
|
||||
return "", "", errUnrecognizedFormat
|
||||
}
|
||||
|
||||
var x exe
|
||||
switch {
|
||||
case bytes.HasPrefix(ident, []byte("\x7FELF")):
|
||||
f, err := elf.NewFile(r)
|
||||
if err != nil {
|
||||
return "", "", errUnrecognizedFormat
|
||||
}
|
||||
x = &elfExe{f}
|
||||
case bytes.HasPrefix(ident, []byte("MZ")):
|
||||
f, err := pe.NewFile(r)
|
||||
if err != nil {
|
||||
return "", "", errUnrecognizedFormat
|
||||
}
|
||||
x = &peExe{f}
|
||||
case bytes.HasPrefix(ident, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(ident[1:], []byte("\xFA\xED\xFE")):
|
||||
f, err := macho.NewFile(r)
|
||||
if err != nil {
|
||||
return "", "", errUnrecognizedFormat
|
||||
}
|
||||
x = &machoExe{f}
|
||||
case bytes.HasPrefix(ident, []byte{0x01, 0xDF}) || bytes.HasPrefix(ident, []byte{0x01, 0xF7}):
|
||||
f, err := xcoff.NewFile(r)
|
||||
if err != nil {
|
||||
return "", "", errUnrecognizedFormat
|
||||
}
|
||||
x = &xcoffExe{f}
|
||||
default:
|
||||
return "", "", errUnrecognizedFormat
|
||||
}
|
||||
|
||||
// Read the first 64kB of dataAddr to find the build info blob.
|
||||
// On some platforms, the blob will be in its own section, and DataStart
|
||||
// returns the address of that section. On others, it's somewhere in the
|
||||
// data segment; the linker puts it near the beginning.
|
||||
// See cmd/link/internal/ld.Link.buildinfo.
|
||||
dataAddr := x.DataStart()
|
||||
data, err := x.ReadData(dataAddr, 64*1024)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
const (
|
||||
buildInfoAlign = 16
|
||||
buildInfoSize = 32
|
||||
)
|
||||
for {
|
||||
i := bytes.Index(data, buildInfoMagic)
|
||||
if i < 0 || len(data)-i < buildInfoSize {
|
||||
return "", "", errNotGoExe
|
||||
}
|
||||
if i%buildInfoAlign == 0 && len(data)-i >= buildInfoSize {
|
||||
data = data[i:]
|
||||
break
|
||||
}
|
||||
data = data[(i+buildInfoAlign-1)&^buildInfoAlign:]
|
||||
}
|
||||
|
||||
// Decode the blob.
|
||||
// The first 14 bytes are buildInfoMagic.
|
||||
// The next two bytes indicate pointer size in bytes (4 or 8) and endianness
|
||||
// (0 for little, 1 for big).
|
||||
// Two virtual addresses to Go strings follow that: runtime.buildVersion,
|
||||
// and runtime.modinfo.
|
||||
// On 32-bit platforms, the last 8 bytes are unused.
|
||||
// If the endianness has the 2 bit set, then the pointers are zero
|
||||
// and the 32-byte header is followed by varint-prefixed string data
|
||||
// for the two string values we care about.
|
||||
ptrSize := int(data[14])
|
||||
if data[15]&2 != 0 {
|
||||
vers, data = decodeString(data[32:])
|
||||
mod, data = decodeString(data)
|
||||
} else {
|
||||
bigEndian := data[15] != 0
|
||||
var bo binary.ByteOrder
|
||||
if bigEndian {
|
||||
bo = binary.BigEndian
|
||||
} else {
|
||||
bo = binary.LittleEndian
|
||||
}
|
||||
var readPtr func([]byte) uint64
|
||||
if ptrSize == 4 {
|
||||
readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
|
||||
} else {
|
||||
readPtr = bo.Uint64
|
||||
}
|
||||
vers = readString(x, ptrSize, readPtr, readPtr(data[16:]))
|
||||
mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:]))
|
||||
}
|
||||
if vers == "" {
|
||||
return "", "", errNotGoExe
|
||||
}
|
||||
if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
|
||||
// Strip module framing: sentinel strings delimiting the module info.
|
||||
// These are cmd/go/internal/modload.infoStart and infoEnd.
|
||||
mod = mod[16 : len(mod)-16]
|
||||
} else {
|
||||
mod = ""
|
||||
}
|
||||
|
||||
return vers, mod, nil
|
||||
}
|
||||
|
||||
func decodeString(data []byte) (s string, rest []byte) {
|
||||
u, n := binary.Uvarint(data)
|
||||
if n <= 0 || u >= uint64(len(data)-n) {
|
||||
return "", nil
|
||||
}
|
||||
return string(data[n : uint64(n)+u]), data[uint64(n)+u:]
|
||||
}
|
||||
|
||||
// readString returns the string at address addr in the executable x.
|
||||
func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string {
|
||||
hdr, err := x.ReadData(addr, uint64(2*ptrSize))
|
||||
if err != nil || len(hdr) < 2*ptrSize {
|
||||
return ""
|
||||
}
|
||||
dataAddr := readPtr(hdr)
|
||||
dataLen := readPtr(hdr[ptrSize:])
|
||||
data, err := x.ReadData(dataAddr, dataLen)
|
||||
if err != nil || uint64(len(data)) < dataLen {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// elfExe is the ELF implementation of the exe interface.
|
||||
type elfExe struct {
|
||||
f *elf.File
|
||||
}
|
||||
|
||||
func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
for _, prog := range x.f.Progs {
|
||||
if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
|
||||
n := prog.Vaddr + prog.Filesz - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := prog.ReadAt(data, int64(addr-prog.Vaddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, errUnrecognizedFormat
|
||||
}
|
||||
|
||||
func (x *elfExe) DataStart() uint64 {
|
||||
for _, s := range x.f.Sections {
|
||||
if s.Name == ".go.buildinfo" {
|
||||
return s.Addr
|
||||
}
|
||||
}
|
||||
for _, p := range x.f.Progs {
|
||||
if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
|
||||
return p.Vaddr
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// peExe is the PE (Windows Portable Executable) implementation of the exe interface.
|
||||
type peExe struct {
|
||||
f *pe.File
|
||||
}
|
||||
|
||||
func (x *peExe) imageBase() uint64 {
|
||||
switch oh := x.f.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
return uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
return oh.ImageBase
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
addr -= x.imageBase()
|
||||
for _, sect := range x.f.Sections {
|
||||
if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
|
||||
n := uint64(sect.VirtualAddress+sect.Size) - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
|
||||
if err != nil {
|
||||
return nil, errUnrecognizedFormat
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, errUnrecognizedFormat
|
||||
}
|
||||
|
||||
func (x *peExe) DataStart() uint64 {
|
||||
// Assume data is first writable section.
|
||||
const (
|
||||
IMAGE_SCN_CNT_CODE = 0x00000020
|
||||
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
|
||||
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
|
||||
IMAGE_SCN_MEM_EXECUTE = 0x20000000
|
||||
IMAGE_SCN_MEM_READ = 0x40000000
|
||||
IMAGE_SCN_MEM_WRITE = 0x80000000
|
||||
IMAGE_SCN_MEM_DISCARDABLE = 0x2000000
|
||||
IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000
|
||||
IMAGE_SCN_ALIGN_32BYTES = 0x600000
|
||||
)
|
||||
for _, sect := range x.f.Sections {
|
||||
if sect.VirtualAddress != 0 && sect.Size != 0 &&
|
||||
sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
|
||||
return uint64(sect.VirtualAddress) + x.imageBase()
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
|
||||
type machoExe struct {
|
||||
f *macho.File
|
||||
}
|
||||
|
||||
func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
|
||||
if seg.Name == "__PAGEZERO" {
|
||||
continue
|
||||
}
|
||||
n := seg.Addr + seg.Filesz - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := seg.ReadAt(data, int64(addr-seg.Addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, errUnrecognizedFormat
|
||||
}
|
||||
|
||||
func (x *machoExe) DataStart() uint64 {
|
||||
// Look for section named "__go_buildinfo".
|
||||
for _, sec := range x.f.Sections {
|
||||
if sec.Name == "__go_buildinfo" {
|
||||
return sec.Addr
|
||||
}
|
||||
}
|
||||
// Try the first non-empty writable segment.
|
||||
const RW = 3
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
|
||||
return seg.Addr
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface.
|
||||
type xcoffExe struct {
|
||||
f *xcoff.File
|
||||
}
|
||||
|
||||
func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
for _, sect := range x.f.Sections {
|
||||
if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
|
||||
n := uint64(sect.VirtualAddress+sect.Size) - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped")
|
||||
}
|
||||
|
||||
func (x *xcoffExe) DataStart() uint64 {
|
||||
return x.f.SectionByType(xcoff.STYP_DATA).VirtualAddress
|
||||
}
|
||||
+470
@@ -0,0 +1,470 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file implements the encoding of source positions.
|
||||
|
||||
package src
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Pos encodes a source position consisting of a (line, column) number pair
|
||||
// and a position base. A zero Pos is a ready to use "unknown" position (nil
|
||||
// position base and zero line number).
|
||||
//
|
||||
// The (line, column) values refer to a position in a file independent of any
|
||||
// position base ("absolute" file position).
|
||||
//
|
||||
// The position base is used to determine the "relative" position, that is the
|
||||
// filename and line number relative to the position base. If the base refers
|
||||
// to the current file, there is no difference between absolute and relative
|
||||
// positions. If it refers to a //line directive, a relative position is relative
|
||||
// to that directive. A position base in turn contains the position at which it
|
||||
// was introduced in the current file.
|
||||
type Pos struct {
|
||||
base *PosBase
|
||||
lico
|
||||
}
|
||||
|
||||
// NoPos is a valid unknown position.
|
||||
var NoPos Pos
|
||||
|
||||
// MakePos creates a new Pos value with the given base, and (file-absolute)
|
||||
// line and column.
|
||||
func MakePos(base *PosBase, line, col uint) Pos {
|
||||
return Pos{base, makeLico(line, col)}
|
||||
}
|
||||
|
||||
// IsKnown reports whether the position p is known.
|
||||
// A position is known if it either has a non-nil
|
||||
// position base, or a non-zero line number.
|
||||
func (p Pos) IsKnown() bool {
|
||||
return p.base != nil || p.Line() != 0
|
||||
}
|
||||
|
||||
// Before reports whether the position p comes before q in the source.
|
||||
// For positions in different files, ordering is by filename.
|
||||
func (p Pos) Before(q Pos) bool {
|
||||
n, m := p.Filename(), q.Filename()
|
||||
return n < m || n == m && p.lico < q.lico
|
||||
}
|
||||
|
||||
// After reports whether the position p comes after q in the source.
|
||||
// For positions in different files, ordering is by filename.
|
||||
func (p Pos) After(q Pos) bool {
|
||||
n, m := p.Filename(), q.Filename()
|
||||
return n > m || n == m && p.lico > q.lico
|
||||
}
|
||||
|
||||
func (p Pos) LineNumber() string {
|
||||
if !p.IsKnown() {
|
||||
return "?"
|
||||
}
|
||||
return p.lico.lineNumber()
|
||||
}
|
||||
|
||||
func (p Pos) LineNumberHTML() string {
|
||||
if !p.IsKnown() {
|
||||
return "?"
|
||||
}
|
||||
return p.lico.lineNumberHTML()
|
||||
}
|
||||
|
||||
// Filename returns the name of the actual file containing this position.
|
||||
func (p Pos) Filename() string { return p.base.Pos().RelFilename() }
|
||||
|
||||
// Base returns the position base.
|
||||
func (p Pos) Base() *PosBase { return p.base }
|
||||
|
||||
// SetBase sets the position base.
|
||||
func (p *Pos) SetBase(base *PosBase) { p.base = base }
|
||||
|
||||
// RelFilename returns the filename recorded with the position's base.
|
||||
func (p Pos) RelFilename() string { return p.base.Filename() }
|
||||
|
||||
// RelLine returns the line number relative to the position's base.
|
||||
func (p Pos) RelLine() uint {
|
||||
b := p.base
|
||||
if b.Line() == 0 {
|
||||
// base line is unknown => relative line is unknown
|
||||
return 0
|
||||
}
|
||||
return b.Line() + (p.Line() - b.Pos().Line())
|
||||
}
|
||||
|
||||
// RelCol returns the column number relative to the position's base.
|
||||
func (p Pos) RelCol() uint {
|
||||
b := p.base
|
||||
if b.Col() == 0 {
|
||||
// base column is unknown => relative column is unknown
|
||||
// (the current specification for line directives requires
|
||||
// this to apply until the next PosBase/line directive,
|
||||
// not just until the new newline)
|
||||
return 0
|
||||
}
|
||||
if p.Line() == b.Pos().Line() {
|
||||
// p on same line as p's base => column is relative to p's base
|
||||
return b.Col() + (p.Col() - b.Pos().Col())
|
||||
}
|
||||
return p.Col()
|
||||
}
|
||||
|
||||
// AbsFilename() returns the absolute filename recorded with the position's base.
|
||||
func (p Pos) AbsFilename() string { return p.base.AbsFilename() }
|
||||
|
||||
// SymFilename() returns the absolute filename recorded with the position's base,
|
||||
// prefixed by FileSymPrefix to make it appropriate for use as a linker symbol.
|
||||
func (p Pos) SymFilename() string { return p.base.SymFilename() }
|
||||
|
||||
func (p Pos) String() string {
|
||||
return p.Format(true, true)
|
||||
}
|
||||
|
||||
// Format formats a position as "filename:line" or "filename:line:column",
|
||||
// controlled by the showCol flag and if the column is known (!= 0).
|
||||
// For positions relative to line directives, the original position is
|
||||
// shown as well, as in "filename:line[origfile:origline:origcolumn] if
|
||||
// showOrig is set.
|
||||
func (p Pos) Format(showCol, showOrig bool) string {
|
||||
buf := new(bytes.Buffer)
|
||||
p.WriteTo(buf, showCol, showOrig)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// WriteTo a position to w, formatted as Format does.
|
||||
func (p Pos) WriteTo(w io.Writer, showCol, showOrig bool) {
|
||||
if !p.IsKnown() {
|
||||
io.WriteString(w, "<unknown line number>")
|
||||
return
|
||||
}
|
||||
|
||||
if b := p.base; b == b.Pos().base {
|
||||
// base is file base (incl. nil)
|
||||
format(w, p.Filename(), p.Line(), p.Col(), showCol)
|
||||
return
|
||||
}
|
||||
|
||||
// base is relative
|
||||
// Print the column only for the original position since the
|
||||
// relative position's column information may be bogus (it's
|
||||
// typically generated code and we can't say much about the
|
||||
// original source at that point but for the file:line info
|
||||
// that's provided via a line directive).
|
||||
// TODO(gri) This may not be true if we have an inlining base.
|
||||
// We may want to differentiate at some point.
|
||||
format(w, p.RelFilename(), p.RelLine(), p.RelCol(), showCol)
|
||||
if showOrig {
|
||||
io.WriteString(w, "[")
|
||||
format(w, p.Filename(), p.Line(), p.Col(), showCol)
|
||||
io.WriteString(w, "]")
|
||||
}
|
||||
}
|
||||
|
||||
// format formats a (filename, line, col) tuple as "filename:line" (showCol
|
||||
// is false or col == 0) or "filename:line:column" (showCol is true and col != 0).
|
||||
func format(w io.Writer, filename string, line, col uint, showCol bool) {
|
||||
io.WriteString(w, filename)
|
||||
io.WriteString(w, ":")
|
||||
fmt.Fprint(w, line)
|
||||
// col == 0 and col == colMax are interpreted as unknown column values
|
||||
if showCol && 0 < col && col < colMax {
|
||||
io.WriteString(w, ":")
|
||||
fmt.Fprint(w, col)
|
||||
}
|
||||
}
|
||||
|
||||
// formatstr wraps format to return a string.
|
||||
func formatstr(filename string, line, col uint, showCol bool) string {
|
||||
buf := new(bytes.Buffer)
|
||||
format(buf, filename, line, col, showCol)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PosBase
|
||||
|
||||
// A PosBase encodes a filename and base position.
|
||||
// Typically, each file and line directive introduce a PosBase.
|
||||
type PosBase struct {
|
||||
pos Pos // position at which the relative position is (line, col)
|
||||
filename string // file name used to open source file, for error messages
|
||||
absFilename string // absolute file name, for PC-Line tables
|
||||
symFilename string // cached symbol file name, to avoid repeated string concatenation
|
||||
line, col uint // relative line, column number at pos
|
||||
inl int // inlining index (see cmd/internal/obj/inl.go)
|
||||
}
|
||||
|
||||
// NewFileBase returns a new *PosBase for a file with the given (relative and
|
||||
// absolute) filenames.
|
||||
func NewFileBase(filename, absFilename string) *PosBase {
|
||||
base := &PosBase{
|
||||
filename: filename,
|
||||
absFilename: absFilename,
|
||||
symFilename: FileSymPrefix + absFilename,
|
||||
line: 1,
|
||||
col: 1,
|
||||
inl: -1,
|
||||
}
|
||||
base.pos = MakePos(base, 1, 1)
|
||||
return base
|
||||
}
|
||||
|
||||
// NewLinePragmaBase returns a new *PosBase for a line directive of the form
|
||||
// //line filename:line:col
|
||||
// /*line filename:line:col*/
|
||||
// at position pos.
|
||||
func NewLinePragmaBase(pos Pos, filename, absFilename string, line, col uint) *PosBase {
|
||||
return &PosBase{pos, filename, absFilename, FileSymPrefix + absFilename, line, col, -1}
|
||||
}
|
||||
|
||||
// NewInliningBase returns a copy of the old PosBase with the given inlining
|
||||
// index. If old == nil, the resulting PosBase has no filename.
|
||||
func NewInliningBase(old *PosBase, inlTreeIndex int) *PosBase {
|
||||
if old == nil {
|
||||
base := &PosBase{line: 1, col: 1, inl: inlTreeIndex}
|
||||
base.pos = MakePos(base, 1, 1)
|
||||
return base
|
||||
}
|
||||
copy := *old
|
||||
base := ©
|
||||
base.inl = inlTreeIndex
|
||||
if old == old.pos.base {
|
||||
base.pos.base = base
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
var noPos Pos
|
||||
|
||||
// Pos returns the position at which base is located.
|
||||
// If b == nil, the result is the zero position.
|
||||
func (b *PosBase) Pos() *Pos {
|
||||
if b != nil {
|
||||
return &b.pos
|
||||
}
|
||||
return &noPos
|
||||
}
|
||||
|
||||
// Filename returns the filename recorded with the base.
|
||||
// If b == nil, the result is the empty string.
|
||||
func (b *PosBase) Filename() string {
|
||||
if b != nil {
|
||||
return b.filename
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// AbsFilename returns the absolute filename recorded with the base.
|
||||
// If b == nil, the result is the empty string.
|
||||
func (b *PosBase) AbsFilename() string {
|
||||
if b != nil {
|
||||
return b.absFilename
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const FileSymPrefix = "gofile.."
|
||||
|
||||
// SymFilename returns the absolute filename recorded with the base,
|
||||
// prefixed by FileSymPrefix to make it appropriate for use as a linker symbol.
|
||||
// If b is nil, SymFilename returns FileSymPrefix + "??".
|
||||
func (b *PosBase) SymFilename() string {
|
||||
if b != nil {
|
||||
return b.symFilename
|
||||
}
|
||||
return FileSymPrefix + "??"
|
||||
}
|
||||
|
||||
// Line returns the line number recorded with the base.
|
||||
// If b == nil, the result is 0.
|
||||
func (b *PosBase) Line() uint {
|
||||
if b != nil {
|
||||
return b.line
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Col returns the column number recorded with the base.
|
||||
// If b == nil, the result is 0.
|
||||
func (b *PosBase) Col() uint {
|
||||
if b != nil {
|
||||
return b.col
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// InliningIndex returns the index into the global inlining
|
||||
// tree recorded with the base. If b == nil or the base has
|
||||
// not been inlined, the result is < 0.
|
||||
func (b *PosBase) InliningIndex() int {
|
||||
if b != nil {
|
||||
return b.inl
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// lico
|
||||
|
||||
// A lico is a compact encoding of a LIne and COlumn number.
|
||||
type lico uint32
|
||||
|
||||
// Layout constants: 20 bits for line, 8 bits for column, 2 for isStmt, 2 for pro/epilogue
|
||||
// (If this is too tight, we can either make lico 64b wide,
|
||||
// or we can introduce a tiered encoding where we remove column
|
||||
// information as line numbers grow bigger; similar to what gcc
|
||||
// does.)
|
||||
// The bitfield order is chosen to make IsStmt be the least significant
|
||||
// part of a position; its use is to communicate statement edges through
|
||||
// instruction scrambling in code generation, not to impose an order.
|
||||
// TODO: Prologue and epilogue are perhaps better handled as pseudo-ops for the assembler,
|
||||
// because they have almost no interaction with other uses of the position.
|
||||
const (
|
||||
lineBits, lineMax = 20, 1<<lineBits - 2
|
||||
bogusLine = 1 // Used to disrupt infinite loops to prevent debugger looping
|
||||
isStmtBits, isStmtMax = 2, 1<<isStmtBits - 1
|
||||
xlogueBits, xlogueMax = 2, 1<<xlogueBits - 1
|
||||
colBits, colMax = 32 - lineBits - xlogueBits - isStmtBits, 1<<colBits - 1
|
||||
|
||||
isStmtShift = 0
|
||||
isStmtMask = isStmtMax << isStmtShift
|
||||
xlogueShift = isStmtBits + isStmtShift
|
||||
xlogueMask = xlogueMax << xlogueShift
|
||||
colShift = xlogueBits + xlogueShift
|
||||
lineShift = colBits + colShift
|
||||
)
|
||||
const (
|
||||
// It is expected that the front end or a phase in SSA will usually generate positions tagged with
|
||||
// PosDefaultStmt, but note statement boundaries with PosIsStmt. Simple statements will have a single
|
||||
// boundary; for loops with initialization may have one for their entry and one for their back edge
|
||||
// (this depends on exactly how the loop is compiled; the intent is to provide a good experience to a
|
||||
// user debugging a program; the goal is that a breakpoint set on the loop line fires both on entry
|
||||
// and on iteration). Proper treatment of non-gofmt input with multiple simple statements on a single
|
||||
// line is TBD.
|
||||
//
|
||||
// Optimizing compilation will move instructions around, and some of these will become known-bad as
|
||||
// step targets for debugging purposes (examples: register spills and reloads; code generated into
|
||||
// the entry block; invariant code hoisted out of loops) but those instructions will still have interesting
|
||||
// positions for profiling purposes. To reflect this these positions will be changed to PosNotStmt.
|
||||
//
|
||||
// When the optimizer removes an instruction marked PosIsStmt; it should attempt to find a nearby
|
||||
// instruction with the same line marked PosDefaultStmt to be the new statement boundary. I.e., the
|
||||
// optimizer should make a best-effort to conserve statement boundary positions, and might be enhanced
|
||||
// to note when a statement boundary is not conserved.
|
||||
//
|
||||
// Code cloning, e.g. loop unrolling or loop unswitching, is an exception to the conservation rule
|
||||
// because a user running a debugger would expect to see breakpoints active in the copies of the code.
|
||||
//
|
||||
// In non-optimizing compilation there is still a role for PosNotStmt because of code generation
|
||||
// into the entry block. PosIsStmt statement positions should be conserved.
|
||||
//
|
||||
// When code generation occurs any remaining default-marked positions are replaced with not-statement
|
||||
// positions.
|
||||
//
|
||||
PosDefaultStmt uint = iota // Default; position is not a statement boundary, but might be if optimization removes the designated statement boundary
|
||||
PosIsStmt // Position is a statement boundary; if optimization removes the corresponding instruction, it should attempt to find a new instruction to be the boundary.
|
||||
PosNotStmt // Position should not be a statement boundary, but line should be preserved for profiling and low-level debugging purposes.
|
||||
)
|
||||
|
||||
type PosXlogue uint
|
||||
|
||||
const (
|
||||
PosDefaultLogue PosXlogue = iota
|
||||
PosPrologueEnd
|
||||
PosEpilogueBegin
|
||||
)
|
||||
|
||||
func makeLicoRaw(line, col uint) lico {
|
||||
return lico(line<<lineShift | col<<colShift)
|
||||
}
|
||||
|
||||
// This is a not-position that will not be elided.
|
||||
// Depending on the debugger (gdb or delve) it may or may not be displayed.
|
||||
func makeBogusLico() lico {
|
||||
return makeLicoRaw(bogusLine, 0).withIsStmt()
|
||||
}
|
||||
|
||||
func makeLico(line, col uint) lico {
|
||||
if line > lineMax {
|
||||
// cannot represent line, use max. line so we have some information
|
||||
line = lineMax
|
||||
}
|
||||
if col > colMax {
|
||||
// cannot represent column, use max. column so we have some information
|
||||
col = colMax
|
||||
}
|
||||
// default is not-sure-if-statement
|
||||
return makeLicoRaw(line, col)
|
||||
}
|
||||
|
||||
func (x lico) Line() uint { return uint(x) >> lineShift }
|
||||
func (x lico) SameLine(y lico) bool { return 0 == (x^y)&^lico(1<<lineShift-1) }
|
||||
func (x lico) Col() uint { return uint(x) >> colShift & colMax }
|
||||
func (x lico) IsStmt() uint {
|
||||
if x == 0 {
|
||||
return PosNotStmt
|
||||
}
|
||||
return uint(x) >> isStmtShift & isStmtMax
|
||||
}
|
||||
func (x lico) Xlogue() PosXlogue {
|
||||
return PosXlogue(uint(x) >> xlogueShift & xlogueMax)
|
||||
}
|
||||
|
||||
// withNotStmt returns a lico for the same location, but not a statement
|
||||
func (x lico) withNotStmt() lico {
|
||||
return x.withStmt(PosNotStmt)
|
||||
}
|
||||
|
||||
// withDefaultStmt returns a lico for the same location, with default isStmt
|
||||
func (x lico) withDefaultStmt() lico {
|
||||
return x.withStmt(PosDefaultStmt)
|
||||
}
|
||||
|
||||
// withIsStmt returns a lico for the same location, tagged as definitely a statement
|
||||
func (x lico) withIsStmt() lico {
|
||||
return x.withStmt(PosIsStmt)
|
||||
}
|
||||
|
||||
// withLogue attaches a prologue/epilogue attribute to a lico
|
||||
func (x lico) withXlogue(xlogue PosXlogue) lico {
|
||||
if x == 0 {
|
||||
if xlogue == 0 {
|
||||
return x
|
||||
}
|
||||
// Normalize 0 to "not a statement"
|
||||
x = lico(PosNotStmt << isStmtShift)
|
||||
}
|
||||
return lico(uint(x) & ^uint(xlogueMax<<xlogueShift) | (uint(xlogue) << xlogueShift))
|
||||
}
|
||||
|
||||
// withStmt returns a lico for the same location with specified is_stmt attribute
|
||||
func (x lico) withStmt(stmt uint) lico {
|
||||
if x == 0 {
|
||||
return lico(0)
|
||||
}
|
||||
return lico(uint(x) & ^uint(isStmtMax<<isStmtShift) | (stmt << isStmtShift))
|
||||
}
|
||||
|
||||
func (x lico) lineNumber() string {
|
||||
return fmt.Sprintf("%d", x.Line())
|
||||
}
|
||||
|
||||
func (x lico) lineNumberHTML() string {
|
||||
if x.IsStmt() == PosDefaultStmt {
|
||||
return fmt.Sprintf("%d", x.Line())
|
||||
}
|
||||
style, pfx := "b", "+"
|
||||
if x.IsStmt() == PosNotStmt {
|
||||
style = "s" // /strike not supported in HTML5
|
||||
pfx = ""
|
||||
}
|
||||
return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style)
|
||||
}
|
||||
|
||||
func (x lico) atColumn1() lico {
|
||||
return makeLico(x.Line(), 1).withIsStmt()
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file implements the compressed encoding of source
|
||||
// positions using a lookup table.
|
||||
|
||||
package src
|
||||
|
||||
// XPos is a more compact representation of Pos.
|
||||
type XPos struct {
|
||||
index int32
|
||||
lico
|
||||
}
|
||||
|
||||
// NoXPos is a valid unknown position.
|
||||
var NoXPos XPos
|
||||
|
||||
// IsKnown reports whether the position p is known.
|
||||
// XPos.IsKnown() matches Pos.IsKnown() for corresponding
|
||||
// positions.
|
||||
func (p XPos) IsKnown() bool {
|
||||
return p.index != 0 || p.Line() != 0
|
||||
}
|
||||
|
||||
// Before reports whether the position p comes before q in the source.
|
||||
// For positions with different bases, ordering is by base index.
|
||||
func (p XPos) Before(q XPos) bool {
|
||||
n, m := p.index, q.index
|
||||
return n < m || n == m && p.lico < q.lico
|
||||
}
|
||||
|
||||
// SameFile reports whether p and q are positions in the same file.
|
||||
func (p XPos) SameFile(q XPos) bool {
|
||||
return p.index == q.index
|
||||
}
|
||||
|
||||
// SameFileAndLine reports whether p and q are positions on the same line in the same file.
|
||||
func (p XPos) SameFileAndLine(q XPos) bool {
|
||||
return p.index == q.index && p.lico.SameLine(q.lico)
|
||||
}
|
||||
|
||||
// After reports whether the position p comes after q in the source.
|
||||
// For positions with different bases, ordering is by base index.
|
||||
func (p XPos) After(q XPos) bool {
|
||||
n, m := p.index, q.index
|
||||
return n > m || n == m && p.lico > q.lico
|
||||
}
|
||||
|
||||
// WithNotStmt returns the same location to be marked with DWARF is_stmt=0
|
||||
func (p XPos) WithNotStmt() XPos {
|
||||
p.lico = p.lico.withNotStmt()
|
||||
return p
|
||||
}
|
||||
|
||||
// WithDefaultStmt returns the same location with undetermined is_stmt
|
||||
func (p XPos) WithDefaultStmt() XPos {
|
||||
p.lico = p.lico.withDefaultStmt()
|
||||
return p
|
||||
}
|
||||
|
||||
// WithIsStmt returns the same location to be marked with DWARF is_stmt=1
|
||||
func (p XPos) WithIsStmt() XPos {
|
||||
p.lico = p.lico.withIsStmt()
|
||||
return p
|
||||
}
|
||||
|
||||
// WithBogusLine returns a bogus line that won't match any recorded for the source code.
|
||||
// Its use is to disrupt the statements within an infinite loop so that the debugger
|
||||
// will not itself loop infinitely waiting for the line number to change.
|
||||
// gdb chooses not to display the bogus line; delve shows it with a complaint, but the
|
||||
// alternative behavior is to hang.
|
||||
func (p XPos) WithBogusLine() XPos {
|
||||
if p.index == 0 {
|
||||
// See #35652
|
||||
panic("Assigning a bogus line to XPos with no file will cause mysterious downstream failures.")
|
||||
}
|
||||
p.lico = makeBogusLico()
|
||||
return p
|
||||
}
|
||||
|
||||
// WithXlogue returns the same location but marked with DWARF function prologue/epilogue
|
||||
func (p XPos) WithXlogue(x PosXlogue) XPos {
|
||||
p.lico = p.lico.withXlogue(x)
|
||||
return p
|
||||
}
|
||||
|
||||
// LineNumber returns a string for the line number, "?" if it is not known.
|
||||
func (p XPos) LineNumber() string {
|
||||
if !p.IsKnown() {
|
||||
return "?"
|
||||
}
|
||||
return p.lico.lineNumber()
|
||||
}
|
||||
|
||||
// FileIndex returns a smallish non-negative integer corresponding to the
|
||||
// file for this source position. Smallish is relative; it can be thousands
|
||||
// large, but not millions.
|
||||
func (p XPos) FileIndex() int32 {
|
||||
return p.index
|
||||
}
|
||||
|
||||
func (p XPos) LineNumberHTML() string {
|
||||
if !p.IsKnown() {
|
||||
return "?"
|
||||
}
|
||||
return p.lico.lineNumberHTML()
|
||||
}
|
||||
|
||||
// AtColumn1 returns the same location but shifted to column 1.
|
||||
func (p XPos) AtColumn1() XPos {
|
||||
p.lico = p.lico.atColumn1()
|
||||
return p
|
||||
}
|
||||
|
||||
// A PosTable tracks Pos -> XPos conversions and vice versa.
|
||||
// Its zero value is a ready-to-use PosTable.
|
||||
type PosTable struct {
|
||||
baseList []*PosBase
|
||||
indexMap map[*PosBase]int
|
||||
nameMap map[string]int // Maps file symbol name to index for debug information.
|
||||
}
|
||||
|
||||
// XPos returns the corresponding XPos for the given pos,
|
||||
// adding pos to t if necessary.
|
||||
func (t *PosTable) XPos(pos Pos) XPos {
|
||||
m := t.indexMap
|
||||
if m == nil {
|
||||
// Create new list and map and populate with nil
|
||||
// base so that NoPos always gets index 0.
|
||||
t.baseList = append(t.baseList, nil)
|
||||
m = map[*PosBase]int{nil: 0}
|
||||
t.indexMap = m
|
||||
t.nameMap = make(map[string]int)
|
||||
}
|
||||
i, ok := m[pos.base]
|
||||
if !ok {
|
||||
i = len(t.baseList)
|
||||
t.baseList = append(t.baseList, pos.base)
|
||||
t.indexMap[pos.base] = i
|
||||
if _, ok := t.nameMap[pos.base.symFilename]; !ok {
|
||||
t.nameMap[pos.base.symFilename] = len(t.nameMap)
|
||||
}
|
||||
}
|
||||
return XPos{int32(i), pos.lico}
|
||||
}
|
||||
|
||||
// Pos returns the corresponding Pos for the given p.
|
||||
// If p cannot be translated via t, the function panics.
|
||||
func (t *PosTable) Pos(p XPos) Pos {
|
||||
var base *PosBase
|
||||
if p.index != 0 {
|
||||
base = t.baseList[p.index]
|
||||
}
|
||||
return Pos{base, p.lico}
|
||||
}
|
||||
|
||||
// FileIndex returns the index of the given filename(symbol) in the PosTable, or -1 if not found.
|
||||
func (t *PosTable) FileIndex(filename string) int {
|
||||
if v, ok := t.nameMap[filename]; ok {
|
||||
return v
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// FileTable returns a slice of all files used to build this package.
|
||||
func (t *PosTable) FileTable() []string {
|
||||
// Create a LUT of the global package level file indices. This table is what
|
||||
// is written in the debug_lines header, the file[N] will be referenced as
|
||||
// N+1 in the debug_lines table.
|
||||
fileLUT := make([]string, len(t.nameMap))
|
||||
for str, i := range t.nameMap {
|
||||
fileLUT[i] = str
|
||||
}
|
||||
return fileLUT
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Code generated by "stringer -type Attr -trimprefix=Attr"; DO NOT EDIT.
|
||||
|
||||
package dwarf
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[AttrSibling-1]
|
||||
_ = x[AttrLocation-2]
|
||||
_ = x[AttrName-3]
|
||||
_ = x[AttrOrdering-9]
|
||||
_ = x[AttrByteSize-11]
|
||||
_ = x[AttrBitOffset-12]
|
||||
_ = x[AttrBitSize-13]
|
||||
_ = x[AttrStmtList-16]
|
||||
_ = x[AttrLowpc-17]
|
||||
_ = x[AttrHighpc-18]
|
||||
_ = x[AttrLanguage-19]
|
||||
_ = x[AttrDiscr-21]
|
||||
_ = x[AttrDiscrValue-22]
|
||||
_ = x[AttrVisibility-23]
|
||||
_ = x[AttrImport-24]
|
||||
_ = x[AttrStringLength-25]
|
||||
_ = x[AttrCommonRef-26]
|
||||
_ = x[AttrCompDir-27]
|
||||
_ = x[AttrConstValue-28]
|
||||
_ = x[AttrContainingType-29]
|
||||
_ = x[AttrDefaultValue-30]
|
||||
_ = x[AttrInline-32]
|
||||
_ = x[AttrIsOptional-33]
|
||||
_ = x[AttrLowerBound-34]
|
||||
_ = x[AttrProducer-37]
|
||||
_ = x[AttrPrototyped-39]
|
||||
_ = x[AttrReturnAddr-42]
|
||||
_ = x[AttrStartScope-44]
|
||||
_ = x[AttrStrideSize-46]
|
||||
_ = x[AttrUpperBound-47]
|
||||
_ = x[AttrAbstractOrigin-49]
|
||||
_ = x[AttrAccessibility-50]
|
||||
_ = x[AttrAddrClass-51]
|
||||
_ = x[AttrArtificial-52]
|
||||
_ = x[AttrBaseTypes-53]
|
||||
_ = x[AttrCalling-54]
|
||||
_ = x[AttrCount-55]
|
||||
_ = x[AttrDataMemberLoc-56]
|
||||
_ = x[AttrDeclColumn-57]
|
||||
_ = x[AttrDeclFile-58]
|
||||
_ = x[AttrDeclLine-59]
|
||||
_ = x[AttrDeclaration-60]
|
||||
_ = x[AttrDiscrList-61]
|
||||
_ = x[AttrEncoding-62]
|
||||
_ = x[AttrExternal-63]
|
||||
_ = x[AttrFrameBase-64]
|
||||
_ = x[AttrFriend-65]
|
||||
_ = x[AttrIdentifierCase-66]
|
||||
_ = x[AttrMacroInfo-67]
|
||||
_ = x[AttrNamelistItem-68]
|
||||
_ = x[AttrPriority-69]
|
||||
_ = x[AttrSegment-70]
|
||||
_ = x[AttrSpecification-71]
|
||||
_ = x[AttrStaticLink-72]
|
||||
_ = x[AttrType-73]
|
||||
_ = x[AttrUseLocation-74]
|
||||
_ = x[AttrVarParam-75]
|
||||
_ = x[AttrVirtuality-76]
|
||||
_ = x[AttrVtableElemLoc-77]
|
||||
_ = x[AttrAllocated-78]
|
||||
_ = x[AttrAssociated-79]
|
||||
_ = x[AttrDataLocation-80]
|
||||
_ = x[AttrStride-81]
|
||||
_ = x[AttrEntrypc-82]
|
||||
_ = x[AttrUseUTF8-83]
|
||||
_ = x[AttrExtension-84]
|
||||
_ = x[AttrRanges-85]
|
||||
_ = x[AttrTrampoline-86]
|
||||
_ = x[AttrCallColumn-87]
|
||||
_ = x[AttrCallFile-88]
|
||||
_ = x[AttrCallLine-89]
|
||||
_ = x[AttrDescription-90]
|
||||
_ = x[AttrBinaryScale-91]
|
||||
_ = x[AttrDecimalScale-92]
|
||||
_ = x[AttrSmall-93]
|
||||
_ = x[AttrDecimalSign-94]
|
||||
_ = x[AttrDigitCount-95]
|
||||
_ = x[AttrPictureString-96]
|
||||
_ = x[AttrMutable-97]
|
||||
_ = x[AttrThreadsScaled-98]
|
||||
_ = x[AttrExplicit-99]
|
||||
_ = x[AttrObjectPointer-100]
|
||||
_ = x[AttrEndianity-101]
|
||||
_ = x[AttrElemental-102]
|
||||
_ = x[AttrPure-103]
|
||||
_ = x[AttrRecursive-104]
|
||||
_ = x[AttrSignature-105]
|
||||
_ = x[AttrMainSubprogram-106]
|
||||
_ = x[AttrDataBitOffset-107]
|
||||
_ = x[AttrConstExpr-108]
|
||||
_ = x[AttrEnumClass-109]
|
||||
_ = x[AttrLinkageName-110]
|
||||
_ = x[AttrStringLengthBitSize-111]
|
||||
_ = x[AttrStringLengthByteSize-112]
|
||||
_ = x[AttrRank-113]
|
||||
_ = x[AttrStrOffsetsBase-114]
|
||||
_ = x[AttrAddrBase-115]
|
||||
_ = x[AttrRnglistsBase-116]
|
||||
_ = x[AttrDwoName-118]
|
||||
_ = x[AttrReference-119]
|
||||
_ = x[AttrRvalueReference-120]
|
||||
_ = x[AttrMacros-121]
|
||||
_ = x[AttrCallAllCalls-122]
|
||||
_ = x[AttrCallAllSourceCalls-123]
|
||||
_ = x[AttrCallAllTailCalls-124]
|
||||
_ = x[AttrCallReturnPC-125]
|
||||
_ = x[AttrCallValue-126]
|
||||
_ = x[AttrCallOrigin-127]
|
||||
_ = x[AttrCallParameter-128]
|
||||
_ = x[AttrCallPC-129]
|
||||
_ = x[AttrCallTailCall-130]
|
||||
_ = x[AttrCallTarget-131]
|
||||
_ = x[AttrCallTargetClobbered-132]
|
||||
_ = x[AttrCallDataLocation-133]
|
||||
_ = x[AttrCallDataValue-134]
|
||||
_ = x[AttrNoreturn-135]
|
||||
_ = x[AttrAlignment-136]
|
||||
_ = x[AttrExportSymbols-137]
|
||||
_ = x[AttrDeleted-138]
|
||||
_ = x[AttrDefaulted-139]
|
||||
_ = x[AttrLoclistsBase-140]
|
||||
}
|
||||
|
||||
const _Attr_name = "SiblingLocationNameOrderingByteSizeBitOffsetBitSizeStmtListLowpcHighpcLanguageDiscrDiscrValueVisibilityImportStringLengthCommonRefCompDirConstValueContainingTypeDefaultValueInlineIsOptionalLowerBoundProducerPrototypedReturnAddrStartScopeStrideSizeUpperBoundAbstractOriginAccessibilityAddrClassArtificialBaseTypesCallingCountDataMemberLocDeclColumnDeclFileDeclLineDeclarationDiscrListEncodingExternalFrameBaseFriendIdentifierCaseMacroInfoNamelistItemPrioritySegmentSpecificationStaticLinkTypeUseLocationVarParamVirtualityVtableElemLocAllocatedAssociatedDataLocationStrideEntrypcUseUTF8ExtensionRangesTrampolineCallColumnCallFileCallLineDescriptionBinaryScaleDecimalScaleSmallDecimalSignDigitCountPictureStringMutableThreadsScaledExplicitObjectPointerEndianityElementalPureRecursiveSignatureMainSubprogramDataBitOffsetConstExprEnumClassLinkageNameStringLengthBitSizeStringLengthByteSizeRankStrOffsetsBaseAddrBaseRnglistsBaseDwoNameReferenceRvalueReferenceMacrosCallAllCallsCallAllSourceCallsCallAllTailCallsCallReturnPCCallValueCallOriginCallParameterCallPCCallTailCallCallTargetCallTargetClobberedCallDataLocationCallDataValueNoreturnAlignmentExportSymbolsDeletedDefaultedLoclistsBase"
|
||||
|
||||
var _Attr_map = map[Attr]string{
|
||||
1: _Attr_name[0:7],
|
||||
2: _Attr_name[7:15],
|
||||
3: _Attr_name[15:19],
|
||||
9: _Attr_name[19:27],
|
||||
11: _Attr_name[27:35],
|
||||
12: _Attr_name[35:44],
|
||||
13: _Attr_name[44:51],
|
||||
16: _Attr_name[51:59],
|
||||
17: _Attr_name[59:64],
|
||||
18: _Attr_name[64:70],
|
||||
19: _Attr_name[70:78],
|
||||
21: _Attr_name[78:83],
|
||||
22: _Attr_name[83:93],
|
||||
23: _Attr_name[93:103],
|
||||
24: _Attr_name[103:109],
|
||||
25: _Attr_name[109:121],
|
||||
26: _Attr_name[121:130],
|
||||
27: _Attr_name[130:137],
|
||||
28: _Attr_name[137:147],
|
||||
29: _Attr_name[147:161],
|
||||
30: _Attr_name[161:173],
|
||||
32: _Attr_name[173:179],
|
||||
33: _Attr_name[179:189],
|
||||
34: _Attr_name[189:199],
|
||||
37: _Attr_name[199:207],
|
||||
39: _Attr_name[207:217],
|
||||
42: _Attr_name[217:227],
|
||||
44: _Attr_name[227:237],
|
||||
46: _Attr_name[237:247],
|
||||
47: _Attr_name[247:257],
|
||||
49: _Attr_name[257:271],
|
||||
50: _Attr_name[271:284],
|
||||
51: _Attr_name[284:293],
|
||||
52: _Attr_name[293:303],
|
||||
53: _Attr_name[303:312],
|
||||
54: _Attr_name[312:319],
|
||||
55: _Attr_name[319:324],
|
||||
56: _Attr_name[324:337],
|
||||
57: _Attr_name[337:347],
|
||||
58: _Attr_name[347:355],
|
||||
59: _Attr_name[355:363],
|
||||
60: _Attr_name[363:374],
|
||||
61: _Attr_name[374:383],
|
||||
62: _Attr_name[383:391],
|
||||
63: _Attr_name[391:399],
|
||||
64: _Attr_name[399:408],
|
||||
65: _Attr_name[408:414],
|
||||
66: _Attr_name[414:428],
|
||||
67: _Attr_name[428:437],
|
||||
68: _Attr_name[437:449],
|
||||
69: _Attr_name[449:457],
|
||||
70: _Attr_name[457:464],
|
||||
71: _Attr_name[464:477],
|
||||
72: _Attr_name[477:487],
|
||||
73: _Attr_name[487:491],
|
||||
74: _Attr_name[491:502],
|
||||
75: _Attr_name[502:510],
|
||||
76: _Attr_name[510:520],
|
||||
77: _Attr_name[520:533],
|
||||
78: _Attr_name[533:542],
|
||||
79: _Attr_name[542:552],
|
||||
80: _Attr_name[552:564],
|
||||
81: _Attr_name[564:570],
|
||||
82: _Attr_name[570:577],
|
||||
83: _Attr_name[577:584],
|
||||
84: _Attr_name[584:593],
|
||||
85: _Attr_name[593:599],
|
||||
86: _Attr_name[599:609],
|
||||
87: _Attr_name[609:619],
|
||||
88: _Attr_name[619:627],
|
||||
89: _Attr_name[627:635],
|
||||
90: _Attr_name[635:646],
|
||||
91: _Attr_name[646:657],
|
||||
92: _Attr_name[657:669],
|
||||
93: _Attr_name[669:674],
|
||||
94: _Attr_name[674:685],
|
||||
95: _Attr_name[685:695],
|
||||
96: _Attr_name[695:708],
|
||||
97: _Attr_name[708:715],
|
||||
98: _Attr_name[715:728],
|
||||
99: _Attr_name[728:736],
|
||||
100: _Attr_name[736:749],
|
||||
101: _Attr_name[749:758],
|
||||
102: _Attr_name[758:767],
|
||||
103: _Attr_name[767:771],
|
||||
104: _Attr_name[771:780],
|
||||
105: _Attr_name[780:789],
|
||||
106: _Attr_name[789:803],
|
||||
107: _Attr_name[803:816],
|
||||
108: _Attr_name[816:825],
|
||||
109: _Attr_name[825:834],
|
||||
110: _Attr_name[834:845],
|
||||
111: _Attr_name[845:864],
|
||||
112: _Attr_name[864:884],
|
||||
113: _Attr_name[884:888],
|
||||
114: _Attr_name[888:902],
|
||||
115: _Attr_name[902:910],
|
||||
116: _Attr_name[910:922],
|
||||
118: _Attr_name[922:929],
|
||||
119: _Attr_name[929:938],
|
||||
120: _Attr_name[938:953],
|
||||
121: _Attr_name[953:959],
|
||||
122: _Attr_name[959:971],
|
||||
123: _Attr_name[971:989],
|
||||
124: _Attr_name[989:1005],
|
||||
125: _Attr_name[1005:1017],
|
||||
126: _Attr_name[1017:1026],
|
||||
127: _Attr_name[1026:1036],
|
||||
128: _Attr_name[1036:1049],
|
||||
129: _Attr_name[1049:1055],
|
||||
130: _Attr_name[1055:1067],
|
||||
131: _Attr_name[1067:1077],
|
||||
132: _Attr_name[1077:1096],
|
||||
133: _Attr_name[1096:1112],
|
||||
134: _Attr_name[1112:1125],
|
||||
135: _Attr_name[1125:1133],
|
||||
136: _Attr_name[1133:1142],
|
||||
137: _Attr_name[1142:1155],
|
||||
138: _Attr_name[1155:1162],
|
||||
139: _Attr_name[1162:1171],
|
||||
140: _Attr_name[1171:1183],
|
||||
}
|
||||
|
||||
func (i Attr) String() string {
|
||||
if str, ok := _Attr_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "Attr(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Buffered reading and decoding of DWARF data streams.
|
||||
|
||||
package dwarf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Data buffer being decoded.
|
||||
type buf struct {
|
||||
dwarf *Data
|
||||
order binary.ByteOrder
|
||||
format dataFormat
|
||||
name string
|
||||
off Offset
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// Data format, other than byte order. This affects the handling of
|
||||
// certain field formats.
|
||||
type dataFormat interface {
|
||||
// DWARF version number. Zero means unknown.
|
||||
version() int
|
||||
|
||||
// 64-bit DWARF format?
|
||||
dwarf64() (dwarf64 bool, isKnown bool)
|
||||
|
||||
// Size of an address, in bytes. Zero means unknown.
|
||||
addrsize() int
|
||||
}
|
||||
|
||||
// Some parts of DWARF have no data format, e.g., abbrevs.
|
||||
type unknownFormat struct{}
|
||||
|
||||
func (u unknownFormat) version() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u unknownFormat) dwarf64() (bool, bool) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (u unknownFormat) addrsize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func makeBuf(d *Data, format dataFormat, name string, off Offset, data []byte) buf {
|
||||
return buf{d, d.order, format, name, off, data, nil}
|
||||
}
|
||||
|
||||
func (b *buf) uint8() uint8 {
|
||||
if len(b.data) < 1 {
|
||||
b.error("underflow")
|
||||
return 0
|
||||
}
|
||||
val := b.data[0]
|
||||
b.data = b.data[1:]
|
||||
b.off++
|
||||
return val
|
||||
}
|
||||
|
||||
func (b *buf) bytes(n int) []byte {
|
||||
if len(b.data) < n {
|
||||
b.error("underflow")
|
||||
return nil
|
||||
}
|
||||
data := b.data[0:n]
|
||||
b.data = b.data[n:]
|
||||
b.off += Offset(n)
|
||||
return data
|
||||
}
|
||||
|
||||
func (b *buf) skip(n int) { b.bytes(n) }
|
||||
|
||||
func (b *buf) string() string {
|
||||
i := bytes.IndexByte(b.data, 0)
|
||||
if i < 0 {
|
||||
b.error("underflow")
|
||||
return ""
|
||||
}
|
||||
|
||||
s := string(b.data[0:i])
|
||||
b.data = b.data[i+1:]
|
||||
b.off += Offset(i + 1)
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *buf) uint16() uint16 {
|
||||
a := b.bytes(2)
|
||||
if a == nil {
|
||||
return 0
|
||||
}
|
||||
return b.order.Uint16(a)
|
||||
}
|
||||
|
||||
func (b *buf) uint24() uint32 {
|
||||
a := b.bytes(3)
|
||||
if a == nil {
|
||||
return 0
|
||||
}
|
||||
if b.dwarf.bigEndian {
|
||||
return uint32(a[2]) | uint32(a[1])<<8 | uint32(a[0])<<16
|
||||
} else {
|
||||
return uint32(a[0]) | uint32(a[1])<<8 | uint32(a[2])<<16
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buf) uint32() uint32 {
|
||||
a := b.bytes(4)
|
||||
if a == nil {
|
||||
return 0
|
||||
}
|
||||
return b.order.Uint32(a)
|
||||
}
|
||||
|
||||
func (b *buf) uint64() uint64 {
|
||||
a := b.bytes(8)
|
||||
if a == nil {
|
||||
return 0
|
||||
}
|
||||
return b.order.Uint64(a)
|
||||
}
|
||||
|
||||
// Read a varint, which is 7 bits per byte, little endian.
|
||||
// the 0x80 bit means read another byte.
|
||||
func (b *buf) varint() (c uint64, bits uint) {
|
||||
for i := 0; i < len(b.data); i++ {
|
||||
byte := b.data[i]
|
||||
c |= uint64(byte&0x7F) << bits
|
||||
bits += 7
|
||||
if byte&0x80 == 0 {
|
||||
b.off += Offset(i + 1)
|
||||
b.data = b.data[i+1:]
|
||||
return c, bits
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Unsigned int is just a varint.
|
||||
func (b *buf) uint() uint64 {
|
||||
x, _ := b.varint()
|
||||
return x
|
||||
}
|
||||
|
||||
// Signed int is a sign-extended varint.
|
||||
func (b *buf) int() int64 {
|
||||
ux, bits := b.varint()
|
||||
x := int64(ux)
|
||||
if x&(1<<(bits-1)) != 0 {
|
||||
x |= -1 << bits
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Address-sized uint.
|
||||
func (b *buf) addr() uint64 {
|
||||
switch b.format.addrsize() {
|
||||
case 1:
|
||||
return uint64(b.uint8())
|
||||
case 2:
|
||||
return uint64(b.uint16())
|
||||
case 4:
|
||||
return uint64(b.uint32())
|
||||
case 8:
|
||||
return b.uint64()
|
||||
}
|
||||
b.error("unknown address size")
|
||||
return 0
|
||||
}
|
||||
|
||||
func (b *buf) unitLength() (length Offset, dwarf64 bool) {
|
||||
length = Offset(b.uint32())
|
||||
if length == 0xffffffff {
|
||||
dwarf64 = true
|
||||
length = Offset(b.uint64())
|
||||
} else if length >= 0xfffffff0 {
|
||||
b.error("unit length has reserved value")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *buf) error(s string) {
|
||||
if b.err == nil {
|
||||
b.data = nil
|
||||
b.err = DecodeError{b.name, b.off, s}
|
||||
}
|
||||
}
|
||||
|
||||
type DecodeError struct {
|
||||
Name string
|
||||
Offset Offset
|
||||
Err string
|
||||
}
|
||||
|
||||
func (e DecodeError) Error() string {
|
||||
return "decoding dwarf section " + e.Name + " at offset 0x" + strconv.FormatInt(int64(e.Offset), 16) + ": " + e.Err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Code generated by "stringer -type=Class"; DO NOT EDIT.
|
||||
|
||||
package dwarf
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ClassUnknown-0]
|
||||
_ = x[ClassAddress-1]
|
||||
_ = x[ClassBlock-2]
|
||||
_ = x[ClassConstant-3]
|
||||
_ = x[ClassExprLoc-4]
|
||||
_ = x[ClassFlag-5]
|
||||
_ = x[ClassLinePtr-6]
|
||||
_ = x[ClassLocListPtr-7]
|
||||
_ = x[ClassMacPtr-8]
|
||||
_ = x[ClassRangeListPtr-9]
|
||||
_ = x[ClassReference-10]
|
||||
_ = x[ClassReferenceSig-11]
|
||||
_ = x[ClassString-12]
|
||||
_ = x[ClassReferenceAlt-13]
|
||||
_ = x[ClassStringAlt-14]
|
||||
}
|
||||
|
||||
const _Class_name = "ClassUnknownClassAddressClassBlockClassConstantClassExprLocClassFlagClassLinePtrClassLocListPtrClassMacPtrClassRangeListPtrClassReferenceClassReferenceSigClassStringClassReferenceAltClassStringAlt"
|
||||
|
||||
var _Class_index = [...]uint8{0, 12, 24, 34, 47, 59, 68, 80, 95, 106, 123, 137, 154, 165, 182, 196}
|
||||
|
||||
func (i Class) String() string {
|
||||
if i < 0 || i >= Class(len(_Class_index)-1) {
|
||||
return "Class(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Class_name[_Class_index[i]:_Class_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Constants
|
||||
|
||||
package dwarf
|
||||
|
||||
//go:generate stringer -type Attr -trimprefix=Attr
|
||||
|
||||
// An Attr identifies the attribute type in a DWARF Entry's Field.
|
||||
type Attr uint32
|
||||
|
||||
const (
|
||||
AttrSibling Attr = 0x01
|
||||
AttrLocation Attr = 0x02
|
||||
AttrName Attr = 0x03
|
||||
AttrOrdering Attr = 0x09
|
||||
AttrByteSize Attr = 0x0B
|
||||
AttrBitOffset Attr = 0x0C
|
||||
AttrBitSize Attr = 0x0D
|
||||
AttrStmtList Attr = 0x10
|
||||
AttrLowpc Attr = 0x11
|
||||
AttrHighpc Attr = 0x12
|
||||
AttrLanguage Attr = 0x13
|
||||
AttrDiscr Attr = 0x15
|
||||
AttrDiscrValue Attr = 0x16
|
||||
AttrVisibility Attr = 0x17
|
||||
AttrImport Attr = 0x18
|
||||
AttrStringLength Attr = 0x19
|
||||
AttrCommonRef Attr = 0x1A
|
||||
AttrCompDir Attr = 0x1B
|
||||
AttrConstValue Attr = 0x1C
|
||||
AttrContainingType Attr = 0x1D
|
||||
AttrDefaultValue Attr = 0x1E
|
||||
AttrInline Attr = 0x20
|
||||
AttrIsOptional Attr = 0x21
|
||||
AttrLowerBound Attr = 0x22
|
||||
AttrProducer Attr = 0x25
|
||||
AttrPrototyped Attr = 0x27
|
||||
AttrReturnAddr Attr = 0x2A
|
||||
AttrStartScope Attr = 0x2C
|
||||
AttrStrideSize Attr = 0x2E
|
||||
AttrUpperBound Attr = 0x2F
|
||||
AttrAbstractOrigin Attr = 0x31
|
||||
AttrAccessibility Attr = 0x32
|
||||
AttrAddrClass Attr = 0x33
|
||||
AttrArtificial Attr = 0x34
|
||||
AttrBaseTypes Attr = 0x35
|
||||
AttrCalling Attr = 0x36
|
||||
AttrCount Attr = 0x37
|
||||
AttrDataMemberLoc Attr = 0x38
|
||||
AttrDeclColumn Attr = 0x39
|
||||
AttrDeclFile Attr = 0x3A
|
||||
AttrDeclLine Attr = 0x3B
|
||||
AttrDeclaration Attr = 0x3C
|
||||
AttrDiscrList Attr = 0x3D
|
||||
AttrEncoding Attr = 0x3E
|
||||
AttrExternal Attr = 0x3F
|
||||
AttrFrameBase Attr = 0x40
|
||||
AttrFriend Attr = 0x41
|
||||
AttrIdentifierCase Attr = 0x42
|
||||
AttrMacroInfo Attr = 0x43
|
||||
AttrNamelistItem Attr = 0x44
|
||||
AttrPriority Attr = 0x45
|
||||
AttrSegment Attr = 0x46
|
||||
AttrSpecification Attr = 0x47
|
||||
AttrStaticLink Attr = 0x48
|
||||
AttrType Attr = 0x49
|
||||
AttrUseLocation Attr = 0x4A
|
||||
AttrVarParam Attr = 0x4B
|
||||
AttrVirtuality Attr = 0x4C
|
||||
AttrVtableElemLoc Attr = 0x4D
|
||||
// The following are new in DWARF 3.
|
||||
AttrAllocated Attr = 0x4E
|
||||
AttrAssociated Attr = 0x4F
|
||||
AttrDataLocation Attr = 0x50
|
||||
AttrStride Attr = 0x51
|
||||
AttrEntrypc Attr = 0x52
|
||||
AttrUseUTF8 Attr = 0x53
|
||||
AttrExtension Attr = 0x54
|
||||
AttrRanges Attr = 0x55
|
||||
AttrTrampoline Attr = 0x56
|
||||
AttrCallColumn Attr = 0x57
|
||||
AttrCallFile Attr = 0x58
|
||||
AttrCallLine Attr = 0x59
|
||||
AttrDescription Attr = 0x5A
|
||||
AttrBinaryScale Attr = 0x5B
|
||||
AttrDecimalScale Attr = 0x5C
|
||||
AttrSmall Attr = 0x5D
|
||||
AttrDecimalSign Attr = 0x5E
|
||||
AttrDigitCount Attr = 0x5F
|
||||
AttrPictureString Attr = 0x60
|
||||
AttrMutable Attr = 0x61
|
||||
AttrThreadsScaled Attr = 0x62
|
||||
AttrExplicit Attr = 0x63
|
||||
AttrObjectPointer Attr = 0x64
|
||||
AttrEndianity Attr = 0x65
|
||||
AttrElemental Attr = 0x66
|
||||
AttrPure Attr = 0x67
|
||||
AttrRecursive Attr = 0x68
|
||||
// The following are new in DWARF 4.
|
||||
AttrSignature Attr = 0x69
|
||||
AttrMainSubprogram Attr = 0x6A
|
||||
AttrDataBitOffset Attr = 0x6B
|
||||
AttrConstExpr Attr = 0x6C
|
||||
AttrEnumClass Attr = 0x6D
|
||||
AttrLinkageName Attr = 0x6E
|
||||
// The following are new in DWARF 5.
|
||||
AttrStringLengthBitSize Attr = 0x6F
|
||||
AttrStringLengthByteSize Attr = 0x70
|
||||
AttrRank Attr = 0x71
|
||||
AttrStrOffsetsBase Attr = 0x72
|
||||
AttrAddrBase Attr = 0x73
|
||||
AttrRnglistsBase Attr = 0x74
|
||||
AttrDwoName Attr = 0x76
|
||||
AttrReference Attr = 0x77
|
||||
AttrRvalueReference Attr = 0x78
|
||||
AttrMacros Attr = 0x79
|
||||
AttrCallAllCalls Attr = 0x7A
|
||||
AttrCallAllSourceCalls Attr = 0x7B
|
||||
AttrCallAllTailCalls Attr = 0x7C
|
||||
AttrCallReturnPC Attr = 0x7D
|
||||
AttrCallValue Attr = 0x7E
|
||||
AttrCallOrigin Attr = 0x7F
|
||||
AttrCallParameter Attr = 0x80
|
||||
AttrCallPC Attr = 0x81
|
||||
AttrCallTailCall Attr = 0x82
|
||||
AttrCallTarget Attr = 0x83
|
||||
AttrCallTargetClobbered Attr = 0x84
|
||||
AttrCallDataLocation Attr = 0x85
|
||||
AttrCallDataValue Attr = 0x86
|
||||
AttrNoreturn Attr = 0x87
|
||||
AttrAlignment Attr = 0x88
|
||||
AttrExportSymbols Attr = 0x89
|
||||
AttrDeleted Attr = 0x8A
|
||||
AttrDefaulted Attr = 0x8B
|
||||
AttrLoclistsBase Attr = 0x8C
|
||||
)
|
||||
|
||||
func (a Attr) GoString() string {
|
||||
if str, ok := _Attr_map[a]; ok {
|
||||
return "dwarf.Attr" + str
|
||||
}
|
||||
return "dwarf." + a.String()
|
||||
}
|
||||
|
||||
// A format is a DWARF data encoding format.
|
||||
type format uint32
|
||||
|
||||
const (
|
||||
// value formats
|
||||
formAddr format = 0x01
|
||||
formDwarfBlock2 format = 0x03
|
||||
formDwarfBlock4 format = 0x04
|
||||
formData2 format = 0x05
|
||||
formData4 format = 0x06
|
||||
formData8 format = 0x07
|
||||
formString format = 0x08
|
||||
formDwarfBlock format = 0x09
|
||||
formDwarfBlock1 format = 0x0A
|
||||
formData1 format = 0x0B
|
||||
formFlag format = 0x0C
|
||||
formSdata format = 0x0D
|
||||
formStrp format = 0x0E
|
||||
formUdata format = 0x0F
|
||||
formRefAddr format = 0x10
|
||||
formRef1 format = 0x11
|
||||
formRef2 format = 0x12
|
||||
formRef4 format = 0x13
|
||||
formRef8 format = 0x14
|
||||
formRefUdata format = 0x15
|
||||
formIndirect format = 0x16
|
||||
// The following are new in DWARF 4.
|
||||
formSecOffset format = 0x17
|
||||
formExprloc format = 0x18
|
||||
formFlagPresent format = 0x19
|
||||
formRefSig8 format = 0x20
|
||||
// The following are new in DWARF 5.
|
||||
formStrx format = 0x1A
|
||||
formAddrx format = 0x1B
|
||||
formRefSup4 format = 0x1C
|
||||
formStrpSup format = 0x1D
|
||||
formData16 format = 0x1E
|
||||
formLineStrp format = 0x1F
|
||||
formImplicitConst format = 0x21
|
||||
formLoclistx format = 0x22
|
||||
formRnglistx format = 0x23
|
||||
formRefSup8 format = 0x24
|
||||
formStrx1 format = 0x25
|
||||
formStrx2 format = 0x26
|
||||
formStrx3 format = 0x27
|
||||
formStrx4 format = 0x28
|
||||
formAddrx1 format = 0x29
|
||||
formAddrx2 format = 0x2A
|
||||
formAddrx3 format = 0x2B
|
||||
formAddrx4 format = 0x2C
|
||||
// Extensions for multi-file compression (.dwz)
|
||||
// http://www.dwarfstd.org/ShowIssue.php?issue=120604.1
|
||||
formGnuRefAlt format = 0x1f20
|
||||
formGnuStrpAlt format = 0x1f21
|
||||
)
|
||||
|
||||
//go:generate stringer -type Tag -trimprefix=Tag
|
||||
|
||||
// A Tag is the classification (the type) of an Entry.
|
||||
type Tag uint32
|
||||
|
||||
const (
|
||||
TagArrayType Tag = 0x01
|
||||
TagClassType Tag = 0x02
|
||||
TagEntryPoint Tag = 0x03
|
||||
TagEnumerationType Tag = 0x04
|
||||
TagFormalParameter Tag = 0x05
|
||||
TagImportedDeclaration Tag = 0x08
|
||||
TagLabel Tag = 0x0A
|
||||
TagLexDwarfBlock Tag = 0x0B
|
||||
TagMember Tag = 0x0D
|
||||
TagPointerType Tag = 0x0F
|
||||
TagReferenceType Tag = 0x10
|
||||
TagCompileUnit Tag = 0x11
|
||||
TagStringType Tag = 0x12
|
||||
TagStructType Tag = 0x13
|
||||
TagSubroutineType Tag = 0x15
|
||||
TagTypedef Tag = 0x16
|
||||
TagUnionType Tag = 0x17
|
||||
TagUnspecifiedParameters Tag = 0x18
|
||||
TagVariant Tag = 0x19
|
||||
TagCommonDwarfBlock Tag = 0x1A
|
||||
TagCommonInclusion Tag = 0x1B
|
||||
TagInheritance Tag = 0x1C
|
||||
TagInlinedSubroutine Tag = 0x1D
|
||||
TagModule Tag = 0x1E
|
||||
TagPtrToMemberType Tag = 0x1F
|
||||
TagSetType Tag = 0x20
|
||||
TagSubrangeType Tag = 0x21
|
||||
TagWithStmt Tag = 0x22
|
||||
TagAccessDeclaration Tag = 0x23
|
||||
TagBaseType Tag = 0x24
|
||||
TagCatchDwarfBlock Tag = 0x25
|
||||
TagConstType Tag = 0x26
|
||||
TagConstant Tag = 0x27
|
||||
TagEnumerator Tag = 0x28
|
||||
TagFileType Tag = 0x29
|
||||
TagFriend Tag = 0x2A
|
||||
TagNamelist Tag = 0x2B
|
||||
TagNamelistItem Tag = 0x2C
|
||||
TagPackedType Tag = 0x2D
|
||||
TagSubprogram Tag = 0x2E
|
||||
TagTemplateTypeParameter Tag = 0x2F
|
||||
TagTemplateValueParameter Tag = 0x30
|
||||
TagThrownType Tag = 0x31
|
||||
TagTryDwarfBlock Tag = 0x32
|
||||
TagVariantPart Tag = 0x33
|
||||
TagVariable Tag = 0x34
|
||||
TagVolatileType Tag = 0x35
|
||||
// The following are new in DWARF 3.
|
||||
TagDwarfProcedure Tag = 0x36
|
||||
TagRestrictType Tag = 0x37
|
||||
TagInterfaceType Tag = 0x38
|
||||
TagNamespace Tag = 0x39
|
||||
TagImportedModule Tag = 0x3A
|
||||
TagUnspecifiedType Tag = 0x3B
|
||||
TagPartialUnit Tag = 0x3C
|
||||
TagImportedUnit Tag = 0x3D
|
||||
TagMutableType Tag = 0x3E // Later removed from DWARF.
|
||||
TagCondition Tag = 0x3F
|
||||
TagSharedType Tag = 0x40
|
||||
// The following are new in DWARF 4.
|
||||
TagTypeUnit Tag = 0x41
|
||||
TagRvalueReferenceType Tag = 0x42
|
||||
TagTemplateAlias Tag = 0x43
|
||||
// The following are new in DWARF 5.
|
||||
TagCoarrayType Tag = 0x44
|
||||
TagGenericSubrange Tag = 0x45
|
||||
TagDynamicType Tag = 0x46
|
||||
TagAtomicType Tag = 0x47
|
||||
TagCallSite Tag = 0x48
|
||||
TagCallSiteParameter Tag = 0x49
|
||||
TagSkeletonUnit Tag = 0x4A
|
||||
TagImmutableType Tag = 0x4B
|
||||
)
|
||||
|
||||
func (t Tag) GoString() string {
|
||||
if t <= TagTemplateAlias {
|
||||
return "dwarf.Tag" + t.String()
|
||||
}
|
||||
return "dwarf." + t.String()
|
||||
}
|
||||
|
||||
// Location expression operators.
|
||||
// The debug info encodes value locations like 8(R3)
|
||||
// as a sequence of these op codes.
|
||||
// This package does not implement full expressions;
|
||||
// the opPlusUconst operator is expected by the type parser.
|
||||
const (
|
||||
opAddr = 0x03 /* 1 op, const addr */
|
||||
opDeref = 0x06
|
||||
opConst1u = 0x08 /* 1 op, 1 byte const */
|
||||
opConst1s = 0x09 /* " signed */
|
||||
opConst2u = 0x0A /* 1 op, 2 byte const */
|
||||
opConst2s = 0x0B /* " signed */
|
||||
opConst4u = 0x0C /* 1 op, 4 byte const */
|
||||
opConst4s = 0x0D /* " signed */
|
||||
opConst8u = 0x0E /* 1 op, 8 byte const */
|
||||
opConst8s = 0x0F /* " signed */
|
||||
opConstu = 0x10 /* 1 op, LEB128 const */
|
||||
opConsts = 0x11 /* " signed */
|
||||
opDup = 0x12
|
||||
opDrop = 0x13
|
||||
opOver = 0x14
|
||||
opPick = 0x15 /* 1 op, 1 byte stack index */
|
||||
opSwap = 0x16
|
||||
opRot = 0x17
|
||||
opXderef = 0x18
|
||||
opAbs = 0x19
|
||||
opAnd = 0x1A
|
||||
opDiv = 0x1B
|
||||
opMinus = 0x1C
|
||||
opMod = 0x1D
|
||||
opMul = 0x1E
|
||||
opNeg = 0x1F
|
||||
opNot = 0x20
|
||||
opOr = 0x21
|
||||
opPlus = 0x22
|
||||
opPlusUconst = 0x23 /* 1 op, ULEB128 addend */
|
||||
opShl = 0x24
|
||||
opShr = 0x25
|
||||
opShra = 0x26
|
||||
opXor = 0x27
|
||||
opSkip = 0x2F /* 1 op, signed 2-byte constant */
|
||||
opBra = 0x28 /* 1 op, signed 2-byte constant */
|
||||
opEq = 0x29
|
||||
opGe = 0x2A
|
||||
opGt = 0x2B
|
||||
opLe = 0x2C
|
||||
opLt = 0x2D
|
||||
opNe = 0x2E
|
||||
opLit0 = 0x30
|
||||
/* OpLitN = OpLit0 + N for N = 0..31 */
|
||||
opReg0 = 0x50
|
||||
/* OpRegN = OpReg0 + N for N = 0..31 */
|
||||
opBreg0 = 0x70 /* 1 op, signed LEB128 constant */
|
||||
/* OpBregN = OpBreg0 + N for N = 0..31 */
|
||||
opRegx = 0x90 /* 1 op, ULEB128 register */
|
||||
opFbreg = 0x91 /* 1 op, SLEB128 offset */
|
||||
opBregx = 0x92 /* 2 op, ULEB128 reg; SLEB128 off */
|
||||
opPiece = 0x93 /* 1 op, ULEB128 size of piece */
|
||||
opDerefSize = 0x94 /* 1-byte size of data retrieved */
|
||||
opXderefSize = 0x95 /* 1-byte size of data retrieved */
|
||||
opNop = 0x96
|
||||
// The following are new in DWARF 3.
|
||||
opPushObjAddr = 0x97
|
||||
opCall2 = 0x98 /* 2-byte offset of DIE */
|
||||
opCall4 = 0x99 /* 4-byte offset of DIE */
|
||||
opCallRef = 0x9A /* 4- or 8- byte offset of DIE */
|
||||
opFormTLSAddress = 0x9B
|
||||
opCallFrameCFA = 0x9C
|
||||
opBitPiece = 0x9D
|
||||
// The following are new in DWARF 4.
|
||||
opImplicitValue = 0x9E
|
||||
opStackValue = 0x9F
|
||||
// The following a new in DWARF 5.
|
||||
opImplicitPointer = 0xA0
|
||||
opAddrx = 0xA1
|
||||
opConstx = 0xA2
|
||||
opEntryValue = 0xA3
|
||||
opConstType = 0xA4
|
||||
opRegvalType = 0xA5
|
||||
opDerefType = 0xA6
|
||||
opXderefType = 0xA7
|
||||
opConvert = 0xA8
|
||||
opReinterpret = 0xA9
|
||||
/* 0xE0-0xFF reserved for user-specific */
|
||||
)
|
||||
|
||||
// Basic type encodings -- the value for AttrEncoding in a TagBaseType Entry.
|
||||
const (
|
||||
encAddress = 0x01
|
||||
encBoolean = 0x02
|
||||
encComplexFloat = 0x03
|
||||
encFloat = 0x04
|
||||
encSigned = 0x05
|
||||
encSignedChar = 0x06
|
||||
encUnsigned = 0x07
|
||||
encUnsignedChar = 0x08
|
||||
// The following are new in DWARF 3.
|
||||
encImaginaryFloat = 0x09
|
||||
encPackedDecimal = 0x0A
|
||||
encNumericString = 0x0B
|
||||
encEdited = 0x0C
|
||||
encSignedFixed = 0x0D
|
||||
encUnsignedFixed = 0x0E
|
||||
encDecimalFloat = 0x0F
|
||||
// The following are new in DWARF 4.
|
||||
encUTF = 0x10
|
||||
// The following are new in DWARF 5.
|
||||
encUCS = 0x11
|
||||
encASCII = 0x12
|
||||
)
|
||||
|
||||
// Statement program standard opcode encodings.
|
||||
const (
|
||||
lnsCopy = 1
|
||||
lnsAdvancePC = 2
|
||||
lnsAdvanceLine = 3
|
||||
lnsSetFile = 4
|
||||
lnsSetColumn = 5
|
||||
lnsNegateStmt = 6
|
||||
lnsSetBasicBlock = 7
|
||||
lnsConstAddPC = 8
|
||||
lnsFixedAdvancePC = 9
|
||||
|
||||
// DWARF 3
|
||||
lnsSetPrologueEnd = 10
|
||||
lnsSetEpilogueBegin = 11
|
||||
lnsSetISA = 12
|
||||
)
|
||||
|
||||
// Statement program extended opcode encodings.
|
||||
const (
|
||||
lneEndSequence = 1
|
||||
lneSetAddress = 2
|
||||
lneDefineFile = 3
|
||||
|
||||
// DWARF 4
|
||||
lneSetDiscriminator = 4
|
||||
)
|
||||
|
||||
// Line table directory directory and file name entry formats.
|
||||
// These are new in DWARF 5.
|
||||
const (
|
||||
lnctPath = 0x01
|
||||
lnctDirectoryIndex = 0x02
|
||||
lnctTimestamp = 0x03
|
||||
lnctSize = 0x04
|
||||
lnctMD5 = 0x05
|
||||
)
|
||||
|
||||
// Location list entry codes.
|
||||
// These are new in DWARF 5.
|
||||
const (
|
||||
lleEndOfList = 0x00
|
||||
lleBaseAddressx = 0x01
|
||||
lleStartxEndx = 0x02
|
||||
lleStartxLength = 0x03
|
||||
lleOffsetPair = 0x04
|
||||
lleDefaultLocation = 0x05
|
||||
lleBaseAddress = 0x06
|
||||
lleStartEnd = 0x07
|
||||
lleStartLength = 0x08
|
||||
)
|
||||
|
||||
// Unit header unit type encodings.
|
||||
// These are new in DWARF 5.
|
||||
const (
|
||||
utCompile = 0x01
|
||||
utType = 0x02
|
||||
utPartial = 0x03
|
||||
utSkeleton = 0x04
|
||||
utSplitCompile = 0x05
|
||||
utSplitType = 0x06
|
||||
)
|
||||
|
||||
// Opcodes for DWARFv5 debug_rnglists section.
|
||||
const (
|
||||
rleEndOfList = 0x0
|
||||
rleBaseAddressx = 0x1
|
||||
rleStartxEndx = 0x2
|
||||
rleStartxLength = 0x3
|
||||
rleOffsetPair = 0x4
|
||||
rleBaseAddress = 0x5
|
||||
rleStartEnd = 0x6
|
||||
rleStartLength = 0x7
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,848 @@
|
||||
// Copyright 2015 The Go 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 dwarf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A LineReader reads a sequence of LineEntry structures from a DWARF
|
||||
// "line" section for a single compilation unit. LineEntries occur in
|
||||
// order of increasing PC and each LineEntry gives metadata for the
|
||||
// instructions from that LineEntry's PC to just before the next
|
||||
// LineEntry's PC. The last entry will have its EndSequence field set.
|
||||
type LineReader struct {
|
||||
buf buf
|
||||
|
||||
// Original .debug_line section data. Used by Seek.
|
||||
section []byte
|
||||
|
||||
str []byte // .debug_str
|
||||
lineStr []byte // .debug_line_str
|
||||
|
||||
// Header information
|
||||
version uint16
|
||||
addrsize int
|
||||
segmentSelectorSize int
|
||||
minInstructionLength int
|
||||
maxOpsPerInstruction int
|
||||
defaultIsStmt bool
|
||||
lineBase int
|
||||
lineRange int
|
||||
opcodeBase int
|
||||
opcodeLengths []int
|
||||
directories []string
|
||||
fileEntries []*LineFile
|
||||
|
||||
programOffset Offset // section offset of line number program
|
||||
endOffset Offset // section offset of byte following program
|
||||
|
||||
initialFileEntries int // initial length of fileEntries
|
||||
|
||||
// Current line number program state machine registers
|
||||
state LineEntry // public state
|
||||
fileIndex int // private state
|
||||
}
|
||||
|
||||
// A LineEntry is a row in a DWARF line table.
|
||||
type LineEntry struct {
|
||||
// Address is the program-counter value of a machine
|
||||
// instruction generated by the compiler. This LineEntry
|
||||
// applies to each instruction from Address to just before the
|
||||
// Address of the next LineEntry.
|
||||
Address uint64
|
||||
|
||||
// OpIndex is the index of an operation within a VLIW
|
||||
// instruction. The index of the first operation is 0. For
|
||||
// non-VLIW architectures, it will always be 0. Address and
|
||||
// OpIndex together form an operation pointer that can
|
||||
// reference any individual operation within the instruction
|
||||
// stream.
|
||||
OpIndex int
|
||||
|
||||
// File is the source file corresponding to these
|
||||
// instructions.
|
||||
File *LineFile
|
||||
|
||||
// Line is the source code line number corresponding to these
|
||||
// instructions. Lines are numbered beginning at 1. It may be
|
||||
// 0 if these instructions cannot be attributed to any source
|
||||
// line.
|
||||
Line int
|
||||
|
||||
// Column is the column number within the source line of these
|
||||
// instructions. Columns are numbered beginning at 1. It may
|
||||
// be 0 to indicate the "left edge" of the line.
|
||||
Column int
|
||||
|
||||
// IsStmt indicates that Address is a recommended breakpoint
|
||||
// location, such as the beginning of a line, statement, or a
|
||||
// distinct subpart of a statement.
|
||||
IsStmt bool
|
||||
|
||||
// BasicBlock indicates that Address is the beginning of a
|
||||
// basic block.
|
||||
BasicBlock bool
|
||||
|
||||
// PrologueEnd indicates that Address is one (of possibly
|
||||
// many) PCs where execution should be suspended for a
|
||||
// breakpoint on entry to the containing function.
|
||||
//
|
||||
// Added in DWARF 3.
|
||||
PrologueEnd bool
|
||||
|
||||
// EpilogueBegin indicates that Address is one (of possibly
|
||||
// many) PCs where execution should be suspended for a
|
||||
// breakpoint on exit from this function.
|
||||
//
|
||||
// Added in DWARF 3.
|
||||
EpilogueBegin bool
|
||||
|
||||
// ISA is the instruction set architecture for these
|
||||
// instructions. Possible ISA values should be defined by the
|
||||
// applicable ABI specification.
|
||||
//
|
||||
// Added in DWARF 3.
|
||||
ISA int
|
||||
|
||||
// Discriminator is an arbitrary integer indicating the block
|
||||
// to which these instructions belong. It serves to
|
||||
// distinguish among multiple blocks that may all have with
|
||||
// the same source file, line, and column. Where only one
|
||||
// block exists for a given source position, it should be 0.
|
||||
//
|
||||
// Added in DWARF 3.
|
||||
Discriminator int
|
||||
|
||||
// EndSequence indicates that Address is the first byte after
|
||||
// the end of a sequence of target machine instructions. If it
|
||||
// is set, only this and the Address field are meaningful. A
|
||||
// line number table may contain information for multiple
|
||||
// potentially disjoint instruction sequences. The last entry
|
||||
// in a line table should always have EndSequence set.
|
||||
EndSequence bool
|
||||
}
|
||||
|
||||
// A LineFile is a source file referenced by a DWARF line table entry.
|
||||
type LineFile struct {
|
||||
Name string
|
||||
Mtime uint64 // Implementation defined modification time, or 0 if unknown
|
||||
Length int // File length, or 0 if unknown
|
||||
}
|
||||
|
||||
// LineReader returns a new reader for the line table of compilation
|
||||
// unit cu, which must be an Entry with tag TagCompileUnit.
|
||||
//
|
||||
// If this compilation unit has no line table, it returns nil, nil.
|
||||
func (d *Data) LineReader(cu *Entry) (*LineReader, error) {
|
||||
if d.line == nil {
|
||||
// No line tables available.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get line table information from cu.
|
||||
off, ok := cu.Val(AttrStmtList).(int64)
|
||||
if !ok {
|
||||
// cu has no line table.
|
||||
return nil, nil
|
||||
}
|
||||
if off > int64(len(d.line)) {
|
||||
return nil, errors.New("AttrStmtList value out of range")
|
||||
}
|
||||
// AttrCompDir is optional if all file names are absolute. Use
|
||||
// the empty string if it's not present.
|
||||
compDir, _ := cu.Val(AttrCompDir).(string)
|
||||
|
||||
// Create the LineReader.
|
||||
u := &d.unit[d.offsetToUnit(cu.Offset)]
|
||||
buf := makeBuf(d, u, "line", Offset(off), d.line[off:])
|
||||
// The compilation directory is implicitly directories[0].
|
||||
r := LineReader{
|
||||
buf: buf,
|
||||
section: d.line,
|
||||
str: d.str,
|
||||
lineStr: d.lineStr,
|
||||
}
|
||||
|
||||
// Read the header.
|
||||
if err := r.readHeader(compDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize line reader state.
|
||||
r.Reset()
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// readHeader reads the line number program header from r.buf and sets
|
||||
// all of the header fields in r.
|
||||
func (r *LineReader) readHeader(compDir string) error {
|
||||
buf := &r.buf
|
||||
|
||||
// Read basic header fields [DWARF2 6.2.4].
|
||||
hdrOffset := buf.off
|
||||
unitLength, dwarf64 := buf.unitLength()
|
||||
r.endOffset = buf.off + unitLength
|
||||
if r.endOffset > buf.off+Offset(len(buf.data)) {
|
||||
return DecodeError{"line", hdrOffset, fmt.Sprintf("line table end %d exceeds section size %d", r.endOffset, buf.off+Offset(len(buf.data)))}
|
||||
}
|
||||
r.version = buf.uint16()
|
||||
if buf.err == nil && (r.version < 2 || r.version > 5) {
|
||||
// DWARF goes to all this effort to make new opcodes
|
||||
// backward-compatible, and then adds fields right in
|
||||
// the middle of the header in new versions, so we're
|
||||
// picky about only supporting known line table
|
||||
// versions.
|
||||
return DecodeError{"line", hdrOffset, fmt.Sprintf("unknown line table version %d", r.version)}
|
||||
}
|
||||
if r.version >= 5 {
|
||||
r.addrsize = int(buf.uint8())
|
||||
r.segmentSelectorSize = int(buf.uint8())
|
||||
} else {
|
||||
r.addrsize = buf.format.addrsize()
|
||||
r.segmentSelectorSize = 0
|
||||
}
|
||||
var headerLength Offset
|
||||
if dwarf64 {
|
||||
headerLength = Offset(buf.uint64())
|
||||
} else {
|
||||
headerLength = Offset(buf.uint32())
|
||||
}
|
||||
r.programOffset = buf.off + headerLength
|
||||
r.minInstructionLength = int(buf.uint8())
|
||||
if r.version >= 4 {
|
||||
// [DWARF4 6.2.4]
|
||||
r.maxOpsPerInstruction = int(buf.uint8())
|
||||
} else {
|
||||
r.maxOpsPerInstruction = 1
|
||||
}
|
||||
r.defaultIsStmt = buf.uint8() != 0
|
||||
r.lineBase = int(int8(buf.uint8()))
|
||||
r.lineRange = int(buf.uint8())
|
||||
|
||||
// Validate header.
|
||||
if buf.err != nil {
|
||||
return buf.err
|
||||
}
|
||||
if r.maxOpsPerInstruction == 0 {
|
||||
return DecodeError{"line", hdrOffset, "invalid maximum operations per instruction: 0"}
|
||||
}
|
||||
if r.lineRange == 0 {
|
||||
return DecodeError{"line", hdrOffset, "invalid line range: 0"}
|
||||
}
|
||||
|
||||
// Read standard opcode length table. This table starts with opcode 1.
|
||||
r.opcodeBase = int(buf.uint8())
|
||||
r.opcodeLengths = make([]int, r.opcodeBase)
|
||||
for i := 1; i < r.opcodeBase; i++ {
|
||||
r.opcodeLengths[i] = int(buf.uint8())
|
||||
}
|
||||
|
||||
// Validate opcode lengths.
|
||||
if buf.err != nil {
|
||||
return buf.err
|
||||
}
|
||||
for i, length := range r.opcodeLengths {
|
||||
if known, ok := knownOpcodeLengths[i]; ok && known != length {
|
||||
return DecodeError{"line", hdrOffset, fmt.Sprintf("opcode %d expected to have length %d, but has length %d", i, known, length)}
|
||||
}
|
||||
}
|
||||
|
||||
if r.version < 5 {
|
||||
// Read include directories table.
|
||||
r.directories = []string{compDir}
|
||||
for {
|
||||
directory := buf.string()
|
||||
if buf.err != nil {
|
||||
return buf.err
|
||||
}
|
||||
if len(directory) == 0 {
|
||||
break
|
||||
}
|
||||
if !pathIsAbs(directory) {
|
||||
// Relative paths are implicitly relative to
|
||||
// the compilation directory.
|
||||
directory = pathJoin(compDir, directory)
|
||||
}
|
||||
r.directories = append(r.directories, directory)
|
||||
}
|
||||
|
||||
// Read file name list. File numbering starts with 1,
|
||||
// so leave the first entry nil.
|
||||
r.fileEntries = make([]*LineFile, 1)
|
||||
for {
|
||||
if done, err := r.readFileEntry(); err != nil {
|
||||
return err
|
||||
} else if done {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dirFormat := r.readLNCTFormat()
|
||||
c := buf.uint()
|
||||
r.directories = make([]string, c)
|
||||
for i := range r.directories {
|
||||
dir, _, _, err := r.readLNCT(dirFormat, dwarf64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.directories[i] = dir
|
||||
}
|
||||
fileFormat := r.readLNCTFormat()
|
||||
c = buf.uint()
|
||||
r.fileEntries = make([]*LineFile, c)
|
||||
for i := range r.fileEntries {
|
||||
name, mtime, size, err := r.readLNCT(fileFormat, dwarf64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.fileEntries[i] = &LineFile{name, mtime, int(size)}
|
||||
}
|
||||
}
|
||||
|
||||
r.initialFileEntries = len(r.fileEntries)
|
||||
|
||||
return buf.err
|
||||
}
|
||||
|
||||
// lnctForm is a pair of an LNCT code and a form. This represents an
|
||||
// entry in the directory name or file name description in the DWARF 5
|
||||
// line number program header.
|
||||
type lnctForm struct {
|
||||
lnct int
|
||||
form format
|
||||
}
|
||||
|
||||
// readLNCTFormat reads an LNCT format description.
|
||||
func (r *LineReader) readLNCTFormat() []lnctForm {
|
||||
c := r.buf.uint8()
|
||||
ret := make([]lnctForm, c)
|
||||
for i := range ret {
|
||||
ret[i].lnct = int(r.buf.uint())
|
||||
ret[i].form = format(r.buf.uint())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// readLNCT reads a sequence of LNCT entries and returns path information.
|
||||
func (r *LineReader) readLNCT(s []lnctForm, dwarf64 bool) (path string, mtime uint64, size uint64, err error) {
|
||||
var dir string
|
||||
for _, lf := range s {
|
||||
var str string
|
||||
var val uint64
|
||||
switch lf.form {
|
||||
case formString:
|
||||
str = r.buf.string()
|
||||
case formStrp, formLineStrp:
|
||||
var off uint64
|
||||
if dwarf64 {
|
||||
off = r.buf.uint64()
|
||||
} else {
|
||||
off = uint64(r.buf.uint32())
|
||||
}
|
||||
if uint64(int(off)) != off {
|
||||
return "", 0, 0, DecodeError{"line", r.buf.off, "strp/line_strp offset out of range"}
|
||||
}
|
||||
var b1 buf
|
||||
if lf.form == formStrp {
|
||||
b1 = makeBuf(r.buf.dwarf, r.buf.format, "str", 0, r.str)
|
||||
} else {
|
||||
b1 = makeBuf(r.buf.dwarf, r.buf.format, "line_str", 0, r.lineStr)
|
||||
}
|
||||
b1.skip(int(off))
|
||||
str = b1.string()
|
||||
if b1.err != nil {
|
||||
return "", 0, 0, DecodeError{"line", r.buf.off, b1.err.Error()}
|
||||
}
|
||||
case formStrpSup:
|
||||
// Supplemental sections not yet supported.
|
||||
if dwarf64 {
|
||||
r.buf.uint64()
|
||||
} else {
|
||||
r.buf.uint32()
|
||||
}
|
||||
case formStrx:
|
||||
// .debug_line.dwo sections not yet supported.
|
||||
r.buf.uint()
|
||||
case formStrx1:
|
||||
r.buf.uint8()
|
||||
case formStrx2:
|
||||
r.buf.uint16()
|
||||
case formStrx3:
|
||||
r.buf.uint24()
|
||||
case formStrx4:
|
||||
r.buf.uint32()
|
||||
case formData1:
|
||||
val = uint64(r.buf.uint8())
|
||||
case formData2:
|
||||
val = uint64(r.buf.uint16())
|
||||
case formData4:
|
||||
val = uint64(r.buf.uint32())
|
||||
case formData8:
|
||||
val = r.buf.uint64()
|
||||
case formData16:
|
||||
r.buf.bytes(16)
|
||||
case formDwarfBlock:
|
||||
r.buf.bytes(int(r.buf.uint()))
|
||||
case formUdata:
|
||||
val = r.buf.uint()
|
||||
}
|
||||
|
||||
switch lf.lnct {
|
||||
case lnctPath:
|
||||
path = str
|
||||
case lnctDirectoryIndex:
|
||||
if val >= uint64(len(r.directories)) {
|
||||
return "", 0, 0, DecodeError{"line", r.buf.off, "directory index out of range"}
|
||||
}
|
||||
dir = r.directories[val]
|
||||
case lnctTimestamp:
|
||||
mtime = val
|
||||
case lnctSize:
|
||||
size = val
|
||||
case lnctMD5:
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
|
||||
if dir != "" && path != "" {
|
||||
path = pathJoin(dir, path)
|
||||
}
|
||||
|
||||
return path, mtime, size, nil
|
||||
}
|
||||
|
||||
// readFileEntry reads a file entry from either the header or a
|
||||
// DW_LNE_define_file extended opcode and adds it to r.fileEntries. A
|
||||
// true return value indicates that there are no more entries to read.
|
||||
func (r *LineReader) readFileEntry() (bool, error) {
|
||||
name := r.buf.string()
|
||||
if r.buf.err != nil {
|
||||
return false, r.buf.err
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
off := r.buf.off
|
||||
dirIndex := int(r.buf.uint())
|
||||
if !pathIsAbs(name) {
|
||||
if dirIndex >= len(r.directories) {
|
||||
return false, DecodeError{"line", off, "directory index too large"}
|
||||
}
|
||||
name = pathJoin(r.directories[dirIndex], name)
|
||||
}
|
||||
mtime := r.buf.uint()
|
||||
length := int(r.buf.uint())
|
||||
|
||||
// If this is a dynamically added path and the cursor was
|
||||
// backed up, we may have already added this entry. Avoid
|
||||
// updating existing line table entries in this case. This
|
||||
// avoids an allocation and potential racy access to the slice
|
||||
// backing store if the user called Files.
|
||||
if len(r.fileEntries) < cap(r.fileEntries) {
|
||||
fe := r.fileEntries[:len(r.fileEntries)+1]
|
||||
if fe[len(fe)-1] != nil {
|
||||
// We already processed this addition.
|
||||
r.fileEntries = fe
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
r.fileEntries = append(r.fileEntries, &LineFile{name, mtime, length})
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// updateFile updates r.state.File after r.fileIndex has
|
||||
// changed or r.fileEntries has changed.
|
||||
func (r *LineReader) updateFile() {
|
||||
if r.fileIndex < len(r.fileEntries) {
|
||||
r.state.File = r.fileEntries[r.fileIndex]
|
||||
} else {
|
||||
r.state.File = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Next sets *entry to the next row in this line table and moves to
|
||||
// the next row. If there are no more entries and the line table is
|
||||
// properly terminated, it returns io.EOF.
|
||||
//
|
||||
// Rows are always in order of increasing entry.Address, but
|
||||
// entry.Line may go forward or backward.
|
||||
func (r *LineReader) Next(entry *LineEntry) error {
|
||||
if r.buf.err != nil {
|
||||
return r.buf.err
|
||||
}
|
||||
|
||||
// Execute opcodes until we reach an opcode that emits a line
|
||||
// table entry.
|
||||
for {
|
||||
if len(r.buf.data) == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
emit := r.step(entry)
|
||||
if r.buf.err != nil {
|
||||
return r.buf.err
|
||||
}
|
||||
if emit {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// knownOpcodeLengths gives the opcode lengths (in varint arguments)
|
||||
// of known standard opcodes.
|
||||
var knownOpcodeLengths = map[int]int{
|
||||
lnsCopy: 0,
|
||||
lnsAdvancePC: 1,
|
||||
lnsAdvanceLine: 1,
|
||||
lnsSetFile: 1,
|
||||
lnsNegateStmt: 0,
|
||||
lnsSetBasicBlock: 0,
|
||||
lnsConstAddPC: 0,
|
||||
lnsSetPrologueEnd: 0,
|
||||
lnsSetEpilogueBegin: 0,
|
||||
lnsSetISA: 1,
|
||||
// lnsFixedAdvancePC takes a uint8 rather than a varint; it's
|
||||
// unclear what length the header is supposed to claim, so
|
||||
// ignore it.
|
||||
}
|
||||
|
||||
// step processes the next opcode and updates r.state. If the opcode
|
||||
// emits a row in the line table, this updates *entry and returns
|
||||
// true.
|
||||
func (r *LineReader) step(entry *LineEntry) bool {
|
||||
opcode := int(r.buf.uint8())
|
||||
|
||||
if opcode >= r.opcodeBase {
|
||||
// Special opcode [DWARF2 6.2.5.1, DWARF4 6.2.5.1]
|
||||
adjustedOpcode := opcode - r.opcodeBase
|
||||
r.advancePC(adjustedOpcode / r.lineRange)
|
||||
lineDelta := r.lineBase + adjustedOpcode%r.lineRange
|
||||
r.state.Line += lineDelta
|
||||
goto emit
|
||||
}
|
||||
|
||||
switch opcode {
|
||||
case 0:
|
||||
// Extended opcode [DWARF2 6.2.5.3]
|
||||
length := Offset(r.buf.uint())
|
||||
startOff := r.buf.off
|
||||
opcode := r.buf.uint8()
|
||||
|
||||
switch opcode {
|
||||
case lneEndSequence:
|
||||
r.state.EndSequence = true
|
||||
*entry = r.state
|
||||
r.resetState()
|
||||
|
||||
case lneSetAddress:
|
||||
switch r.addrsize {
|
||||
case 1:
|
||||
r.state.Address = uint64(r.buf.uint8())
|
||||
case 2:
|
||||
r.state.Address = uint64(r.buf.uint16())
|
||||
case 4:
|
||||
r.state.Address = uint64(r.buf.uint32())
|
||||
case 8:
|
||||
r.state.Address = r.buf.uint64()
|
||||
default:
|
||||
r.buf.error("unknown address size")
|
||||
}
|
||||
|
||||
case lneDefineFile:
|
||||
if done, err := r.readFileEntry(); err != nil {
|
||||
r.buf.err = err
|
||||
return false
|
||||
} else if done {
|
||||
r.buf.err = DecodeError{"line", startOff, "malformed DW_LNE_define_file operation"}
|
||||
return false
|
||||
}
|
||||
r.updateFile()
|
||||
|
||||
case lneSetDiscriminator:
|
||||
// [DWARF4 6.2.5.3]
|
||||
r.state.Discriminator = int(r.buf.uint())
|
||||
}
|
||||
|
||||
r.buf.skip(int(startOff + length - r.buf.off))
|
||||
|
||||
if opcode == lneEndSequence {
|
||||
return true
|
||||
}
|
||||
|
||||
// Standard opcodes [DWARF2 6.2.5.2]
|
||||
case lnsCopy:
|
||||
goto emit
|
||||
|
||||
case lnsAdvancePC:
|
||||
r.advancePC(int(r.buf.uint()))
|
||||
|
||||
case lnsAdvanceLine:
|
||||
r.state.Line += int(r.buf.int())
|
||||
|
||||
case lnsSetFile:
|
||||
r.fileIndex = int(r.buf.uint())
|
||||
r.updateFile()
|
||||
|
||||
case lnsSetColumn:
|
||||
r.state.Column = int(r.buf.uint())
|
||||
|
||||
case lnsNegateStmt:
|
||||
r.state.IsStmt = !r.state.IsStmt
|
||||
|
||||
case lnsSetBasicBlock:
|
||||
r.state.BasicBlock = true
|
||||
|
||||
case lnsConstAddPC:
|
||||
r.advancePC((255 - r.opcodeBase) / r.lineRange)
|
||||
|
||||
case lnsFixedAdvancePC:
|
||||
r.state.Address += uint64(r.buf.uint16())
|
||||
|
||||
// DWARF3 standard opcodes [DWARF3 6.2.5.2]
|
||||
case lnsSetPrologueEnd:
|
||||
r.state.PrologueEnd = true
|
||||
|
||||
case lnsSetEpilogueBegin:
|
||||
r.state.EpilogueBegin = true
|
||||
|
||||
case lnsSetISA:
|
||||
r.state.ISA = int(r.buf.uint())
|
||||
|
||||
default:
|
||||
// Unhandled standard opcode. Skip the number of
|
||||
// arguments that the prologue says this opcode has.
|
||||
for i := 0; i < r.opcodeLengths[opcode]; i++ {
|
||||
r.buf.uint()
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
emit:
|
||||
*entry = r.state
|
||||
r.state.BasicBlock = false
|
||||
r.state.PrologueEnd = false
|
||||
r.state.EpilogueBegin = false
|
||||
r.state.Discriminator = 0
|
||||
return true
|
||||
}
|
||||
|
||||
// advancePC advances "operation pointer" (the combination of Address
|
||||
// and OpIndex) in r.state by opAdvance steps.
|
||||
func (r *LineReader) advancePC(opAdvance int) {
|
||||
opIndex := r.state.OpIndex + opAdvance
|
||||
r.state.Address += uint64(r.minInstructionLength * (opIndex / r.maxOpsPerInstruction))
|
||||
r.state.OpIndex = opIndex % r.maxOpsPerInstruction
|
||||
}
|
||||
|
||||
// A LineReaderPos represents a position in a line table.
|
||||
type LineReaderPos struct {
|
||||
// off is the current offset in the DWARF line section.
|
||||
off Offset
|
||||
// numFileEntries is the length of fileEntries.
|
||||
numFileEntries int
|
||||
// state and fileIndex are the statement machine state at
|
||||
// offset off.
|
||||
state LineEntry
|
||||
fileIndex int
|
||||
}
|
||||
|
||||
// Tell returns the current position in the line table.
|
||||
func (r *LineReader) Tell() LineReaderPos {
|
||||
return LineReaderPos{r.buf.off, len(r.fileEntries), r.state, r.fileIndex}
|
||||
}
|
||||
|
||||
// Seek restores the line table reader to a position returned by Tell.
|
||||
//
|
||||
// The argument pos must have been returned by a call to Tell on this
|
||||
// line table.
|
||||
func (r *LineReader) Seek(pos LineReaderPos) {
|
||||
r.buf.off = pos.off
|
||||
r.buf.data = r.section[r.buf.off:r.endOffset]
|
||||
r.fileEntries = r.fileEntries[:pos.numFileEntries]
|
||||
r.state = pos.state
|
||||
r.fileIndex = pos.fileIndex
|
||||
}
|
||||
|
||||
// Reset repositions the line table reader at the beginning of the
|
||||
// line table.
|
||||
func (r *LineReader) Reset() {
|
||||
// Reset buffer to the line number program offset.
|
||||
r.buf.off = r.programOffset
|
||||
r.buf.data = r.section[r.buf.off:r.endOffset]
|
||||
|
||||
// Reset file entries list.
|
||||
r.fileEntries = r.fileEntries[:r.initialFileEntries]
|
||||
|
||||
// Reset line number program state.
|
||||
r.resetState()
|
||||
}
|
||||
|
||||
// resetState resets r.state to its default values
|
||||
func (r *LineReader) resetState() {
|
||||
// Reset the state machine registers to the defaults given in
|
||||
// [DWARF4 6.2.2].
|
||||
r.state = LineEntry{
|
||||
Address: 0,
|
||||
OpIndex: 0,
|
||||
File: nil,
|
||||
Line: 1,
|
||||
Column: 0,
|
||||
IsStmt: r.defaultIsStmt,
|
||||
BasicBlock: false,
|
||||
PrologueEnd: false,
|
||||
EpilogueBegin: false,
|
||||
ISA: 0,
|
||||
Discriminator: 0,
|
||||
}
|
||||
r.fileIndex = 1
|
||||
r.updateFile()
|
||||
}
|
||||
|
||||
// Files returns the file name table of this compilation unit as of
|
||||
// the current position in the line table. The file name table may be
|
||||
// referenced from attributes in this compilation unit such as
|
||||
// AttrDeclFile.
|
||||
//
|
||||
// Entry 0 is always nil, since file index 0 represents "no file".
|
||||
//
|
||||
// The file name table of a compilation unit is not fixed. Files
|
||||
// returns the file table as of the current position in the line
|
||||
// table. This may contain more entries than the file table at an
|
||||
// earlier position in the line table, though existing entries never
|
||||
// change.
|
||||
func (r *LineReader) Files() []*LineFile {
|
||||
return r.fileEntries
|
||||
}
|
||||
|
||||
// ErrUnknownPC is the error returned by LineReader.ScanPC when the
|
||||
// seek PC is not covered by any entry in the line table.
|
||||
var ErrUnknownPC = errors.New("ErrUnknownPC")
|
||||
|
||||
// SeekPC sets *entry to the LineEntry that includes pc and positions
|
||||
// the reader on the next entry in the line table. If necessary, this
|
||||
// will seek backwards to find pc.
|
||||
//
|
||||
// If pc is not covered by any entry in this line table, SeekPC
|
||||
// returns ErrUnknownPC. In this case, *entry and the final seek
|
||||
// position are unspecified.
|
||||
//
|
||||
// Note that DWARF line tables only permit sequential, forward scans.
|
||||
// Hence, in the worst case, this takes time linear in the size of the
|
||||
// line table. If the caller wishes to do repeated fast PC lookups, it
|
||||
// should build an appropriate index of the line table.
|
||||
func (r *LineReader) SeekPC(pc uint64, entry *LineEntry) error {
|
||||
if err := r.Next(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.Address > pc {
|
||||
// We're too far. Start at the beginning of the table.
|
||||
r.Reset()
|
||||
if err := r.Next(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.Address > pc {
|
||||
// The whole table starts after pc.
|
||||
r.Reset()
|
||||
return ErrUnknownPC
|
||||
}
|
||||
}
|
||||
|
||||
// Scan until we pass pc, then back up one.
|
||||
for {
|
||||
var next LineEntry
|
||||
pos := r.Tell()
|
||||
if err := r.Next(&next); err != nil {
|
||||
if err == io.EOF {
|
||||
return ErrUnknownPC
|
||||
}
|
||||
return err
|
||||
}
|
||||
if next.Address > pc {
|
||||
if entry.EndSequence {
|
||||
// pc is in a hole in the table.
|
||||
return ErrUnknownPC
|
||||
}
|
||||
// entry is the desired entry. Back up the
|
||||
// cursor to "next" and return success.
|
||||
r.Seek(pos)
|
||||
return nil
|
||||
}
|
||||
*entry = next
|
||||
}
|
||||
}
|
||||
|
||||
// pathIsAbs reports whether path is an absolute path (or "full path
|
||||
// name" in DWARF parlance). This is in "whatever form makes sense for
|
||||
// the host system", so this accepts both UNIX-style and DOS-style
|
||||
// absolute paths. We avoid the filepath package because we want this
|
||||
// to behave the same regardless of our host system and because we
|
||||
// don't know what system the paths came from.
|
||||
func pathIsAbs(path string) bool {
|
||||
_, path = splitDrive(path)
|
||||
return len(path) > 0 && (path[0] == '/' || path[0] == '\\')
|
||||
}
|
||||
|
||||
// pathJoin joins dirname and filename. filename must be relative.
|
||||
// DWARF paths can be UNIX-style or DOS-style, so this handles both.
|
||||
func pathJoin(dirname, filename string) string {
|
||||
if len(dirname) == 0 {
|
||||
return filename
|
||||
}
|
||||
// dirname should be absolute, which means we can determine
|
||||
// whether it's a DOS path reasonably reliably by looking for
|
||||
// a drive letter or UNC path.
|
||||
drive, dirname := splitDrive(dirname)
|
||||
if drive == "" {
|
||||
// UNIX-style path.
|
||||
return path.Join(dirname, filename)
|
||||
}
|
||||
// DOS-style path.
|
||||
drive2, filename := splitDrive(filename)
|
||||
if drive2 != "" {
|
||||
if !strings.EqualFold(drive, drive2) {
|
||||
// Different drives. There's not much we can
|
||||
// do here, so just ignore the directory.
|
||||
return drive2 + filename
|
||||
}
|
||||
// Drives are the same. Ignore drive on filename.
|
||||
}
|
||||
if !(strings.HasSuffix(dirname, "/") || strings.HasSuffix(dirname, `\`)) && dirname != "" {
|
||||
sep := `\`
|
||||
if strings.HasPrefix(dirname, "/") {
|
||||
sep = `/`
|
||||
}
|
||||
dirname += sep
|
||||
}
|
||||
return drive + dirname + filename
|
||||
}
|
||||
|
||||
// splitDrive splits the DOS drive letter or UNC share point from
|
||||
// path, if any. path == drive + rest
|
||||
func splitDrive(path string) (drive, rest string) {
|
||||
if len(path) >= 2 && path[1] == ':' {
|
||||
if c := path[0]; 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
|
||||
return path[:2], path[2:]
|
||||
}
|
||||
}
|
||||
if len(path) > 3 && (path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/') {
|
||||
// Normalize the path so we can search for just \ below.
|
||||
npath := strings.Replace(path, "/", `\`, -1)
|
||||
// Get the host part, which must be non-empty.
|
||||
slash1 := strings.IndexByte(npath[2:], '\\') + 2
|
||||
if slash1 > 2 {
|
||||
// Get the mount-point part, which must be non-empty.
|
||||
slash2 := strings.IndexByte(npath[slash1+1:], '\\') + slash1 + 1
|
||||
if slash2 > slash1 {
|
||||
return path[:slash2], path[slash2:]
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", path
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright 2009 The Go 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 dwarf provides access to DWARF debugging information loaded from
|
||||
// executable files, as defined in the DWARF 2.0 Standard at
|
||||
// http://dwarfstd.org/doc/dwarf-2.0.0.pdf
|
||||
package dwarf
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Data represents the DWARF debugging information
|
||||
// loaded from an executable file (for example, an ELF or Mach-O executable).
|
||||
type Data struct {
|
||||
// raw data
|
||||
abbrev []byte
|
||||
aranges []byte
|
||||
frame []byte
|
||||
info []byte
|
||||
line []byte
|
||||
pubnames []byte
|
||||
ranges []byte
|
||||
str []byte
|
||||
|
||||
// New sections added in DWARF 5.
|
||||
addr *debugAddr
|
||||
lineStr []byte
|
||||
strOffsets []byte
|
||||
rngLists *rngLists
|
||||
|
||||
// parsed data
|
||||
abbrevCache map[uint64]abbrevTable
|
||||
bigEndian bool
|
||||
order binary.ByteOrder
|
||||
typeCache map[Offset]Type
|
||||
typeSigs map[uint64]*typeUnit
|
||||
unit []unit
|
||||
}
|
||||
|
||||
// rngLists represents the contents of a debug_rnglists section (DWARFv5).
|
||||
type rngLists struct {
|
||||
is64 bool
|
||||
asize uint8
|
||||
data []byte
|
||||
ver uint16
|
||||
}
|
||||
|
||||
// debugAddr represents the contents of a debug_addr section (DWARFv5).
|
||||
type debugAddr struct {
|
||||
is64 bool
|
||||
asize uint8
|
||||
data []byte
|
||||
}
|
||||
|
||||
var errSegmentSelector = errors.New("non-zero segment_selector size not supported")
|
||||
|
||||
// New returns a new Data object initialized from the given parameters.
|
||||
// Rather than calling this function directly, clients should typically use
|
||||
// the DWARF method of the File type of the appropriate package debug/elf,
|
||||
// debug/macho, or debug/pe.
|
||||
//
|
||||
// The []byte arguments are the data from the corresponding debug section
|
||||
// in the object file; for example, for an ELF object, abbrev is the contents of
|
||||
// the ".debug_abbrev" section.
|
||||
func New(abbrev, aranges, frame, info, line, pubnames, ranges, str []byte) (*Data, error) {
|
||||
d := &Data{
|
||||
abbrev: abbrev,
|
||||
aranges: aranges,
|
||||
frame: frame,
|
||||
info: info,
|
||||
line: line,
|
||||
pubnames: pubnames,
|
||||
ranges: ranges,
|
||||
str: str,
|
||||
abbrevCache: make(map[uint64]abbrevTable),
|
||||
typeCache: make(map[Offset]Type),
|
||||
typeSigs: make(map[uint64]*typeUnit),
|
||||
}
|
||||
|
||||
// Sniff .debug_info to figure out byte order.
|
||||
// 32-bit DWARF: 4 byte length, 2 byte version.
|
||||
// 64-bit DWARf: 4 bytes of 0xff, 8 byte length, 2 byte version.
|
||||
if len(d.info) < 6 {
|
||||
return nil, DecodeError{"info", Offset(len(d.info)), "too short"}
|
||||
}
|
||||
offset := 4
|
||||
if d.info[0] == 0xff && d.info[1] == 0xff && d.info[2] == 0xff && d.info[3] == 0xff {
|
||||
if len(d.info) < 14 {
|
||||
return nil, DecodeError{"info", Offset(len(d.info)), "too short"}
|
||||
}
|
||||
offset = 12
|
||||
}
|
||||
// Fetch the version, a tiny 16-bit number (1, 2, 3, 4, 5).
|
||||
x, y := d.info[offset], d.info[offset+1]
|
||||
switch {
|
||||
case x == 0 && y == 0:
|
||||
return nil, DecodeError{"info", 4, "unsupported version 0"}
|
||||
case x == 0:
|
||||
d.bigEndian = true
|
||||
d.order = binary.BigEndian
|
||||
case y == 0:
|
||||
d.bigEndian = false
|
||||
d.order = binary.LittleEndian
|
||||
default:
|
||||
return nil, DecodeError{"info", 4, "cannot determine byte order"}
|
||||
}
|
||||
|
||||
u, err := d.parseUnits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.unit = u
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// AddTypes will add one .debug_types section to the DWARF data. A
|
||||
// typical object with DWARF version 4 debug info will have multiple
|
||||
// .debug_types sections. The name is used for error reporting only,
|
||||
// and serves to distinguish one .debug_types section from another.
|
||||
func (d *Data) AddTypes(name string, types []byte) error {
|
||||
return d.parseTypes(name, types)
|
||||
}
|
||||
|
||||
// AddSection adds another DWARF section by name. The name should be a
|
||||
// DWARF section name such as ".debug_addr", ".debug_str_offsets", and
|
||||
// so forth. This approach is used for new DWARF sections added in
|
||||
// DWARF 5 and later.
|
||||
func (d *Data) AddSection(name string, contents []byte) error {
|
||||
var err error
|
||||
switch name {
|
||||
case ".debug_addr":
|
||||
d.addr, err = d.parseAddrHeader(contents)
|
||||
case ".debug_line_str":
|
||||
d.lineStr = contents
|
||||
case ".debug_str_offsets":
|
||||
d.strOffsets = contents
|
||||
case ".debug_rnglists":
|
||||
d.rngLists, err = d.parseRngListsHeader(contents)
|
||||
}
|
||||
// Just ignore names that we don't yet support.
|
||||
return err
|
||||
}
|
||||
|
||||
// parseRngListsHeader reads the header of a debug_rnglists section, see
|
||||
// DWARFv5 section 7.28 (page 242).
|
||||
func (d *Data) parseRngListsHeader(bytes []byte) (*rngLists, error) {
|
||||
rngLists := &rngLists{data: bytes}
|
||||
|
||||
buf := makeBuf(d, unknownFormat{}, "rnglists", 0, bytes)
|
||||
_, rngLists.is64 = buf.unitLength()
|
||||
|
||||
rngLists.ver = buf.uint16() // version
|
||||
|
||||
rngLists.asize = buf.uint8()
|
||||
segsize := buf.uint8()
|
||||
if segsize != 0 {
|
||||
return nil, errSegmentSelector
|
||||
}
|
||||
|
||||
// Header fields not read: offset_entry_count, offset table
|
||||
|
||||
return rngLists, nil
|
||||
}
|
||||
|
||||
func (rngLists *rngLists) version() int {
|
||||
return int(rngLists.ver)
|
||||
}
|
||||
|
||||
func (rngLists *rngLists) dwarf64() (bool, bool) {
|
||||
return rngLists.is64, true
|
||||
}
|
||||
|
||||
func (rngLists *rngLists) addrsize() int {
|
||||
return int(rngLists.asize)
|
||||
}
|
||||
|
||||
// parseAddrHeader reads the header of a debug_addr section, see DWARFv5
|
||||
// section 7.27 (page 241).
|
||||
func (d *Data) parseAddrHeader(bytes []byte) (*debugAddr, error) {
|
||||
addr := &debugAddr{data: bytes}
|
||||
|
||||
buf := makeBuf(d, unknownFormat{}, "addr", 0, bytes)
|
||||
_, addr.is64 = buf.unitLength()
|
||||
|
||||
addr.asize = buf.uint8()
|
||||
segsize := buf.uint8()
|
||||
if segsize != 0 {
|
||||
return nil, errSegmentSelector
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
func (addr *debugAddr) version() int {
|
||||
return 5
|
||||
}
|
||||
|
||||
func (addr *debugAddr) dwarf64() (bool, bool) {
|
||||
return addr.is64, true
|
||||
}
|
||||
|
||||
func (addr *debugAddr) addrsize() int {
|
||||
return int(addr.asize)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Code generated by "stringer -type Tag -trimprefix=Tag"; DO NOT EDIT.
|
||||
|
||||
package dwarf
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TagArrayType-1]
|
||||
_ = x[TagClassType-2]
|
||||
_ = x[TagEntryPoint-3]
|
||||
_ = x[TagEnumerationType-4]
|
||||
_ = x[TagFormalParameter-5]
|
||||
_ = x[TagImportedDeclaration-8]
|
||||
_ = x[TagLabel-10]
|
||||
_ = x[TagLexDwarfBlock-11]
|
||||
_ = x[TagMember-13]
|
||||
_ = x[TagPointerType-15]
|
||||
_ = x[TagReferenceType-16]
|
||||
_ = x[TagCompileUnit-17]
|
||||
_ = x[TagStringType-18]
|
||||
_ = x[TagStructType-19]
|
||||
_ = x[TagSubroutineType-21]
|
||||
_ = x[TagTypedef-22]
|
||||
_ = x[TagUnionType-23]
|
||||
_ = x[TagUnspecifiedParameters-24]
|
||||
_ = x[TagVariant-25]
|
||||
_ = x[TagCommonDwarfBlock-26]
|
||||
_ = x[TagCommonInclusion-27]
|
||||
_ = x[TagInheritance-28]
|
||||
_ = x[TagInlinedSubroutine-29]
|
||||
_ = x[TagModule-30]
|
||||
_ = x[TagPtrToMemberType-31]
|
||||
_ = x[TagSetType-32]
|
||||
_ = x[TagSubrangeType-33]
|
||||
_ = x[TagWithStmt-34]
|
||||
_ = x[TagAccessDeclaration-35]
|
||||
_ = x[TagBaseType-36]
|
||||
_ = x[TagCatchDwarfBlock-37]
|
||||
_ = x[TagConstType-38]
|
||||
_ = x[TagConstant-39]
|
||||
_ = x[TagEnumerator-40]
|
||||
_ = x[TagFileType-41]
|
||||
_ = x[TagFriend-42]
|
||||
_ = x[TagNamelist-43]
|
||||
_ = x[TagNamelistItem-44]
|
||||
_ = x[TagPackedType-45]
|
||||
_ = x[TagSubprogram-46]
|
||||
_ = x[TagTemplateTypeParameter-47]
|
||||
_ = x[TagTemplateValueParameter-48]
|
||||
_ = x[TagThrownType-49]
|
||||
_ = x[TagTryDwarfBlock-50]
|
||||
_ = x[TagVariantPart-51]
|
||||
_ = x[TagVariable-52]
|
||||
_ = x[TagVolatileType-53]
|
||||
_ = x[TagDwarfProcedure-54]
|
||||
_ = x[TagRestrictType-55]
|
||||
_ = x[TagInterfaceType-56]
|
||||
_ = x[TagNamespace-57]
|
||||
_ = x[TagImportedModule-58]
|
||||
_ = x[TagUnspecifiedType-59]
|
||||
_ = x[TagPartialUnit-60]
|
||||
_ = x[TagImportedUnit-61]
|
||||
_ = x[TagMutableType-62]
|
||||
_ = x[TagCondition-63]
|
||||
_ = x[TagSharedType-64]
|
||||
_ = x[TagTypeUnit-65]
|
||||
_ = x[TagRvalueReferenceType-66]
|
||||
_ = x[TagTemplateAlias-67]
|
||||
_ = x[TagCoarrayType-68]
|
||||
_ = x[TagGenericSubrange-69]
|
||||
_ = x[TagDynamicType-70]
|
||||
_ = x[TagAtomicType-71]
|
||||
_ = x[TagCallSite-72]
|
||||
_ = x[TagCallSiteParameter-73]
|
||||
_ = x[TagSkeletonUnit-74]
|
||||
_ = x[TagImmutableType-75]
|
||||
}
|
||||
|
||||
const (
|
||||
_Tag_name_0 = "ArrayTypeClassTypeEntryPointEnumerationTypeFormalParameter"
|
||||
_Tag_name_1 = "ImportedDeclaration"
|
||||
_Tag_name_2 = "LabelLexDwarfBlock"
|
||||
_Tag_name_3 = "Member"
|
||||
_Tag_name_4 = "PointerTypeReferenceTypeCompileUnitStringTypeStructType"
|
||||
_Tag_name_5 = "SubroutineTypeTypedefUnionTypeUnspecifiedParametersVariantCommonDwarfBlockCommonInclusionInheritanceInlinedSubroutineModulePtrToMemberTypeSetTypeSubrangeTypeWithStmtAccessDeclarationBaseTypeCatchDwarfBlockConstTypeConstantEnumeratorFileTypeFriendNamelistNamelistItemPackedTypeSubprogramTemplateTypeParameterTemplateValueParameterThrownTypeTryDwarfBlockVariantPartVariableVolatileTypeDwarfProcedureRestrictTypeInterfaceTypeNamespaceImportedModuleUnspecifiedTypePartialUnitImportedUnitMutableTypeConditionSharedTypeTypeUnitRvalueReferenceTypeTemplateAliasCoarrayTypeGenericSubrangeDynamicTypeAtomicTypeCallSiteCallSiteParameterSkeletonUnitImmutableType"
|
||||
)
|
||||
|
||||
var (
|
||||
_Tag_index_0 = [...]uint8{0, 9, 18, 28, 43, 58}
|
||||
_Tag_index_2 = [...]uint8{0, 5, 18}
|
||||
_Tag_index_4 = [...]uint8{0, 11, 24, 35, 45, 55}
|
||||
_Tag_index_5 = [...]uint16{0, 14, 21, 30, 51, 58, 74, 89, 100, 117, 123, 138, 145, 157, 165, 182, 190, 205, 214, 222, 232, 240, 246, 254, 266, 276, 286, 307, 329, 339, 352, 363, 371, 383, 397, 409, 422, 431, 445, 460, 471, 483, 494, 503, 513, 521, 540, 553, 564, 579, 590, 600, 608, 625, 637, 650}
|
||||
)
|
||||
|
||||
func (i Tag) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 5:
|
||||
i -= 1
|
||||
return _Tag_name_0[_Tag_index_0[i]:_Tag_index_0[i+1]]
|
||||
case i == 8:
|
||||
return _Tag_name_1
|
||||
case 10 <= i && i <= 11:
|
||||
i -= 10
|
||||
return _Tag_name_2[_Tag_index_2[i]:_Tag_index_2[i+1]]
|
||||
case i == 13:
|
||||
return _Tag_name_3
|
||||
case 15 <= i && i <= 19:
|
||||
i -= 15
|
||||
return _Tag_name_4[_Tag_index_4[i]:_Tag_index_4[i+1]]
|
||||
case 21 <= i && i <= 75:
|
||||
i -= 21
|
||||
return _Tag_name_5[_Tag_index_5[i]:_Tag_index_5[i+1]]
|
||||
default:
|
||||
return "Tag(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,751 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// DWARF type information structures.
|
||||
// The format is heavily biased toward C, but for simplicity
|
||||
// the String methods use a pseudo-Go syntax.
|
||||
|
||||
package dwarf
|
||||
|
||||
import "strconv"
|
||||
|
||||
// A Type conventionally represents a pointer to any of the
|
||||
// specific Type structures (CharType, StructType, etc.).
|
||||
type Type interface {
|
||||
Common() *CommonType
|
||||
String() string
|
||||
Size() int64
|
||||
}
|
||||
|
||||
// A CommonType holds fields common to multiple types.
|
||||
// If a field is not known or not applicable for a given type,
|
||||
// the zero value is used.
|
||||
type CommonType struct {
|
||||
ByteSize int64 // size of value of this type, in bytes
|
||||
Name string // name that can be used to refer to type
|
||||
}
|
||||
|
||||
func (c *CommonType) Common() *CommonType { return c }
|
||||
|
||||
func (c *CommonType) Size() int64 { return c.ByteSize }
|
||||
|
||||
// Basic types
|
||||
|
||||
// A BasicType holds fields common to all basic types.
|
||||
type BasicType struct {
|
||||
CommonType
|
||||
BitSize int64
|
||||
BitOffset int64
|
||||
}
|
||||
|
||||
func (b *BasicType) Basic() *BasicType { return b }
|
||||
|
||||
func (t *BasicType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
// A CharType represents a signed character type.
|
||||
type CharType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A UcharType represents an unsigned character type.
|
||||
type UcharType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// An IntType represents a signed integer type.
|
||||
type IntType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A UintType represents an unsigned integer type.
|
||||
type UintType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A FloatType represents a floating point type.
|
||||
type FloatType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A ComplexType represents a complex floating point type.
|
||||
type ComplexType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A BoolType represents a boolean type.
|
||||
type BoolType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// An AddrType represents a machine address type.
|
||||
type AddrType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// An UnspecifiedType represents an implicit, unknown, ambiguous or nonexistent type.
|
||||
type UnspecifiedType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// qualifiers
|
||||
|
||||
// A QualType represents a type that has the C/C++ "const", "restrict", or "volatile" qualifier.
|
||||
type QualType struct {
|
||||
CommonType
|
||||
Qual string
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (t *QualType) String() string { return t.Qual + " " + t.Type.String() }
|
||||
|
||||
func (t *QualType) Size() int64 { return t.Type.Size() }
|
||||
|
||||
// An ArrayType represents a fixed size array type.
|
||||
type ArrayType struct {
|
||||
CommonType
|
||||
Type Type
|
||||
StrideBitSize int64 // if > 0, number of bits to hold each element
|
||||
Count int64 // if == -1, an incomplete array, like char x[].
|
||||
}
|
||||
|
||||
func (t *ArrayType) String() string {
|
||||
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.String()
|
||||
}
|
||||
|
||||
func (t *ArrayType) Size() int64 {
|
||||
if t.Count == -1 {
|
||||
return 0
|
||||
}
|
||||
return t.Count * t.Type.Size()
|
||||
}
|
||||
|
||||
// A VoidType represents the C void type.
|
||||
type VoidType struct {
|
||||
CommonType
|
||||
}
|
||||
|
||||
func (t *VoidType) String() string { return "void" }
|
||||
|
||||
// A PtrType represents a pointer type.
|
||||
type PtrType struct {
|
||||
CommonType
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (t *PtrType) String() string { return "*" + t.Type.String() }
|
||||
|
||||
// A StructType represents a struct, union, or C++ class type.
|
||||
type StructType struct {
|
||||
CommonType
|
||||
StructName string
|
||||
Kind string // "struct", "union", or "class".
|
||||
Field []*StructField
|
||||
Incomplete bool // if true, struct, union, class is declared but not defined
|
||||
}
|
||||
|
||||
// A StructField represents a field in a struct, union, or C++ class type.
|
||||
type StructField struct {
|
||||
Name string
|
||||
Type Type
|
||||
ByteOffset int64
|
||||
ByteSize int64 // usually zero; use Type.Size() for normal fields
|
||||
BitOffset int64 // within the ByteSize bytes at ByteOffset
|
||||
BitSize int64 // zero if not a bit field
|
||||
}
|
||||
|
||||
func (t *StructType) String() string {
|
||||
if t.StructName != "" {
|
||||
return t.Kind + " " + t.StructName
|
||||
}
|
||||
return t.Defn()
|
||||
}
|
||||
|
||||
func (t *StructType) Defn() string {
|
||||
s := t.Kind
|
||||
if t.StructName != "" {
|
||||
s += " " + t.StructName
|
||||
}
|
||||
if t.Incomplete {
|
||||
s += " /*incomplete*/"
|
||||
return s
|
||||
}
|
||||
s += " {"
|
||||
for i, f := range t.Field {
|
||||
if i > 0 {
|
||||
s += "; "
|
||||
}
|
||||
s += f.Name + " " + f.Type.String()
|
||||
s += "@" + strconv.FormatInt(f.ByteOffset, 10)
|
||||
if f.BitSize > 0 {
|
||||
s += " : " + strconv.FormatInt(f.BitSize, 10)
|
||||
s += "@" + strconv.FormatInt(f.BitOffset, 10)
|
||||
}
|
||||
}
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
// An EnumType represents an enumerated type.
|
||||
// The only indication of its native integer type is its ByteSize
|
||||
// (inside CommonType).
|
||||
type EnumType struct {
|
||||
CommonType
|
||||
EnumName string
|
||||
Val []*EnumValue
|
||||
}
|
||||
|
||||
// An EnumValue represents a single enumeration value.
|
||||
type EnumValue struct {
|
||||
Name string
|
||||
Val int64
|
||||
}
|
||||
|
||||
func (t *EnumType) String() string {
|
||||
s := "enum"
|
||||
if t.EnumName != "" {
|
||||
s += " " + t.EnumName
|
||||
}
|
||||
s += " {"
|
||||
for i, v := range t.Val {
|
||||
if i > 0 {
|
||||
s += "; "
|
||||
}
|
||||
s += v.Name + "=" + strconv.FormatInt(v.Val, 10)
|
||||
}
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
// A FuncType represents a function type.
|
||||
type FuncType struct {
|
||||
CommonType
|
||||
ReturnType Type
|
||||
ParamType []Type
|
||||
}
|
||||
|
||||
func (t *FuncType) String() string {
|
||||
s := "func("
|
||||
for i, t := range t.ParamType {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += t.String()
|
||||
}
|
||||
s += ")"
|
||||
if t.ReturnType != nil {
|
||||
s += " " + t.ReturnType.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// A DotDotDotType represents the variadic ... function parameter.
|
||||
type DotDotDotType struct {
|
||||
CommonType
|
||||
}
|
||||
|
||||
func (t *DotDotDotType) String() string { return "..." }
|
||||
|
||||
// A TypedefType represents a named type.
|
||||
type TypedefType struct {
|
||||
CommonType
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (t *TypedefType) String() string { return t.Name }
|
||||
|
||||
func (t *TypedefType) Size() int64 { return t.Type.Size() }
|
||||
|
||||
// An UnsupportedType is a placeholder returned in situations where we
|
||||
// encounter a type that isn't supported.
|
||||
type UnsupportedType struct {
|
||||
CommonType
|
||||
Tag Tag
|
||||
}
|
||||
|
||||
func (t *UnsupportedType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return t.Name + "(unsupported type " + t.Tag.String() + ")"
|
||||
}
|
||||
|
||||
// typeReader is used to read from either the info section or the
|
||||
// types section.
|
||||
type typeReader interface {
|
||||
Seek(Offset)
|
||||
Next() (*Entry, error)
|
||||
clone() typeReader
|
||||
offset() Offset
|
||||
// AddressSize returns the size in bytes of addresses in the current
|
||||
// compilation unit.
|
||||
AddressSize() int
|
||||
}
|
||||
|
||||
// Type reads the type at off in the DWARF ``info'' section.
|
||||
func (d *Data) Type(off Offset) (Type, error) {
|
||||
return d.readType("info", d.Reader(), off, d.typeCache, nil)
|
||||
}
|
||||
|
||||
// readType reads a type from r at off of name. It adds types to the
|
||||
// type cache, appends new typedef types to typedefs, and computes the
|
||||
// sizes of types. Callers should pass nil for typedefs; this is used
|
||||
// for internal recursion.
|
||||
func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Offset]Type, typedefs *[]*TypedefType) (Type, error) {
|
||||
if t, ok := typeCache[off]; ok {
|
||||
return t, nil
|
||||
}
|
||||
r.Seek(off)
|
||||
e, err := r.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addressSize := r.AddressSize()
|
||||
if e == nil || e.Offset != off {
|
||||
return nil, DecodeError{name, off, "no type at offset"}
|
||||
}
|
||||
|
||||
// If this is the root of the recursion, prepare to resolve
|
||||
// typedef sizes once the recursion is done. This must be done
|
||||
// after the type graph is constructed because it may need to
|
||||
// resolve cycles in a different order than readType
|
||||
// encounters them.
|
||||
if typedefs == nil {
|
||||
var typedefList []*TypedefType
|
||||
defer func() {
|
||||
for _, t := range typedefList {
|
||||
t.Common().ByteSize = t.Type.Size()
|
||||
}
|
||||
}()
|
||||
typedefs = &typedefList
|
||||
}
|
||||
|
||||
// Parse type from Entry.
|
||||
// Must always set typeCache[off] before calling
|
||||
// d.readType recursively, to handle circular types correctly.
|
||||
var typ Type
|
||||
|
||||
nextDepth := 0
|
||||
|
||||
// Get next child; set err if error happens.
|
||||
next := func() *Entry {
|
||||
if !e.Children {
|
||||
return nil
|
||||
}
|
||||
// Only return direct children.
|
||||
// Skip over composite entries that happen to be nested
|
||||
// inside this one. Most DWARF generators wouldn't generate
|
||||
// such a thing, but clang does.
|
||||
// See golang.org/issue/6472.
|
||||
for {
|
||||
kid, err1 := r.Next()
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return nil
|
||||
}
|
||||
if kid == nil {
|
||||
err = DecodeError{name, r.offset(), "unexpected end of DWARF entries"}
|
||||
return nil
|
||||
}
|
||||
if kid.Tag == 0 {
|
||||
if nextDepth > 0 {
|
||||
nextDepth--
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if kid.Children {
|
||||
nextDepth++
|
||||
}
|
||||
if nextDepth > 0 {
|
||||
continue
|
||||
}
|
||||
return kid
|
||||
}
|
||||
}
|
||||
|
||||
// Get Type referred to by Entry's AttrType field.
|
||||
// Set err if error happens. Not having a type is an error.
|
||||
typeOf := func(e *Entry) Type {
|
||||
tval := e.Val(AttrType)
|
||||
var t Type
|
||||
switch toff := tval.(type) {
|
||||
case Offset:
|
||||
if t, err = d.readType(name, r.clone(), toff, typeCache, typedefs); err != nil {
|
||||
return nil
|
||||
}
|
||||
case uint64:
|
||||
if t, err = d.sigToType(toff); err != nil {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
// It appears that no Type means "void".
|
||||
return new(VoidType)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
switch e.Tag {
|
||||
case TagArrayType:
|
||||
// Multi-dimensional array. (DWARF v2 §5.4)
|
||||
// Attributes:
|
||||
// AttrType:subtype [required]
|
||||
// AttrStrideSize: size in bits of each element of the array
|
||||
// AttrByteSize: size of entire array
|
||||
// Children:
|
||||
// TagSubrangeType or TagEnumerationType giving one dimension.
|
||||
// dimensions are in left to right order.
|
||||
t := new(ArrayType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.Type = typeOf(e); err != nil {
|
||||
goto Error
|
||||
}
|
||||
t.StrideBitSize, _ = e.Val(AttrStrideSize).(int64)
|
||||
|
||||
// Accumulate dimensions,
|
||||
var dims []int64
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
// TODO(rsc): Can also be TagEnumerationType
|
||||
// but haven't seen that in the wild yet.
|
||||
switch kid.Tag {
|
||||
case TagSubrangeType:
|
||||
count, ok := kid.Val(AttrCount).(int64)
|
||||
if !ok {
|
||||
// Old binaries may have an upper bound instead.
|
||||
count, ok = kid.Val(AttrUpperBound).(int64)
|
||||
if ok {
|
||||
count++ // Length is one more than upper bound.
|
||||
} else if len(dims) == 0 {
|
||||
count = -1 // As in x[].
|
||||
}
|
||||
}
|
||||
dims = append(dims, count)
|
||||
case TagEnumerationType:
|
||||
err = DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}
|
||||
goto Error
|
||||
}
|
||||
}
|
||||
if len(dims) == 0 {
|
||||
// LLVM generates this for x[].
|
||||
dims = []int64{-1}
|
||||
}
|
||||
|
||||
t.Count = dims[0]
|
||||
for i := len(dims) - 1; i >= 1; i-- {
|
||||
t.Type = &ArrayType{Type: t.Type, Count: dims[i]}
|
||||
}
|
||||
|
||||
case TagBaseType:
|
||||
// Basic type. (DWARF v2 §5.1)
|
||||
// Attributes:
|
||||
// AttrName: name of base type in programming language of the compilation unit [required]
|
||||
// AttrEncoding: encoding value for type (encFloat etc) [required]
|
||||
// AttrByteSize: size of type in bytes [required]
|
||||
// AttrBitOffset: for sub-byte types, size in bits
|
||||
// AttrBitSize: for sub-byte types, bit offset of high order bit in the AttrByteSize bytes
|
||||
name, _ := e.Val(AttrName).(string)
|
||||
enc, ok := e.Val(AttrEncoding).(int64)
|
||||
if !ok {
|
||||
err = DecodeError{name, e.Offset, "missing encoding attribute for " + name}
|
||||
goto Error
|
||||
}
|
||||
switch enc {
|
||||
default:
|
||||
err = DecodeError{name, e.Offset, "unrecognized encoding attribute value"}
|
||||
goto Error
|
||||
|
||||
case encAddress:
|
||||
typ = new(AddrType)
|
||||
case encBoolean:
|
||||
typ = new(BoolType)
|
||||
case encComplexFloat:
|
||||
typ = new(ComplexType)
|
||||
if name == "complex" {
|
||||
// clang writes out 'complex' instead of 'complex float' or 'complex double'.
|
||||
// clang also writes out a byte size that we can use to distinguish.
|
||||
// See issue 8694.
|
||||
switch byteSize, _ := e.Val(AttrByteSize).(int64); byteSize {
|
||||
case 8:
|
||||
name = "complex float"
|
||||
case 16:
|
||||
name = "complex double"
|
||||
}
|
||||
}
|
||||
case encFloat:
|
||||
typ = new(FloatType)
|
||||
case encSigned:
|
||||
typ = new(IntType)
|
||||
case encUnsigned:
|
||||
typ = new(UintType)
|
||||
case encSignedChar:
|
||||
typ = new(CharType)
|
||||
case encUnsignedChar:
|
||||
typ = new(UcharType)
|
||||
}
|
||||
typeCache[off] = typ
|
||||
t := typ.(interface {
|
||||
Basic() *BasicType
|
||||
}).Basic()
|
||||
t.Name = name
|
||||
t.BitSize, _ = e.Val(AttrBitSize).(int64)
|
||||
t.BitOffset, _ = e.Val(AttrBitOffset).(int64)
|
||||
|
||||
case TagClassType, TagStructType, TagUnionType:
|
||||
// Structure, union, or class type. (DWARF v2 §5.5)
|
||||
// Attributes:
|
||||
// AttrName: name of struct, union, or class
|
||||
// AttrByteSize: byte size [required]
|
||||
// AttrDeclaration: if true, struct/union/class is incomplete
|
||||
// Children:
|
||||
// TagMember to describe one member.
|
||||
// AttrName: name of member [required]
|
||||
// AttrType: type of member [required]
|
||||
// AttrByteSize: size in bytes
|
||||
// AttrBitOffset: bit offset within bytes for bit fields
|
||||
// AttrBitSize: bit size for bit fields
|
||||
// AttrDataMemberLoc: location within struct [required for struct, class]
|
||||
// There is much more to handle C++, all ignored for now.
|
||||
t := new(StructType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
switch e.Tag {
|
||||
case TagClassType:
|
||||
t.Kind = "class"
|
||||
case TagStructType:
|
||||
t.Kind = "struct"
|
||||
case TagUnionType:
|
||||
t.Kind = "union"
|
||||
}
|
||||
t.StructName, _ = e.Val(AttrName).(string)
|
||||
t.Incomplete = e.Val(AttrDeclaration) != nil
|
||||
t.Field = make([]*StructField, 0, 8)
|
||||
var lastFieldType *Type
|
||||
var lastFieldBitOffset int64
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
if kid.Tag != TagMember {
|
||||
continue
|
||||
}
|
||||
f := new(StructField)
|
||||
if f.Type = typeOf(kid); err != nil {
|
||||
goto Error
|
||||
}
|
||||
switch loc := kid.Val(AttrDataMemberLoc).(type) {
|
||||
case []byte:
|
||||
// TODO: Should have original compilation
|
||||
// unit here, not unknownFormat.
|
||||
b := makeBuf(d, unknownFormat{}, "location", 0, loc)
|
||||
if b.uint8() != opPlusUconst {
|
||||
err = DecodeError{name, kid.Offset, "unexpected opcode"}
|
||||
goto Error
|
||||
}
|
||||
f.ByteOffset = int64(b.uint())
|
||||
if b.err != nil {
|
||||
err = b.err
|
||||
goto Error
|
||||
}
|
||||
case int64:
|
||||
f.ByteOffset = loc
|
||||
}
|
||||
|
||||
haveBitOffset := false
|
||||
f.Name, _ = kid.Val(AttrName).(string)
|
||||
f.ByteSize, _ = kid.Val(AttrByteSize).(int64)
|
||||
f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64)
|
||||
f.BitSize, _ = kid.Val(AttrBitSize).(int64)
|
||||
t.Field = append(t.Field, f)
|
||||
|
||||
bito := f.BitOffset
|
||||
if !haveBitOffset {
|
||||
bito = f.ByteOffset * 8
|
||||
}
|
||||
if bito == lastFieldBitOffset && t.Kind != "union" {
|
||||
// Last field was zero width. Fix array length.
|
||||
// (DWARF writes out 0-length arrays as if they were 1-length arrays.)
|
||||
zeroArray(lastFieldType)
|
||||
}
|
||||
lastFieldType = &f.Type
|
||||
lastFieldBitOffset = bito
|
||||
}
|
||||
if t.Kind != "union" {
|
||||
b, ok := e.Val(AttrByteSize).(int64)
|
||||
if ok && b*8 == lastFieldBitOffset {
|
||||
// Final field must be zero width. Fix array length.
|
||||
zeroArray(lastFieldType)
|
||||
}
|
||||
}
|
||||
|
||||
case TagConstType, TagVolatileType, TagRestrictType:
|
||||
// Type modifier (DWARF v2 §5.2)
|
||||
// Attributes:
|
||||
// AttrType: subtype
|
||||
t := new(QualType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.Type = typeOf(e); err != nil {
|
||||
goto Error
|
||||
}
|
||||
switch e.Tag {
|
||||
case TagConstType:
|
||||
t.Qual = "const"
|
||||
case TagRestrictType:
|
||||
t.Qual = "restrict"
|
||||
case TagVolatileType:
|
||||
t.Qual = "volatile"
|
||||
}
|
||||
|
||||
case TagEnumerationType:
|
||||
// Enumeration type (DWARF v2 §5.6)
|
||||
// Attributes:
|
||||
// AttrName: enum name if any
|
||||
// AttrByteSize: bytes required to represent largest value
|
||||
// Children:
|
||||
// TagEnumerator:
|
||||
// AttrName: name of constant
|
||||
// AttrConstValue: value of constant
|
||||
t := new(EnumType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.EnumName, _ = e.Val(AttrName).(string)
|
||||
t.Val = make([]*EnumValue, 0, 8)
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
if kid.Tag == TagEnumerator {
|
||||
f := new(EnumValue)
|
||||
f.Name, _ = kid.Val(AttrName).(string)
|
||||
f.Val, _ = kid.Val(AttrConstValue).(int64)
|
||||
n := len(t.Val)
|
||||
if n >= cap(t.Val) {
|
||||
val := make([]*EnumValue, n, n*2)
|
||||
copy(val, t.Val)
|
||||
t.Val = val
|
||||
}
|
||||
t.Val = t.Val[0 : n+1]
|
||||
t.Val[n] = f
|
||||
}
|
||||
}
|
||||
|
||||
case TagPointerType:
|
||||
// Type modifier (DWARF v2 §5.2)
|
||||
// Attributes:
|
||||
// AttrType: subtype [not required! void* has no AttrType]
|
||||
// AttrAddrClass: address class [ignored]
|
||||
t := new(PtrType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if e.Val(AttrType) == nil {
|
||||
t.Type = &VoidType{}
|
||||
break
|
||||
}
|
||||
t.Type = typeOf(e)
|
||||
|
||||
case TagSubroutineType:
|
||||
// Subroutine type. (DWARF v2 §5.7)
|
||||
// Attributes:
|
||||
// AttrType: type of return value if any
|
||||
// AttrName: possible name of type [ignored]
|
||||
// AttrPrototyped: whether used ANSI C prototype [ignored]
|
||||
// Children:
|
||||
// TagFormalParameter: typed parameter
|
||||
// AttrType: type of parameter
|
||||
// TagUnspecifiedParameter: final ...
|
||||
t := new(FuncType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.ReturnType = typeOf(e); err != nil {
|
||||
goto Error
|
||||
}
|
||||
t.ParamType = make([]Type, 0, 8)
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
var tkid Type
|
||||
switch kid.Tag {
|
||||
default:
|
||||
continue
|
||||
case TagFormalParameter:
|
||||
if tkid = typeOf(kid); err != nil {
|
||||
goto Error
|
||||
}
|
||||
case TagUnspecifiedParameters:
|
||||
tkid = &DotDotDotType{}
|
||||
}
|
||||
t.ParamType = append(t.ParamType, tkid)
|
||||
}
|
||||
|
||||
case TagTypedef:
|
||||
// Typedef (DWARF v2 §5.3)
|
||||
// Attributes:
|
||||
// AttrName: name [required]
|
||||
// AttrType: type definition [required]
|
||||
t := new(TypedefType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Type = typeOf(e)
|
||||
|
||||
case TagUnspecifiedType:
|
||||
// Unspecified type (DWARF v3 §5.2)
|
||||
// Attributes:
|
||||
// AttrName: name
|
||||
t := new(UnspecifiedType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
|
||||
default:
|
||||
// This is some other type DIE that we're currently not
|
||||
// equipped to handle. Return an abstract "unsupported type"
|
||||
// object in such cases.
|
||||
t := new(UnsupportedType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.Tag = e.Tag
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
|
||||
{
|
||||
b, ok := e.Val(AttrByteSize).(int64)
|
||||
if !ok {
|
||||
b = -1
|
||||
switch t := typ.(type) {
|
||||
case *TypedefType:
|
||||
// Record that we need to resolve this
|
||||
// type's size once the type graph is
|
||||
// constructed.
|
||||
*typedefs = append(*typedefs, t)
|
||||
case *PtrType:
|
||||
b = int64(addressSize)
|
||||
}
|
||||
}
|
||||
typ.Common().ByteSize = b
|
||||
}
|
||||
return typ, nil
|
||||
|
||||
Error:
|
||||
// If the parse fails, take the type out of the cache
|
||||
// so that the next call with this offset doesn't hit
|
||||
// the cache and return success.
|
||||
delete(typeCache, off)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func zeroArray(t *Type) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
at, ok := (*t).(*ArrayType)
|
||||
if !ok || at.Type.Size() == 0 {
|
||||
return
|
||||
}
|
||||
// Make a copy to avoid invalidating typeCache.
|
||||
tt := *at
|
||||
tt.Count = 0
|
||||
*t = &tt
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2012 The Go 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 dwarf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Parse the type units stored in a DWARF4 .debug_types section. Each
|
||||
// type unit defines a single primary type and an 8-byte signature.
|
||||
// Other sections may then use formRefSig8 to refer to the type.
|
||||
|
||||
// The typeUnit format is a single type with a signature. It holds
|
||||
// the same data as a compilation unit.
|
||||
type typeUnit struct {
|
||||
unit
|
||||
toff Offset // Offset to signature type within data.
|
||||
name string // Name of .debug_type section.
|
||||
cache Type // Cache the type, nil to start.
|
||||
}
|
||||
|
||||
// Parse a .debug_types section.
|
||||
func (d *Data) parseTypes(name string, types []byte) error {
|
||||
b := makeBuf(d, unknownFormat{}, name, 0, types)
|
||||
for len(b.data) > 0 {
|
||||
base := b.off
|
||||
n, dwarf64 := b.unitLength()
|
||||
if n != Offset(uint32(n)) {
|
||||
b.error("type unit length overflow")
|
||||
return b.err
|
||||
}
|
||||
hdroff := b.off
|
||||
vers := int(b.uint16())
|
||||
if vers != 4 {
|
||||
b.error("unsupported DWARF version " + strconv.Itoa(vers))
|
||||
return b.err
|
||||
}
|
||||
var ao uint64
|
||||
if !dwarf64 {
|
||||
ao = uint64(b.uint32())
|
||||
} else {
|
||||
ao = b.uint64()
|
||||
}
|
||||
atable, err := d.parseAbbrev(ao, vers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
asize := b.uint8()
|
||||
sig := b.uint64()
|
||||
|
||||
var toff uint32
|
||||
if !dwarf64 {
|
||||
toff = b.uint32()
|
||||
} else {
|
||||
to64 := b.uint64()
|
||||
if to64 != uint64(uint32(to64)) {
|
||||
b.error("type unit type offset overflow")
|
||||
return b.err
|
||||
}
|
||||
toff = uint32(to64)
|
||||
}
|
||||
|
||||
boff := b.off
|
||||
d.typeSigs[sig] = &typeUnit{
|
||||
unit: unit{
|
||||
base: base,
|
||||
off: boff,
|
||||
data: b.bytes(int(n - (b.off - hdroff))),
|
||||
atable: atable,
|
||||
asize: int(asize),
|
||||
vers: vers,
|
||||
is64: dwarf64,
|
||||
},
|
||||
toff: Offset(toff),
|
||||
name: name,
|
||||
}
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the type for a type signature.
|
||||
func (d *Data) sigToType(sig uint64) (Type, error) {
|
||||
tu := d.typeSigs[sig]
|
||||
if tu == nil {
|
||||
return nil, fmt.Errorf("no type unit with signature %v", sig)
|
||||
}
|
||||
if tu.cache != nil {
|
||||
return tu.cache, nil
|
||||
}
|
||||
|
||||
b := makeBuf(d, tu, tu.name, tu.off, tu.data)
|
||||
r := &typeUnitReader{d: d, tu: tu, b: b}
|
||||
t, err := d.readType(tu.name, r, tu.toff, make(map[Offset]Type), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tu.cache = t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// typeUnitReader is a typeReader for a tagTypeUnit.
|
||||
type typeUnitReader struct {
|
||||
d *Data
|
||||
tu *typeUnit
|
||||
b buf
|
||||
err error
|
||||
}
|
||||
|
||||
// Seek to a new position in the type unit.
|
||||
func (tur *typeUnitReader) Seek(off Offset) {
|
||||
tur.err = nil
|
||||
doff := off - tur.tu.off
|
||||
if doff < 0 || doff >= Offset(len(tur.tu.data)) {
|
||||
tur.err = fmt.Errorf("%s: offset %d out of range; max %d", tur.tu.name, doff, len(tur.tu.data))
|
||||
return
|
||||
}
|
||||
tur.b = makeBuf(tur.d, tur.tu, tur.tu.name, off, tur.tu.data[doff:])
|
||||
}
|
||||
|
||||
// AddressSize returns the size in bytes of addresses in the current type unit.
|
||||
func (tur *typeUnitReader) AddressSize() int {
|
||||
return tur.tu.unit.asize
|
||||
}
|
||||
|
||||
// Next reads the next Entry from the type unit.
|
||||
func (tur *typeUnitReader) Next() (*Entry, error) {
|
||||
if tur.err != nil {
|
||||
return nil, tur.err
|
||||
}
|
||||
if len(tur.tu.data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
e := tur.b.entry(nil, tur.tu.atable, tur.tu.base, tur.tu.vers)
|
||||
if tur.b.err != nil {
|
||||
tur.err = tur.b.err
|
||||
return nil, tur.err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// clone returns a new reader for the type unit.
|
||||
func (tur *typeUnitReader) clone() typeReader {
|
||||
return &typeUnitReader{
|
||||
d: tur.d,
|
||||
tu: tur.tu,
|
||||
b: makeBuf(tur.d, tur.tu, tur.tu.name, tur.tu.off, tur.tu.data),
|
||||
}
|
||||
}
|
||||
|
||||
// offset returns the current offset.
|
||||
func (tur *typeUnitReader) offset() Offset {
|
||||
return tur.b.off
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2009 The Go 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 dwarf
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// DWARF debug info is split into a sequence of compilation units.
|
||||
// Each unit has its own abbreviation table and address size.
|
||||
|
||||
type unit struct {
|
||||
base Offset // byte offset of header within the aggregate info
|
||||
off Offset // byte offset of data within the aggregate info
|
||||
data []byte
|
||||
atable abbrevTable
|
||||
asize int
|
||||
vers int
|
||||
utype uint8 // DWARF 5 unit type
|
||||
is64 bool // True for 64-bit DWARF format
|
||||
}
|
||||
|
||||
// Implement the dataFormat interface.
|
||||
|
||||
func (u *unit) version() int {
|
||||
return u.vers
|
||||
}
|
||||
|
||||
func (u *unit) dwarf64() (bool, bool) {
|
||||
return u.is64, true
|
||||
}
|
||||
|
||||
func (u *unit) addrsize() int {
|
||||
return u.asize
|
||||
}
|
||||
|
||||
func (d *Data) parseUnits() ([]unit, error) {
|
||||
// Count units.
|
||||
nunit := 0
|
||||
b := makeBuf(d, unknownFormat{}, "info", 0, d.info)
|
||||
for len(b.data) > 0 {
|
||||
len, _ := b.unitLength()
|
||||
if len != Offset(uint32(len)) {
|
||||
b.error("unit length overflow")
|
||||
break
|
||||
}
|
||||
b.skip(int(len))
|
||||
nunit++
|
||||
}
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
// Again, this time writing them down.
|
||||
b = makeBuf(d, unknownFormat{}, "info", 0, d.info)
|
||||
units := make([]unit, nunit)
|
||||
for i := range units {
|
||||
u := &units[i]
|
||||
u.base = b.off
|
||||
var n Offset
|
||||
n, u.is64 = b.unitLength()
|
||||
dataOff := b.off
|
||||
vers := b.uint16()
|
||||
if vers < 2 || vers > 5 {
|
||||
b.error("unsupported DWARF version " + strconv.Itoa(int(vers)))
|
||||
break
|
||||
}
|
||||
u.vers = int(vers)
|
||||
if vers >= 5 {
|
||||
u.utype = b.uint8()
|
||||
u.asize = int(b.uint8())
|
||||
}
|
||||
var abbrevOff uint64
|
||||
if u.is64 {
|
||||
abbrevOff = b.uint64()
|
||||
} else {
|
||||
abbrevOff = uint64(b.uint32())
|
||||
}
|
||||
atable, err := d.parseAbbrev(abbrevOff, u.vers)
|
||||
if err != nil {
|
||||
if b.err == nil {
|
||||
b.err = err
|
||||
}
|
||||
break
|
||||
}
|
||||
u.atable = atable
|
||||
if vers < 5 {
|
||||
u.asize = int(b.uint8())
|
||||
}
|
||||
|
||||
switch u.utype {
|
||||
case utSkeleton, utSplitCompile:
|
||||
b.uint64() // unit ID
|
||||
case utType, utSplitType:
|
||||
b.uint64() // type signature
|
||||
if u.is64 { // type offset
|
||||
b.uint64()
|
||||
} else {
|
||||
b.uint32()
|
||||
}
|
||||
}
|
||||
|
||||
u.off = b.off
|
||||
u.data = b.bytes(int(n - (b.off - dataOff)))
|
||||
}
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
return units, nil
|
||||
}
|
||||
|
||||
// offsetToUnit returns the index of the unit containing offset off.
|
||||
// It returns -1 if no unit contains this offset.
|
||||
func (d *Data) offsetToUnit(off Offset) int {
|
||||
// Find the unit after off
|
||||
next := sort.Search(len(d.unit), func(i int) bool {
|
||||
return d.unit[i].off > off
|
||||
})
|
||||
if next == 0 {
|
||||
return -1
|
||||
}
|
||||
u := &d.unit[next-1]
|
||||
if u.off <= off && off < u.off+Offset(len(u.data)) {
|
||||
return next - 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
+3176
File diff suppressed because it is too large
Load Diff
+1471
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package elf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// errorReader returns error from all operations.
|
||||
type errorReader struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (r errorReader) Read(p []byte) (n int, err error) {
|
||||
return 0, r.error
|
||||
}
|
||||
|
||||
func (r errorReader) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
return 0, r.error
|
||||
}
|
||||
|
||||
func (r errorReader) Seek(offset int64, whence int) (int64, error) {
|
||||
return 0, r.error
|
||||
}
|
||||
|
||||
func (r errorReader) Close() error {
|
||||
return r.error
|
||||
}
|
||||
|
||||
// readSeekerFromReader converts an io.Reader into an io.ReadSeeker.
|
||||
// In general Seek may not be efficient, but it is optimized for
|
||||
// common cases such as seeking to the end to find the length of the
|
||||
// data.
|
||||
type readSeekerFromReader struct {
|
||||
reset func() (io.Reader, error)
|
||||
r io.Reader
|
||||
size int64
|
||||
offset int64
|
||||
}
|
||||
|
||||
func (r *readSeekerFromReader) start() {
|
||||
x, err := r.reset()
|
||||
if err != nil {
|
||||
r.r = errorReader{err}
|
||||
} else {
|
||||
r.r = x
|
||||
}
|
||||
r.offset = 0
|
||||
}
|
||||
|
||||
func (r *readSeekerFromReader) Read(p []byte) (n int, err error) {
|
||||
if r.r == nil {
|
||||
r.start()
|
||||
}
|
||||
n, err = r.r.Read(p)
|
||||
r.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *readSeekerFromReader) Seek(offset int64, whence int) (int64, error) {
|
||||
var newOffset int64
|
||||
switch whence {
|
||||
case seekStart:
|
||||
newOffset = offset
|
||||
case seekCurrent:
|
||||
newOffset = r.offset + offset
|
||||
case seekEnd:
|
||||
newOffset = r.size + offset
|
||||
default:
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
switch {
|
||||
case newOffset == r.offset:
|
||||
return newOffset, nil
|
||||
|
||||
case newOffset < 0, newOffset > r.size:
|
||||
return 0, os.ErrInvalid
|
||||
|
||||
case newOffset == 0:
|
||||
r.r = nil
|
||||
|
||||
case newOffset == r.size:
|
||||
r.r = errorReader{io.EOF}
|
||||
|
||||
default:
|
||||
if newOffset < r.offset {
|
||||
// Restart at the beginning.
|
||||
r.start()
|
||||
}
|
||||
// Read until we reach offset.
|
||||
var buf [512]byte
|
||||
for r.offset < newOffset {
|
||||
b := buf[:]
|
||||
if newOffset-r.offset < int64(len(buf)) {
|
||||
b = buf[:newOffset-r.offset]
|
||||
}
|
||||
if _, err := r.Read(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
r.offset = newOffset
|
||||
return r.offset, nil
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
/*
|
||||
* Line tables
|
||||
*/
|
||||
|
||||
package gosym
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// version of the pclntab
|
||||
type version int
|
||||
|
||||
const (
|
||||
verUnknown version = iota
|
||||
ver11
|
||||
ver12
|
||||
ver116
|
||||
ver118
|
||||
)
|
||||
|
||||
func (v version) String() string {
|
||||
switch v {
|
||||
case verUnknown:
|
||||
return "Unknown"
|
||||
case ver11:
|
||||
return "1.1"
|
||||
case ver12:
|
||||
return "1.2"
|
||||
case ver116:
|
||||
return "1.16"
|
||||
case ver118:
|
||||
return "1.18"
|
||||
default:
|
||||
return fmt.Sprintf("ERROR Unknown ID number %d", int(v))
|
||||
}
|
||||
}
|
||||
|
||||
// A LineTable is a data structure mapping program counters to line numbers.
|
||||
//
|
||||
// In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable,
|
||||
// and the line number corresponded to a numbering of all source lines in the
|
||||
// program, across all files. That absolute line number would then have to be
|
||||
// converted separately to a file name and line number within the file.
|
||||
//
|
||||
// In Go 1.2, the format of the data changed so that there is a single LineTable
|
||||
// for the entire program, shared by all Funcs, and there are no absolute line
|
||||
// numbers, just line numbers within specific files.
|
||||
//
|
||||
// For the most part, LineTable's methods should be treated as an internal
|
||||
// detail of the package; callers should use the methods on Table instead.
|
||||
type LineTable struct {
|
||||
Data []byte
|
||||
PC uint64
|
||||
Line int
|
||||
|
||||
// This mutex is used to keep parsing of pclntab synchronous.
|
||||
mu sync.Mutex
|
||||
|
||||
// Contains the version of the pclntab section.
|
||||
Version version
|
||||
|
||||
// Go 1.2/1.16/1.18 state
|
||||
Binary binary.ByteOrder
|
||||
Quantum uint32
|
||||
Ptrsize uint32
|
||||
textStart uint64 // address of runtime.text symbol (1.18+)
|
||||
funcnametab []byte
|
||||
cutab []byte
|
||||
funcdata []byte
|
||||
functab []byte
|
||||
nfunctab uint32
|
||||
filetab []byte
|
||||
pctab []byte // points to the pctables.
|
||||
nfiletab uint32
|
||||
funcNames map[uint32]string // cache the function names
|
||||
strings map[uint32]string // interned substrings of Data, keyed by offset
|
||||
// fileMap varies depending on the version of the object file.
|
||||
// For ver12, it maps the name to the index in the file table.
|
||||
// For ver116, it maps the name to the offset in filetab.
|
||||
fileMap map[string]uint32
|
||||
}
|
||||
|
||||
// NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4,
|
||||
// but we have no idea whether we're using arm or not. This only
|
||||
// matters in the old (pre-Go 1.2) symbol table format, so it's not worth
|
||||
// fixing.
|
||||
const oldQuantum = 1
|
||||
|
||||
func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) {
|
||||
// The PC/line table can be thought of as a sequence of
|
||||
// <pc update>* <line update>
|
||||
// batches. Each update batch results in a (pc, line) pair,
|
||||
// where line applies to every PC from pc up to but not
|
||||
// including the pc of the next pair.
|
||||
//
|
||||
// Here we process each update individually, which simplifies
|
||||
// the code, but makes the corner cases more confusing.
|
||||
b, pc, line = t.Data, t.PC, t.Line
|
||||
for pc <= targetPC && line != targetLine && len(b) > 0 {
|
||||
code := b[0]
|
||||
b = b[1:]
|
||||
switch {
|
||||
case code == 0:
|
||||
if len(b) < 4 {
|
||||
b = b[0:0]
|
||||
break
|
||||
}
|
||||
val := binary.BigEndian.Uint32(b)
|
||||
b = b[4:]
|
||||
line += int(val)
|
||||
case code <= 64:
|
||||
line += int(code)
|
||||
case code <= 128:
|
||||
line -= int(code - 64)
|
||||
default:
|
||||
pc += oldQuantum * uint64(code-128)
|
||||
continue
|
||||
}
|
||||
pc += oldQuantum
|
||||
}
|
||||
return b, pc, line
|
||||
}
|
||||
|
||||
func (t *LineTable) slice(pc uint64) *LineTable {
|
||||
data, pc, line := t.parse(pc, -1)
|
||||
return &LineTable{Data: data, PC: pc, Line: line}
|
||||
}
|
||||
|
||||
// PCToLine returns the line number for the given program counter.
|
||||
//
|
||||
// Deprecated: Use Table's PCToLine method instead.
|
||||
func (t *LineTable) PCToLine(pc uint64) int {
|
||||
if t.isGo12() {
|
||||
return t.go12PCToLine(pc)
|
||||
}
|
||||
_, _, line := t.parse(pc, -1)
|
||||
return line
|
||||
}
|
||||
|
||||
// LineToPC returns the program counter for the given line number,
|
||||
// considering only program counters before maxpc.
|
||||
//
|
||||
// Deprecated: Use Table's LineToPC method instead.
|
||||
func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
|
||||
if t.isGo12() {
|
||||
return 0
|
||||
}
|
||||
_, pc, line1 := t.parse(maxpc, line)
|
||||
if line1 != line {
|
||||
return 0
|
||||
}
|
||||
// Subtract quantum from PC to account for post-line increment
|
||||
return pc - oldQuantum
|
||||
}
|
||||
|
||||
// NewLineTable returns a new PC/line table
|
||||
// corresponding to the encoded data.
|
||||
// Text must be the start address of the
|
||||
// corresponding text segment.
|
||||
func NewLineTable(data []byte, text uint64) *LineTable {
|
||||
return &LineTable{Data: data, PC: text, Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)}
|
||||
}
|
||||
|
||||
// Go 1.2 symbol table format.
|
||||
// See golang.org/s/go12symtab.
|
||||
//
|
||||
// A general note about the methods here: rather than try to avoid
|
||||
// index out of bounds errors, we trust Go to detect them, and then
|
||||
// we recover from the panics and treat them as indicative of a malformed
|
||||
// or incomplete table.
|
||||
//
|
||||
// The methods called by symtab.go, which begin with "go12" prefixes,
|
||||
// are expected to have that recovery logic.
|
||||
|
||||
// isGo12 reports whether this is a Go 1.2 (or later) symbol table.
|
||||
func (t *LineTable) isGo12() bool {
|
||||
t.parsePclnTab()
|
||||
return t.Version >= ver12
|
||||
}
|
||||
|
||||
const (
|
||||
go12magic = 0xfffffffb
|
||||
go116magic = 0xfffffffa
|
||||
go118magic = 0xfffffff0
|
||||
)
|
||||
|
||||
// uintptr returns the pointer-sized value encoded at b.
|
||||
// The pointer size is dictated by the table being read.
|
||||
func (t *LineTable) uintptr(b []byte) uint64 {
|
||||
if t.Ptrsize == 4 {
|
||||
return uint64(t.Binary.Uint32(b))
|
||||
}
|
||||
return t.Binary.Uint64(b)
|
||||
}
|
||||
|
||||
// parsePclnTab parses the pclntab, setting the version.
|
||||
func (t *LineTable) parsePclnTab() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.Version != verUnknown {
|
||||
return
|
||||
}
|
||||
|
||||
// Note that during this function, setting the version is the last thing we do.
|
||||
// If we set the version too early, and parsing failed (likely as a panic on
|
||||
// slice lookups), we'd have a mistaken version.
|
||||
//
|
||||
// Error paths through this code will default the version to 1.1.
|
||||
t.Version = ver11
|
||||
|
||||
if !disableRecover {
|
||||
defer func() {
|
||||
// If we panic parsing, assume it's a Go 1.1 pclntab.
|
||||
recover()
|
||||
}()
|
||||
}
|
||||
|
||||
// Check header: 4-byte magic, two zeros, pc quantum, pointer size.
|
||||
if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 ||
|
||||
(t.Data[6] != 1 && t.Data[6] != 2 && t.Data[6] != 4) || // pc quantum
|
||||
(t.Data[7] != 4 && t.Data[7] != 8) { // pointer size
|
||||
return
|
||||
}
|
||||
|
||||
var possibleVersion version
|
||||
leMagic := binary.LittleEndian.Uint32(t.Data)
|
||||
beMagic := binary.BigEndian.Uint32(t.Data)
|
||||
switch {
|
||||
case leMagic == go12magic:
|
||||
t.Binary, possibleVersion = binary.LittleEndian, ver12
|
||||
case beMagic == go12magic:
|
||||
t.Binary, possibleVersion = binary.BigEndian, ver12
|
||||
case leMagic == go116magic:
|
||||
t.Binary, possibleVersion = binary.LittleEndian, ver116
|
||||
case beMagic == go116magic:
|
||||
t.Binary, possibleVersion = binary.BigEndian, ver116
|
||||
case leMagic == go118magic:
|
||||
t.Binary, possibleVersion = binary.LittleEndian, ver118
|
||||
case beMagic == go118magic:
|
||||
t.Binary, possibleVersion = binary.BigEndian, ver118
|
||||
default:
|
||||
return
|
||||
}
|
||||
t.Version = possibleVersion
|
||||
|
||||
// quantum and ptrSize are the same between 1.2, 1.16, and 1.18
|
||||
t.Quantum = uint32(t.Data[6])
|
||||
t.Ptrsize = uint32(t.Data[7])
|
||||
|
||||
offset := func(word uint32) uint64 {
|
||||
return t.uintptr(t.Data[8+word*t.Ptrsize:])
|
||||
}
|
||||
data := func(word uint32) []byte {
|
||||
return t.Data[offset(word):]
|
||||
}
|
||||
|
||||
switch possibleVersion {
|
||||
case ver118:
|
||||
t.nfunctab = uint32(offset(0))
|
||||
t.nfiletab = uint32(offset(1))
|
||||
t.textStart = t.PC // use the start PC instead of reading from the table, which may be unrelocated
|
||||
t.funcnametab = data(3)
|
||||
t.cutab = data(4)
|
||||
t.filetab = data(5)
|
||||
t.pctab = data(6)
|
||||
t.funcdata = data(7)
|
||||
t.functab = data(7)
|
||||
functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
|
||||
t.functab = t.functab[:functabsize]
|
||||
case ver116:
|
||||
t.nfunctab = uint32(offset(0))
|
||||
t.nfiletab = uint32(offset(1))
|
||||
t.funcnametab = data(2)
|
||||
t.cutab = data(3)
|
||||
t.filetab = data(4)
|
||||
t.pctab = data(5)
|
||||
t.funcdata = data(6)
|
||||
t.functab = data(6)
|
||||
functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
|
||||
t.functab = t.functab[:functabsize]
|
||||
case ver12:
|
||||
t.nfunctab = uint32(t.uintptr(t.Data[8:]))
|
||||
t.funcdata = t.Data
|
||||
t.funcnametab = t.Data
|
||||
t.functab = t.Data[8+t.Ptrsize:]
|
||||
t.pctab = t.Data
|
||||
functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
|
||||
fileoff := t.Binary.Uint32(t.functab[functabsize:])
|
||||
t.functab = t.functab[:functabsize]
|
||||
t.filetab = t.Data[fileoff:]
|
||||
t.nfiletab = t.Binary.Uint32(t.filetab)
|
||||
t.filetab = t.filetab[:t.nfiletab*4]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// go12Funcs returns a slice of Funcs derived from the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12Funcs() []Func {
|
||||
// Assume it is malformed and return nil on error.
|
||||
if !disableRecover {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
}
|
||||
|
||||
ft := t.funcTab()
|
||||
funcs := make([]Func, ft.Count())
|
||||
syms := make([]Sym, len(funcs))
|
||||
for i := range funcs {
|
||||
f := &funcs[i]
|
||||
f.Entry = ft.pc(i)
|
||||
f.End = ft.pc(i + 1)
|
||||
info := t.funcData(uint32(i))
|
||||
f.LineTable = t
|
||||
f.FrameSize = int(info.deferreturn())
|
||||
syms[i] = Sym{
|
||||
Value: f.Entry,
|
||||
Type: 'T',
|
||||
Name: t.funcName(info.nameoff()),
|
||||
GoType: 0,
|
||||
Func: f,
|
||||
}
|
||||
f.Sym = &syms[i]
|
||||
}
|
||||
return funcs
|
||||
}
|
||||
|
||||
// findFunc returns the funcData corresponding to the given program counter.
|
||||
func (t *LineTable) findFunc(pc uint64) funcData {
|
||||
ft := t.funcTab()
|
||||
if pc < ft.pc(0) || pc >= ft.pc(ft.Count()) {
|
||||
return funcData{}
|
||||
}
|
||||
idx := sort.Search(int(t.nfunctab), func(i int) bool {
|
||||
return ft.pc(i) > pc
|
||||
})
|
||||
idx--
|
||||
return t.funcData(uint32(idx))
|
||||
}
|
||||
|
||||
// readvarint reads, removes, and returns a varint from *pp.
|
||||
func (t *LineTable) readvarint(pp *[]byte) uint32 {
|
||||
var v, shift uint32
|
||||
p := *pp
|
||||
for shift = 0; ; shift += 7 {
|
||||
b := p[0]
|
||||
p = p[1:]
|
||||
v |= (uint32(b) & 0x7F) << shift
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
*pp = p
|
||||
return v
|
||||
}
|
||||
|
||||
// funcName returns the name of the function found at off.
|
||||
func (t *LineTable) funcName(off uint32) string {
|
||||
if s, ok := t.funcNames[off]; ok {
|
||||
return s
|
||||
}
|
||||
i := bytes.IndexByte(t.funcnametab[off:], 0)
|
||||
s := string(t.funcnametab[off : off+uint32(i)])
|
||||
t.funcNames[off] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// stringFrom returns a Go string found at off from a position.
|
||||
func (t *LineTable) stringFrom(arr []byte, off uint32) string {
|
||||
if s, ok := t.strings[off]; ok {
|
||||
return s
|
||||
}
|
||||
i := bytes.IndexByte(arr[off:], 0)
|
||||
s := string(arr[off : off+uint32(i)])
|
||||
t.strings[off] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// string returns a Go string found at off.
|
||||
func (t *LineTable) string(off uint32) string {
|
||||
return t.stringFrom(t.funcdata, off)
|
||||
}
|
||||
|
||||
// functabFieldSize returns the size in bytes of a single functab field.
|
||||
func (t *LineTable) functabFieldSize() int {
|
||||
if t.Version >= ver118 {
|
||||
return 4
|
||||
}
|
||||
return int(t.Ptrsize)
|
||||
}
|
||||
|
||||
// funcTab returns t's funcTab.
|
||||
func (t *LineTable) funcTab() funcTab {
|
||||
return funcTab{LineTable: t, sz: t.functabFieldSize()}
|
||||
}
|
||||
|
||||
// funcTab is memory corresponding to a slice of functab structs, followed by an invalid PC.
|
||||
// A functab struct is a PC and a func offset.
|
||||
type funcTab struct {
|
||||
*LineTable
|
||||
sz int // cached result of t.functabFieldSize
|
||||
}
|
||||
|
||||
// Count returns the number of func entries in f.
|
||||
func (f funcTab) Count() int {
|
||||
return int(f.nfunctab)
|
||||
}
|
||||
|
||||
// pc returns the PC of the i'th func in f.
|
||||
func (f funcTab) pc(i int) uint64 {
|
||||
u := f.uint(f.functab[2*i*f.sz:])
|
||||
if f.Version >= ver118 {
|
||||
u += f.textStart
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// funcOff returns the funcdata offset of the i'th func in f.
|
||||
func (f funcTab) funcOff(i int) uint64 {
|
||||
return f.uint(f.functab[(2*i+1)*f.sz:])
|
||||
}
|
||||
|
||||
// uint returns the uint stored at b.
|
||||
func (f funcTab) uint(b []byte) uint64 {
|
||||
if f.sz == 4 {
|
||||
return uint64(f.Binary.Uint32(b))
|
||||
}
|
||||
return f.Binary.Uint64(b)
|
||||
}
|
||||
|
||||
// funcData is memory corresponding to an _func struct.
|
||||
type funcData struct {
|
||||
t *LineTable // LineTable this data is a part of
|
||||
data []byte // raw memory for the function
|
||||
}
|
||||
|
||||
// funcData returns the ith funcData in t.functab.
|
||||
func (t *LineTable) funcData(i uint32) funcData {
|
||||
data := t.funcdata[t.funcTab().funcOff(int(i)):]
|
||||
return funcData{t: t, data: data}
|
||||
}
|
||||
|
||||
// IsZero reports whether f is the zero value.
|
||||
func (f funcData) IsZero() bool {
|
||||
return f.t == nil && f.data == nil
|
||||
}
|
||||
|
||||
// entryPC returns the func's entry PC.
|
||||
func (f *funcData) entryPC() uint64 {
|
||||
// In Go 1.18, the first field of _func changed
|
||||
// from a uintptr entry PC to a uint32 entry offset.
|
||||
if f.t.Version >= ver118 {
|
||||
// TODO: support multiple text sections.
|
||||
// See runtime/symtab.go:(*moduledata).textAddr.
|
||||
return uint64(f.t.Binary.Uint32(f.data)) + f.t.textStart
|
||||
}
|
||||
return f.t.uintptr(f.data)
|
||||
}
|
||||
|
||||
func (f funcData) nameoff() uint32 { return f.field(1) }
|
||||
func (f funcData) deferreturn() uint32 { return f.field(3) }
|
||||
func (f funcData) pcfile() uint32 { return f.field(5) }
|
||||
func (f funcData) pcln() uint32 { return f.field(6) }
|
||||
func (f funcData) cuOffset() uint32 { return f.field(8) }
|
||||
|
||||
// field returns the nth field of the _func struct.
|
||||
// It panics if n == 0 or n > 9; for n == 0, call f.entryPC.
|
||||
// Most callers should use a named field accessor (just above).
|
||||
func (f funcData) field(n uint32) uint32 {
|
||||
if n == 0 || n > 9 {
|
||||
panic("bad funcdata field")
|
||||
}
|
||||
// In Go 1.18, the first field of _func changed
|
||||
// from a uintptr entry PC to a uint32 entry offset.
|
||||
sz0 := f.t.Ptrsize
|
||||
if f.t.Version >= ver118 {
|
||||
sz0 = 4
|
||||
}
|
||||
off := sz0 + (n-1)*4 // subsequent fields are 4 bytes each
|
||||
data := f.data[off:]
|
||||
return f.t.Binary.Uint32(data)
|
||||
}
|
||||
|
||||
// step advances to the next pc, value pair in the encoded table.
|
||||
func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
|
||||
uvdelta := t.readvarint(p)
|
||||
if uvdelta == 0 && !first {
|
||||
return false
|
||||
}
|
||||
if uvdelta&1 != 0 {
|
||||
uvdelta = ^(uvdelta >> 1)
|
||||
} else {
|
||||
uvdelta >>= 1
|
||||
}
|
||||
vdelta := int32(uvdelta)
|
||||
pcdelta := t.readvarint(p) * t.Quantum
|
||||
*pc += uint64(pcdelta)
|
||||
*val += vdelta
|
||||
return true
|
||||
}
|
||||
|
||||
// pcvalue reports the value associated with the target pc.
|
||||
// off is the offset to the beginning of the pc-value table,
|
||||
// and entry is the start PC for the corresponding function.
|
||||
func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 {
|
||||
p := t.pctab[off:]
|
||||
|
||||
val := int32(-1)
|
||||
pc := entry
|
||||
for t.step(&p, &pc, &val, pc == entry) {
|
||||
if targetpc < pc {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// findFileLine scans one function in the binary looking for a
|
||||
// program counter in the given file on the given line.
|
||||
// It does so by running the pc-value tables mapping program counter
|
||||
// to file number. Since most functions come from a single file, these
|
||||
// are usually short and quick to scan. If a file match is found, then the
|
||||
// code goes to the expense of looking for a simultaneous line number match.
|
||||
func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 {
|
||||
if filetab == 0 || linetab == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
fp := t.pctab[filetab:]
|
||||
fl := t.pctab[linetab:]
|
||||
fileVal := int32(-1)
|
||||
filePC := entry
|
||||
lineVal := int32(-1)
|
||||
linePC := entry
|
||||
fileStartPC := filePC
|
||||
for t.step(&fp, &filePC, &fileVal, filePC == entry) {
|
||||
fileIndex := fileVal
|
||||
if t.Version == ver116 || t.Version == ver118 {
|
||||
fileIndex = int32(t.Binary.Uint32(cutab[fileVal*4:]))
|
||||
}
|
||||
if fileIndex == filenum && fileStartPC < filePC {
|
||||
// fileIndex is in effect starting at fileStartPC up to
|
||||
// but not including filePC, and it's the file we want.
|
||||
// Run the PC table looking for a matching line number
|
||||
// or until we reach filePC.
|
||||
lineStartPC := linePC
|
||||
for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) {
|
||||
// lineVal is in effect until linePC, and lineStartPC < filePC.
|
||||
if lineVal == line {
|
||||
if fileStartPC <= lineStartPC {
|
||||
return lineStartPC
|
||||
}
|
||||
if fileStartPC < linePC {
|
||||
return fileStartPC
|
||||
}
|
||||
}
|
||||
lineStartPC = linePC
|
||||
}
|
||||
}
|
||||
fileStartPC = filePC
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// go12PCToLine maps program counter to line number for the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12PCToLine(pc uint64) (line int) {
|
||||
defer func() {
|
||||
if !disableRecover && recover() != nil {
|
||||
line = -1
|
||||
}
|
||||
}()
|
||||
|
||||
f := t.findFunc(pc)
|
||||
if f.IsZero() {
|
||||
return -1
|
||||
}
|
||||
entry := f.entryPC()
|
||||
linetab := f.pcln()
|
||||
return int(t.pcvalue(linetab, entry, pc))
|
||||
}
|
||||
|
||||
// go12PCToFile maps program counter to file name for the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12PCToFile(pc uint64) (file string) {
|
||||
defer func() {
|
||||
if !disableRecover && recover() != nil {
|
||||
file = ""
|
||||
}
|
||||
}()
|
||||
|
||||
f := t.findFunc(pc)
|
||||
if f.IsZero() {
|
||||
return ""
|
||||
}
|
||||
entry := f.entryPC()
|
||||
filetab := f.pcfile()
|
||||
fno := t.pcvalue(filetab, entry, pc)
|
||||
if t.Version == ver12 {
|
||||
if fno <= 0 {
|
||||
return ""
|
||||
}
|
||||
return t.string(t.Binary.Uint32(t.filetab[4*fno:]))
|
||||
}
|
||||
// Go ≥ 1.16
|
||||
if fno < 0 { // 0 is valid for ≥ 1.16
|
||||
return ""
|
||||
}
|
||||
cuoff := f.cuOffset()
|
||||
if fnoff := t.Binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) {
|
||||
return t.stringFrom(t.filetab, fnoff)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
|
||||
defer func() {
|
||||
if !disableRecover && recover() != nil {
|
||||
pc = 0
|
||||
}
|
||||
}()
|
||||
|
||||
t.initFileMap()
|
||||
filenum, ok := t.fileMap[file]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Scan all functions.
|
||||
// If this turns out to be a bottleneck, we could build a map[int32][]int32
|
||||
// mapping file number to a list of functions with code from that file.
|
||||
var cutab []byte
|
||||
for i := uint32(0); i < t.nfunctab; i++ {
|
||||
f := t.funcData(i)
|
||||
entry := f.entryPC()
|
||||
filetab := f.pcfile()
|
||||
linetab := f.pcln()
|
||||
if t.Version == ver116 || t.Version == ver118 {
|
||||
cutab = t.cutab[f.cuOffset()*4:]
|
||||
}
|
||||
pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab)
|
||||
if pc != 0 {
|
||||
return pc
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// initFileMap initializes the map from file name to file number.
|
||||
func (t *LineTable) initFileMap() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.fileMap != nil {
|
||||
return
|
||||
}
|
||||
m := make(map[string]uint32)
|
||||
|
||||
if t.Version == ver12 {
|
||||
for i := uint32(1); i < t.nfiletab; i++ {
|
||||
s := t.string(t.Binary.Uint32(t.filetab[4*i:]))
|
||||
m[s] = i
|
||||
}
|
||||
} else {
|
||||
var pos uint32
|
||||
for i := uint32(0); i < t.nfiletab; i++ {
|
||||
s := t.stringFrom(t.filetab, pos)
|
||||
m[s] = pos
|
||||
pos += uint32(len(s) + 1)
|
||||
}
|
||||
}
|
||||
t.fileMap = m
|
||||
}
|
||||
|
||||
// go12MapFiles adds to m a key for every file in the Go 1.2 LineTable.
|
||||
// Every key maps to obj. That's not a very interesting map, but it provides
|
||||
// a way for callers to obtain the list of files in the program.
|
||||
func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) {
|
||||
if !disableRecover {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
}
|
||||
|
||||
t.initFileMap()
|
||||
for file := range t.fileMap {
|
||||
m[file] = obj
|
||||
}
|
||||
}
|
||||
|
||||
// disableRecover causes this package not to swallow panics.
|
||||
// This is useful when making changes.
|
||||
const disableRecover = false
|
||||
@@ -0,0 +1,766 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Package gosym implements access to the Go symbol
|
||||
// and line number tables embedded in Go binaries generated
|
||||
// by the gc compilers.
|
||||
package gosym
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
* Symbols
|
||||
*/
|
||||
|
||||
// A Sym represents a single symbol table entry.
|
||||
type Sym struct {
|
||||
Value uint64
|
||||
Type byte
|
||||
Name string
|
||||
GoType uint64
|
||||
// If this symbol is a function symbol, the corresponding Func
|
||||
Func *Func
|
||||
}
|
||||
|
||||
// Static reports whether this symbol is static (not visible outside its file).
|
||||
func (s *Sym) Static() bool { return s.Type >= 'a' }
|
||||
|
||||
// nameWithoutInst returns s.Name if s.Name has no brackets (does not reference an
|
||||
// instantiated type, function, or method). If s.Name contains brackets, then it
|
||||
// returns s.Name with all the contents between (and including) the outermost left
|
||||
// and right bracket removed. This is useful to ignore any extra slashes or dots
|
||||
// inside the brackets from the string searches below, where needed.
|
||||
func (s *Sym) nameWithoutInst() string {
|
||||
start := strings.Index(s.Name, "[")
|
||||
if start < 0 {
|
||||
return s.Name
|
||||
}
|
||||
end := strings.LastIndex(s.Name, "]")
|
||||
if end < 0 {
|
||||
// Malformed name, should contain closing bracket too.
|
||||
return s.Name
|
||||
}
|
||||
return s.Name[0:start] + s.Name[end+1:]
|
||||
}
|
||||
|
||||
// PackageName returns the package part of the symbol name,
|
||||
// or the empty string if there is none.
|
||||
func (s *Sym) PackageName() string {
|
||||
name := s.nameWithoutInst()
|
||||
|
||||
// A prefix of "type." and "go." is a compiler-generated symbol that doesn't belong to any package.
|
||||
// See variable reservedimports in cmd/compile/internal/gc/subr.go
|
||||
if strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.") {
|
||||
return ""
|
||||
}
|
||||
|
||||
pathend := strings.LastIndex(name, "/")
|
||||
if pathend < 0 {
|
||||
pathend = 0
|
||||
}
|
||||
|
||||
if i := strings.Index(name[pathend:], "."); i != -1 {
|
||||
return name[:pathend+i]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ReceiverName returns the receiver type name of this symbol,
|
||||
// or the empty string if there is none. A receiver name is only detected in
|
||||
// the case that s.Name is fully-specified with a package name.
|
||||
func (s *Sym) ReceiverName() string {
|
||||
name := s.nameWithoutInst()
|
||||
// If we find a slash in name, it should precede any bracketed expression
|
||||
// that was removed, so pathend will apply correctly to name and s.Name.
|
||||
pathend := strings.LastIndex(name, "/")
|
||||
if pathend < 0 {
|
||||
pathend = 0
|
||||
}
|
||||
// Find the first dot after pathend (or from the beginning, if there was
|
||||
// no slash in name).
|
||||
l := strings.Index(name[pathend:], ".")
|
||||
// Find the last dot after pathend (or the beginnng).
|
||||
r := strings.LastIndex(name[pathend:], ".")
|
||||
if l == -1 || r == -1 || l == r {
|
||||
// There is no receiver if we didn't find two distinct dots after pathend.
|
||||
return ""
|
||||
}
|
||||
// Given there is a trailing '.' that is in name, find it now in s.Name.
|
||||
// pathend+l should apply to s.Name, because it should be the dot in the
|
||||
// package name.
|
||||
r = strings.LastIndex(s.Name[pathend:], ".")
|
||||
return s.Name[pathend+l+1 : pathend+r]
|
||||
}
|
||||
|
||||
// BaseName returns the symbol name without the package or receiver name.
|
||||
func (s *Sym) BaseName() string {
|
||||
name := s.nameWithoutInst()
|
||||
if i := strings.LastIndex(name, "."); i != -1 {
|
||||
if s.Name != name {
|
||||
brack := strings.Index(s.Name, "[")
|
||||
if i > brack {
|
||||
// BaseName is a method name after the brackets, so
|
||||
// recalculate for s.Name. Otherwise, i applies
|
||||
// correctly to s.Name, since it is before the
|
||||
// brackets.
|
||||
i = strings.LastIndex(s.Name, ".")
|
||||
}
|
||||
}
|
||||
return s.Name[i+1:]
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// A Func collects information about a single function.
|
||||
type Func struct {
|
||||
Entry uint64
|
||||
*Sym
|
||||
End uint64
|
||||
Params []*Sym // nil for Go 1.3 and later binaries
|
||||
Locals []*Sym // nil for Go 1.3 and later binaries
|
||||
FrameSize int
|
||||
LineTable *LineTable
|
||||
Obj *Obj
|
||||
}
|
||||
|
||||
// An Obj represents a collection of functions in a symbol table.
|
||||
//
|
||||
// The exact method of division of a binary into separate Objs is an internal detail
|
||||
// of the symbol table format.
|
||||
//
|
||||
// In early versions of Go each source file became a different Obj.
|
||||
//
|
||||
// In Go 1 and Go 1.1, each package produced one Obj for all Go sources
|
||||
// and one Obj per C source file.
|
||||
//
|
||||
// In Go 1.2, there is a single Obj for the entire program.
|
||||
type Obj struct {
|
||||
// Funcs is a list of functions in the Obj.
|
||||
Funcs []Func
|
||||
|
||||
// In Go 1.1 and earlier, Paths is a list of symbols corresponding
|
||||
// to the source file names that produced the Obj.
|
||||
// In Go 1.2, Paths is nil.
|
||||
// Use the keys of Table.Files to obtain a list of source files.
|
||||
Paths []Sym // meta
|
||||
}
|
||||
|
||||
/*
|
||||
* Symbol tables
|
||||
*/
|
||||
|
||||
// Table represents a Go symbol table. It stores all of the
|
||||
// symbols decoded from the program and provides methods to translate
|
||||
// between symbols, names, and addresses.
|
||||
type Table struct {
|
||||
Syms []Sym // nil for Go 1.3 and later binaries
|
||||
Funcs []Func
|
||||
Files map[string]*Obj // for Go 1.2 and later all files map to one Obj
|
||||
Objs []Obj // for Go 1.2 and later only one Obj in slice
|
||||
|
||||
Go12line *LineTable // Go 1.2 line number table
|
||||
}
|
||||
|
||||
type sym struct {
|
||||
value uint64
|
||||
gotype uint64
|
||||
typ byte
|
||||
name []byte
|
||||
}
|
||||
|
||||
var (
|
||||
littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
|
||||
bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
|
||||
oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
|
||||
)
|
||||
|
||||
func walksymtab(data []byte, fn func(sym) error) error {
|
||||
if len(data) == 0 { // missing symtab is okay
|
||||
return nil
|
||||
}
|
||||
var order binary.ByteOrder = binary.BigEndian
|
||||
newTable := false
|
||||
switch {
|
||||
case bytes.HasPrefix(data, oldLittleEndianSymtab):
|
||||
// Same as Go 1.0, but little endian.
|
||||
// Format was used during interim development between Go 1.0 and Go 1.1.
|
||||
// Should not be widespread, but easy to support.
|
||||
data = data[6:]
|
||||
order = binary.LittleEndian
|
||||
case bytes.HasPrefix(data, bigEndianSymtab):
|
||||
newTable = true
|
||||
case bytes.HasPrefix(data, littleEndianSymtab):
|
||||
newTable = true
|
||||
order = binary.LittleEndian
|
||||
}
|
||||
var ptrsz int
|
||||
if newTable {
|
||||
if len(data) < 8 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
ptrsz = int(data[7])
|
||||
if ptrsz != 4 && ptrsz != 8 {
|
||||
return &DecodingError{7, "invalid pointer size", ptrsz}
|
||||
}
|
||||
data = data[8:]
|
||||
}
|
||||
var s sym
|
||||
p := data
|
||||
for len(p) >= 4 {
|
||||
var typ byte
|
||||
if newTable {
|
||||
// Symbol type, value, Go type.
|
||||
typ = p[0] & 0x3F
|
||||
wideValue := p[0]&0x40 != 0
|
||||
goType := p[0]&0x80 != 0
|
||||
if typ < 26 {
|
||||
typ += 'A'
|
||||
} else {
|
||||
typ += 'a' - 26
|
||||
}
|
||||
s.typ = typ
|
||||
p = p[1:]
|
||||
if wideValue {
|
||||
if len(p) < ptrsz {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// fixed-width value
|
||||
if ptrsz == 8 {
|
||||
s.value = order.Uint64(p[0:8])
|
||||
p = p[8:]
|
||||
} else {
|
||||
s.value = uint64(order.Uint32(p[0:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
} else {
|
||||
// varint value
|
||||
s.value = 0
|
||||
shift := uint(0)
|
||||
for len(p) > 0 && p[0]&0x80 != 0 {
|
||||
s.value |= uint64(p[0]&0x7F) << shift
|
||||
shift += 7
|
||||
p = p[1:]
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
s.value |= uint64(p[0]) << shift
|
||||
p = p[1:]
|
||||
}
|
||||
if goType {
|
||||
if len(p) < ptrsz {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// fixed-width go type
|
||||
if ptrsz == 8 {
|
||||
s.gotype = order.Uint64(p[0:8])
|
||||
p = p[8:]
|
||||
} else {
|
||||
s.gotype = uint64(order.Uint32(p[0:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Value, symbol type.
|
||||
s.value = uint64(order.Uint32(p[0:4]))
|
||||
if len(p) < 5 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
typ = p[4]
|
||||
if typ&0x80 == 0 {
|
||||
return &DecodingError{len(data) - len(p) + 4, "bad symbol type", typ}
|
||||
}
|
||||
typ &^= 0x80
|
||||
s.typ = typ
|
||||
p = p[5:]
|
||||
}
|
||||
|
||||
// Name.
|
||||
var i int
|
||||
var nnul int
|
||||
for i = 0; i < len(p); i++ {
|
||||
if p[i] == 0 {
|
||||
nnul = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
switch typ {
|
||||
case 'z', 'Z':
|
||||
p = p[i+nnul:]
|
||||
for i = 0; i+2 <= len(p); i += 2 {
|
||||
if p[i] == 0 && p[i+1] == 0 {
|
||||
nnul = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p) < i+nnul {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
s.name = p[0:i]
|
||||
i += nnul
|
||||
p = p[i:]
|
||||
|
||||
if !newTable {
|
||||
if len(p) < 4 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// Go type.
|
||||
s.gotype = uint64(order.Uint32(p[:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
fn(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTable decodes the Go symbol table (the ".gosymtab" section in ELF),
|
||||
// returning an in-memory representation.
|
||||
// Starting with Go 1.3, the Go symbol table no longer includes symbol data.
|
||||
func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
|
||||
var n int
|
||||
err := walksymtab(symtab, func(s sym) error {
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t Table
|
||||
if pcln.isGo12() {
|
||||
t.Go12line = pcln
|
||||
}
|
||||
fname := make(map[uint16]string)
|
||||
t.Syms = make([]Sym, 0, n)
|
||||
nf := 0
|
||||
nz := 0
|
||||
lasttyp := uint8(0)
|
||||
err = walksymtab(symtab, func(s sym) error {
|
||||
n := len(t.Syms)
|
||||
t.Syms = t.Syms[0 : n+1]
|
||||
ts := &t.Syms[n]
|
||||
ts.Type = s.typ
|
||||
ts.Value = s.value
|
||||
ts.GoType = s.gotype
|
||||
switch s.typ {
|
||||
default:
|
||||
// rewrite name to use . instead of · (c2 b7)
|
||||
w := 0
|
||||
b := s.name
|
||||
for i := 0; i < len(b); i++ {
|
||||
if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 {
|
||||
i++
|
||||
b[i] = '.'
|
||||
}
|
||||
b[w] = b[i]
|
||||
w++
|
||||
}
|
||||
ts.Name = string(s.name[0:w])
|
||||
case 'z', 'Z':
|
||||
if lasttyp != 'z' && lasttyp != 'Z' {
|
||||
nz++
|
||||
}
|
||||
for i := 0; i < len(s.name); i += 2 {
|
||||
eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
|
||||
elt, ok := fname[eltIdx]
|
||||
if !ok {
|
||||
return &DecodingError{-1, "bad filename code", eltIdx}
|
||||
}
|
||||
if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
|
||||
ts.Name += "/"
|
||||
}
|
||||
ts.Name += elt
|
||||
}
|
||||
}
|
||||
switch s.typ {
|
||||
case 'T', 't', 'L', 'l':
|
||||
nf++
|
||||
case 'f':
|
||||
fname[uint16(s.value)] = ts.Name
|
||||
}
|
||||
lasttyp = s.typ
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Funcs = make([]Func, 0, nf)
|
||||
t.Files = make(map[string]*Obj)
|
||||
|
||||
var obj *Obj
|
||||
if t.Go12line != nil {
|
||||
// Put all functions into one Obj.
|
||||
t.Objs = make([]Obj, 1)
|
||||
obj = &t.Objs[0]
|
||||
t.Go12line.go12MapFiles(t.Files, obj)
|
||||
} else {
|
||||
t.Objs = make([]Obj, 0, nz)
|
||||
}
|
||||
|
||||
// Count text symbols and attach frame sizes, parameters, and
|
||||
// locals to them. Also, find object file boundaries.
|
||||
lastf := 0
|
||||
for i := 0; i < len(t.Syms); i++ {
|
||||
sym := &t.Syms[i]
|
||||
switch sym.Type {
|
||||
case 'Z', 'z': // path symbol
|
||||
if t.Go12line != nil {
|
||||
// Go 1.2 binaries have the file information elsewhere. Ignore.
|
||||
break
|
||||
}
|
||||
// Finish the current object
|
||||
if obj != nil {
|
||||
obj.Funcs = t.Funcs[lastf:]
|
||||
}
|
||||
lastf = len(t.Funcs)
|
||||
|
||||
// Start new object
|
||||
n := len(t.Objs)
|
||||
t.Objs = t.Objs[0 : n+1]
|
||||
obj = &t.Objs[n]
|
||||
|
||||
// Count & copy path symbols
|
||||
var end int
|
||||
for end = i + 1; end < len(t.Syms); end++ {
|
||||
if c := t.Syms[end].Type; c != 'Z' && c != 'z' {
|
||||
break
|
||||
}
|
||||
}
|
||||
obj.Paths = t.Syms[i:end]
|
||||
i = end - 1 // loop will i++
|
||||
|
||||
// Record file names
|
||||
depth := 0
|
||||
for j := range obj.Paths {
|
||||
s := &obj.Paths[j]
|
||||
if s.Name == "" {
|
||||
depth--
|
||||
} else {
|
||||
if depth == 0 {
|
||||
t.Files[s.Name] = obj
|
||||
}
|
||||
depth++
|
||||
}
|
||||
}
|
||||
|
||||
case 'T', 't', 'L', 'l': // text symbol
|
||||
if n := len(t.Funcs); n > 0 {
|
||||
t.Funcs[n-1].End = sym.Value
|
||||
}
|
||||
if sym.Name == "runtime.etext" || sym.Name == "etext" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Count parameter and local (auto) syms
|
||||
var np, na int
|
||||
var end int
|
||||
countloop:
|
||||
for end = i + 1; end < len(t.Syms); end++ {
|
||||
switch t.Syms[end].Type {
|
||||
case 'T', 't', 'L', 'l', 'Z', 'z':
|
||||
break countloop
|
||||
case 'p':
|
||||
np++
|
||||
case 'a':
|
||||
na++
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the function symbol
|
||||
n := len(t.Funcs)
|
||||
t.Funcs = t.Funcs[0 : n+1]
|
||||
fn := &t.Funcs[n]
|
||||
sym.Func = fn
|
||||
fn.Params = make([]*Sym, 0, np)
|
||||
fn.Locals = make([]*Sym, 0, na)
|
||||
fn.Sym = sym
|
||||
fn.Entry = sym.Value
|
||||
fn.Obj = obj
|
||||
if t.Go12line != nil {
|
||||
// All functions share the same line table.
|
||||
// It knows how to narrow down to a specific
|
||||
// function quickly.
|
||||
fn.LineTable = t.Go12line
|
||||
} else if pcln != nil {
|
||||
fn.LineTable = pcln.slice(fn.Entry)
|
||||
pcln = fn.LineTable
|
||||
}
|
||||
for j := i; j < end; j++ {
|
||||
s := &t.Syms[j]
|
||||
switch s.Type {
|
||||
case 'm':
|
||||
fn.FrameSize = int(s.Value)
|
||||
case 'p':
|
||||
n := len(fn.Params)
|
||||
fn.Params = fn.Params[0 : n+1]
|
||||
fn.Params[n] = s
|
||||
case 'a':
|
||||
n := len(fn.Locals)
|
||||
fn.Locals = fn.Locals[0 : n+1]
|
||||
fn.Locals[n] = s
|
||||
}
|
||||
}
|
||||
i = end - 1 // loop will i++
|
||||
}
|
||||
}
|
||||
|
||||
if t.Go12line != nil && nf == 0 {
|
||||
t.Funcs = t.Go12line.go12Funcs()
|
||||
}
|
||||
if obj != nil {
|
||||
obj.Funcs = t.Funcs[lastf:]
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// PCToFunc returns the function containing the program counter pc,
|
||||
// or nil if there is no such function.
|
||||
func (t *Table) PCToFunc(pc uint64) *Func {
|
||||
funcs := t.Funcs
|
||||
for len(funcs) > 0 {
|
||||
m := len(funcs) / 2
|
||||
fn := &funcs[m]
|
||||
switch {
|
||||
case pc < fn.Entry:
|
||||
funcs = funcs[0:m]
|
||||
case fn.Entry <= pc && pc < fn.End:
|
||||
return fn
|
||||
default:
|
||||
funcs = funcs[m+1:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PCToLine looks up line number information for a program counter.
|
||||
// If there is no information, it returns fn == nil.
|
||||
func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) {
|
||||
if fn = t.PCToFunc(pc); fn == nil {
|
||||
return
|
||||
}
|
||||
if t.Go12line != nil {
|
||||
file = t.Go12line.go12PCToFile(pc)
|
||||
line = t.Go12line.go12PCToLine(pc)
|
||||
} else {
|
||||
file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LineToPC looks up the first program counter on the given line in
|
||||
// the named file. It returns UnknownPathError or UnknownLineError if
|
||||
// there is an error looking up this line.
|
||||
func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
|
||||
obj, ok := t.Files[file]
|
||||
if !ok {
|
||||
return 0, nil, UnknownFileError(file)
|
||||
}
|
||||
|
||||
if t.Go12line != nil {
|
||||
pc := t.Go12line.go12LineToPC(file, line)
|
||||
if pc == 0 {
|
||||
return 0, nil, &UnknownLineError{file, line}
|
||||
}
|
||||
return pc, t.PCToFunc(pc), nil
|
||||
}
|
||||
|
||||
abs, err := obj.alineFromLine(file, line)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := range obj.Funcs {
|
||||
f := &obj.Funcs[i]
|
||||
pc := f.LineTable.LineToPC(abs, f.End)
|
||||
if pc != 0 {
|
||||
return pc, f, nil
|
||||
}
|
||||
}
|
||||
return 0, nil, &UnknownLineError{file, line}
|
||||
}
|
||||
|
||||
// LookupSym returns the text, data, or bss symbol with the given name,
|
||||
// or nil if no such symbol is found.
|
||||
func (t *Table) LookupSym(name string) *Sym {
|
||||
// TODO(austin) Maybe make a map
|
||||
for i := range t.Syms {
|
||||
s := &t.Syms[i]
|
||||
switch s.Type {
|
||||
case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupFunc returns the text, data, or bss symbol with the given name,
|
||||
// or nil if no such symbol is found.
|
||||
func (t *Table) LookupFunc(name string) *Func {
|
||||
for i := range t.Funcs {
|
||||
f := &t.Funcs[i]
|
||||
if f.Sym.Name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SymByAddr returns the text, data, or bss symbol starting at the given address.
|
||||
func (t *Table) SymByAddr(addr uint64) *Sym {
|
||||
for i := range t.Syms {
|
||||
s := &t.Syms[i]
|
||||
switch s.Type {
|
||||
case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
|
||||
if s.Value == addr {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Object files
|
||||
*/
|
||||
|
||||
// This is legacy code for Go 1.1 and earlier, which used the
|
||||
// Plan 9 format for pc-line tables. This code was never quite
|
||||
// correct. It's probably very close, and it's usually correct, but
|
||||
// we never quite found all the corner cases.
|
||||
//
|
||||
// Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab.
|
||||
|
||||
func (o *Obj) lineFromAline(aline int) (string, int) {
|
||||
type stackEnt struct {
|
||||
path string
|
||||
start int
|
||||
offset int
|
||||
prev *stackEnt
|
||||
}
|
||||
|
||||
noPath := &stackEnt{"", 0, 0, nil}
|
||||
tos := noPath
|
||||
|
||||
pathloop:
|
||||
for _, s := range o.Paths {
|
||||
val := int(s.Value)
|
||||
switch {
|
||||
case val > aline:
|
||||
break pathloop
|
||||
|
||||
case val == 1:
|
||||
// Start a new stack
|
||||
tos = &stackEnt{s.Name, val, 0, noPath}
|
||||
|
||||
case s.Name == "":
|
||||
// Pop
|
||||
if tos == noPath {
|
||||
return "<malformed symbol table>", 0
|
||||
}
|
||||
tos.prev.offset += val - tos.start
|
||||
tos = tos.prev
|
||||
|
||||
default:
|
||||
// Push
|
||||
tos = &stackEnt{s.Name, val, 0, tos}
|
||||
}
|
||||
}
|
||||
|
||||
if tos == noPath {
|
||||
return "", 0
|
||||
}
|
||||
return tos.path, aline - tos.start - tos.offset + 1
|
||||
}
|
||||
|
||||
func (o *Obj) alineFromLine(path string, line int) (int, error) {
|
||||
if line < 1 {
|
||||
return 0, &UnknownLineError{path, line}
|
||||
}
|
||||
|
||||
for i, s := range o.Paths {
|
||||
// Find this path
|
||||
if s.Name != path {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find this line at this stack level
|
||||
depth := 0
|
||||
var incstart int
|
||||
line += int(s.Value)
|
||||
pathloop:
|
||||
for _, s := range o.Paths[i:] {
|
||||
val := int(s.Value)
|
||||
switch {
|
||||
case depth == 1 && val >= line:
|
||||
return line - 1, nil
|
||||
|
||||
case s.Name == "":
|
||||
depth--
|
||||
if depth == 0 {
|
||||
break pathloop
|
||||
} else if depth == 1 {
|
||||
line += val - incstart
|
||||
}
|
||||
|
||||
default:
|
||||
if depth == 1 {
|
||||
incstart = val
|
||||
}
|
||||
depth++
|
||||
}
|
||||
}
|
||||
return 0, &UnknownLineError{path, line}
|
||||
}
|
||||
return 0, UnknownFileError(path)
|
||||
}
|
||||
|
||||
/*
|
||||
* Errors
|
||||
*/
|
||||
|
||||
// UnknownFileError represents a failure to find the specific file in
|
||||
// the symbol table.
|
||||
type UnknownFileError string
|
||||
|
||||
func (e UnknownFileError) Error() string { return "unknown file: " + string(e) }
|
||||
|
||||
// UnknownLineError represents a failure to map a line to a program
|
||||
// counter, either because the line is beyond the bounds of the file
|
||||
// or because there is no code on the given line.
|
||||
type UnknownLineError struct {
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
func (e *UnknownLineError) Error() string {
|
||||
return "no code at " + e.File + ":" + strconv.Itoa(e.Line)
|
||||
}
|
||||
|
||||
// DecodingError represents an error during the decoding of
|
||||
// the symbol table.
|
||||
type DecodingError struct {
|
||||
off int
|
||||
msg string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func (e *DecodingError) Error() string {
|
||||
msg := e.msg
|
||||
if e.val != nil {
|
||||
msg += fmt.Sprintf(" '%v'", e.val)
|
||||
}
|
||||
msg += fmt.Sprintf(" at byte %#x", e.off)
|
||||
return msg
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package macho
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A FatFile is a Mach-O universal binary that contains at least one architecture.
|
||||
type FatFile struct {
|
||||
Magic uint32
|
||||
Arches []FatArch
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
// A FatArchHeader represents a fat header for a specific image architecture.
|
||||
type FatArchHeader struct {
|
||||
Cpu Cpu
|
||||
SubCpu uint32
|
||||
Offset uint32
|
||||
Size uint32
|
||||
Align uint32
|
||||
}
|
||||
|
||||
const fatArchHeaderSize = 5 * 4
|
||||
|
||||
// A FatArch is a Mach-O File inside a FatFile.
|
||||
type FatArch struct {
|
||||
FatArchHeader
|
||||
*File
|
||||
}
|
||||
|
||||
// ErrNotFat is returned from NewFatFile or OpenFat when the file is not a
|
||||
// universal binary but may be a thin binary, based on its magic number.
|
||||
var ErrNotFat = &FormatError{0, "not a fat Mach-O file", nil}
|
||||
|
||||
// NewFatFile creates a new FatFile for accessing all the Mach-O images in a
|
||||
// universal binary. The Mach-O binary is expected to start at position 0 in
|
||||
// the ReaderAt.
|
||||
func NewFatFile(r io.ReaderAt) (*FatFile, error) {
|
||||
var ff FatFile
|
||||
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
||||
|
||||
// Read the fat_header struct, which is always in big endian.
|
||||
// Start with the magic number.
|
||||
err := binary.Read(sr, binary.BigEndian, &ff.Magic)
|
||||
if err != nil {
|
||||
return nil, &FormatError{0, "error reading magic number", nil}
|
||||
} else if ff.Magic != MagicFat {
|
||||
// See if this is a Mach-O file via its magic number. The magic
|
||||
// must be converted to little endian first though.
|
||||
var buf [4]byte
|
||||
binary.BigEndian.PutUint32(buf[:], ff.Magic)
|
||||
leMagic := binary.LittleEndian.Uint32(buf[:])
|
||||
if leMagic == Magic32 || leMagic == Magic64 {
|
||||
return nil, ErrNotFat
|
||||
} else {
|
||||
return nil, &FormatError{0, "invalid magic number", nil}
|
||||
}
|
||||
}
|
||||
offset := int64(4)
|
||||
|
||||
// Read the number of FatArchHeaders that come after the fat_header.
|
||||
var narch uint32
|
||||
err = binary.Read(sr, binary.BigEndian, &narch)
|
||||
if err != nil {
|
||||
return nil, &FormatError{offset, "invalid fat_header", nil}
|
||||
}
|
||||
offset += 4
|
||||
|
||||
if narch < 1 {
|
||||
return nil, &FormatError{offset, "file contains no images", nil}
|
||||
}
|
||||
|
||||
// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
|
||||
// there are not duplicate architectures.
|
||||
seenArches := make(map[uint64]bool, narch)
|
||||
// Make sure that all images are for the same MH_ type.
|
||||
var machoType Type
|
||||
|
||||
// Following the fat_header comes narch fat_arch structs that index
|
||||
// Mach-O images further in the file.
|
||||
ff.Arches = make([]FatArch, narch)
|
||||
for i := uint32(0); i < narch; i++ {
|
||||
fa := &ff.Arches[i]
|
||||
err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
|
||||
if err != nil {
|
||||
return nil, &FormatError{offset, "invalid fat_arch header", nil}
|
||||
}
|
||||
offset += fatArchHeaderSize
|
||||
|
||||
fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
|
||||
fa.File, err = NewFile(fr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the architecture for this image is not duplicate.
|
||||
seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
|
||||
if o, k := seenArches[seenArch]; o || k {
|
||||
return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil}
|
||||
}
|
||||
seenArches[seenArch] = true
|
||||
|
||||
// Make sure the Mach-O type matches that of the first image.
|
||||
if i == 0 {
|
||||
machoType = fa.Type
|
||||
} else {
|
||||
if fa.Type != machoType {
|
||||
return nil, &FormatError{offset, fmt.Sprintf("Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType), nil}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ff, nil
|
||||
}
|
||||
|
||||
// OpenFat opens the named file using os.Open and prepares it for use as a Mach-O
|
||||
// universal binary.
|
||||
func OpenFat(name string) (*FatFile, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ff, err := NewFatFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
ff.closer = f
|
||||
return ff, nil
|
||||
}
|
||||
|
||||
func (ff *FatFile) Close() error {
|
||||
var err error
|
||||
if ff.closer != nil {
|
||||
err = ff.closer.Close()
|
||||
ff.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Package macho implements access to Mach-O object files.
|
||||
package macho
|
||||
|
||||
// High level access to low level data structures.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mandiant/GoReSym/debug/dwarf"
|
||||
)
|
||||
|
||||
// A File represents an open Mach-O file.
|
||||
type File struct {
|
||||
FileHeader
|
||||
ByteOrder binary.ByteOrder
|
||||
Loads []Load
|
||||
Sections []*Section
|
||||
|
||||
Symtab *Symtab
|
||||
Dysymtab *Dysymtab
|
||||
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
// A Load represents any Mach-O load command.
|
||||
type Load interface {
|
||||
Raw() []byte
|
||||
}
|
||||
|
||||
// A LoadBytes is the uninterpreted bytes of a Mach-O load command.
|
||||
type LoadBytes []byte
|
||||
|
||||
func (b LoadBytes) Raw() []byte { return b }
|
||||
|
||||
// A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command.
|
||||
type SegmentHeader struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Name string
|
||||
Addr uint64
|
||||
Memsz uint64
|
||||
Offset uint64
|
||||
Filesz uint64
|
||||
Maxprot uint32
|
||||
Prot uint32
|
||||
Nsect uint32
|
||||
Flag uint32
|
||||
}
|
||||
|
||||
// A Segment represents a Mach-O 32-bit or 64-bit load segment command.
|
||||
type Segment struct {
|
||||
LoadBytes
|
||||
SegmentHeader
|
||||
|
||||
// Embed ReaderAt for ReadAt method.
|
||||
// Do not embed SectionReader directly
|
||||
// to avoid having Read and Seek.
|
||||
// If a client wants Read and Seek it must use
|
||||
// Open() to avoid fighting over the seek offset
|
||||
// with other clients.
|
||||
io.ReaderAt
|
||||
sr *io.SectionReader
|
||||
}
|
||||
|
||||
// Data reads and returns the contents of the segment.
|
||||
func (s *Segment) Data() ([]byte, error) {
|
||||
dat := make([]byte, s.sr.Size())
|
||||
n, err := s.sr.ReadAt(dat, 0)
|
||||
if n == len(dat) {
|
||||
err = nil
|
||||
}
|
||||
return dat[0:n], err
|
||||
}
|
||||
|
||||
// Open returns a new ReadSeeker reading the segment.
|
||||
func (s *Segment) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
|
||||
|
||||
type SectionHeader struct {
|
||||
Name string
|
||||
Seg string
|
||||
Addr uint64
|
||||
Size uint64
|
||||
Offset uint32
|
||||
Align uint32
|
||||
Reloff uint32
|
||||
Nreloc uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// A Reloc represents a Mach-O relocation.
|
||||
type Reloc struct {
|
||||
Addr uint32
|
||||
Value uint32
|
||||
// when Scattered == false && Extern == true, Value is the symbol number.
|
||||
// when Scattered == false && Extern == false, Value is the section number.
|
||||
// when Scattered == true, Value is the value that this reloc refers to.
|
||||
Type uint8
|
||||
Len uint8 // 0=byte, 1=word, 2=long, 3=quad
|
||||
Pcrel bool
|
||||
Extern bool // valid if Scattered == false
|
||||
Scattered bool
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
SectionHeader
|
||||
Relocs []Reloc
|
||||
|
||||
// Embed ReaderAt for ReadAt method.
|
||||
// Do not embed SectionReader directly
|
||||
// to avoid having Read and Seek.
|
||||
// If a client wants Read and Seek it must use
|
||||
// Open() to avoid fighting over the seek offset
|
||||
// with other clients.
|
||||
io.ReaderAt
|
||||
sr *io.SectionReader
|
||||
}
|
||||
|
||||
// Data reads and returns the contents of the Mach-O section.
|
||||
func (s *Section) Data() ([]byte, error) {
|
||||
dat := make([]byte, s.sr.Size())
|
||||
n, err := s.sr.ReadAt(dat, 0)
|
||||
if n == len(dat) {
|
||||
err = nil
|
||||
}
|
||||
return dat[0:n], err
|
||||
}
|
||||
|
||||
// Open returns a new ReadSeeker reading the Mach-O section.
|
||||
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
|
||||
|
||||
// A Dylib represents a Mach-O load dynamic library command.
|
||||
type Dylib struct {
|
||||
LoadBytes
|
||||
Name string
|
||||
Time uint32
|
||||
CurrentVersion uint32
|
||||
CompatVersion uint32
|
||||
}
|
||||
|
||||
// A Symtab represents a Mach-O symbol table command.
|
||||
type Symtab struct {
|
||||
LoadBytes
|
||||
SymtabCmd
|
||||
Syms []Symbol
|
||||
}
|
||||
|
||||
// A Dysymtab represents a Mach-O dynamic symbol table command.
|
||||
type Dysymtab struct {
|
||||
LoadBytes
|
||||
DysymtabCmd
|
||||
IndirectSyms []uint32 // indices into Symtab.Syms
|
||||
}
|
||||
|
||||
// A Rpath represents a Mach-O rpath command.
|
||||
type Rpath struct {
|
||||
LoadBytes
|
||||
Path string
|
||||
}
|
||||
|
||||
// A Symbol is a Mach-O 32-bit or 64-bit symbol table entry.
|
||||
type Symbol struct {
|
||||
Name string
|
||||
Type uint8
|
||||
Sect uint8
|
||||
Desc uint16
|
||||
Value uint64
|
||||
}
|
||||
|
||||
/*
|
||||
* Mach-O reader
|
||||
*/
|
||||
|
||||
// FormatError is returned by some operations if the data does
|
||||
// not have the correct format for an object file.
|
||||
type FormatError struct {
|
||||
off int64
|
||||
msg string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func (e *FormatError) Error() string {
|
||||
msg := e.msg
|
||||
if e.val != nil {
|
||||
msg += fmt.Sprintf(" '%v'", e.val)
|
||||
}
|
||||
msg += fmt.Sprintf(" in record at byte %#x", e.off)
|
||||
return msg
|
||||
}
|
||||
|
||||
// Open opens the named file using os.Open and prepares it for use as a Mach-O binary.
|
||||
func Open(name string) (*File, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ff, err := NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
ff.closer = f
|
||||
return ff, nil
|
||||
}
|
||||
|
||||
// Close closes the File.
|
||||
// If the File was created using NewFile directly instead of Open,
|
||||
// Close has no effect.
|
||||
func (f *File) Close() error {
|
||||
var err error
|
||||
if f.closer != nil {
|
||||
err = f.closer.Close()
|
||||
f.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewFile creates a new File for accessing a Mach-O binary in an underlying reader.
|
||||
// The Mach-O binary is expected to start at position 0 in the ReaderAt.
|
||||
func NewFile(r io.ReaderAt) (*File, error) {
|
||||
f := new(File)
|
||||
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
||||
|
||||
// Read and decode Mach magic to determine byte order, size.
|
||||
// Magic32 and Magic64 differ only in the bottom bit.
|
||||
var ident [4]byte
|
||||
if _, err := r.ReadAt(ident[0:], 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
be := binary.BigEndian.Uint32(ident[0:])
|
||||
le := binary.LittleEndian.Uint32(ident[0:])
|
||||
switch Magic32 &^ 1 {
|
||||
case be &^ 1:
|
||||
f.ByteOrder = binary.BigEndian
|
||||
f.Magic = be
|
||||
case le &^ 1:
|
||||
f.ByteOrder = binary.LittleEndian
|
||||
f.Magic = le
|
||||
default:
|
||||
return nil, &FormatError{0, "invalid magic number", nil}
|
||||
}
|
||||
|
||||
// Read entire file header.
|
||||
if err := binary.Read(sr, f.ByteOrder, &f.FileHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Then load commands.
|
||||
offset := int64(fileHeaderSize32)
|
||||
if f.Magic == Magic64 {
|
||||
offset = fileHeaderSize64
|
||||
}
|
||||
dat := make([]byte, f.Cmdsz)
|
||||
if _, err := r.ReadAt(dat, offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Loads = make([]Load, f.Ncmd)
|
||||
bo := f.ByteOrder
|
||||
for i := range f.Loads {
|
||||
// Each load command begins with uint32 command and length.
|
||||
if len(dat) < 8 {
|
||||
return nil, &FormatError{offset, "command block too small", nil}
|
||||
}
|
||||
cmd, siz := LoadCmd(bo.Uint32(dat[0:4])), bo.Uint32(dat[4:8])
|
||||
if siz < 8 || siz > uint32(len(dat)) {
|
||||
return nil, &FormatError{offset, "invalid command block size", nil}
|
||||
}
|
||||
var cmddat []byte
|
||||
cmddat, dat = dat[0:siz], dat[siz:]
|
||||
offset += int64(siz)
|
||||
var s *Segment
|
||||
switch cmd {
|
||||
default:
|
||||
f.Loads[i] = LoadBytes(cmddat)
|
||||
|
||||
case LoadCmdRpath:
|
||||
var hdr RpathCmd
|
||||
b := bytes.NewReader(cmddat)
|
||||
if err := binary.Read(b, bo, &hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := new(Rpath)
|
||||
if hdr.Path >= uint32(len(cmddat)) {
|
||||
return nil, &FormatError{offset, "invalid path in rpath command", hdr.Path}
|
||||
}
|
||||
l.Path = cstring(cmddat[hdr.Path:])
|
||||
l.LoadBytes = LoadBytes(cmddat)
|
||||
f.Loads[i] = l
|
||||
|
||||
case LoadCmdDylib:
|
||||
var hdr DylibCmd
|
||||
b := bytes.NewReader(cmddat)
|
||||
if err := binary.Read(b, bo, &hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := new(Dylib)
|
||||
if hdr.Name >= uint32(len(cmddat)) {
|
||||
return nil, &FormatError{offset, "invalid name in dynamic library command", hdr.Name}
|
||||
}
|
||||
l.Name = cstring(cmddat[hdr.Name:])
|
||||
l.Time = hdr.Time
|
||||
l.CurrentVersion = hdr.CurrentVersion
|
||||
l.CompatVersion = hdr.CompatVersion
|
||||
l.LoadBytes = LoadBytes(cmddat)
|
||||
f.Loads[i] = l
|
||||
|
||||
case LoadCmdSymtab:
|
||||
var hdr SymtabCmd
|
||||
b := bytes.NewReader(cmddat)
|
||||
if err := binary.Read(b, bo, &hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
strtab := make([]byte, hdr.Strsize)
|
||||
if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var symsz int
|
||||
if f.Magic == Magic64 {
|
||||
symsz = 16
|
||||
} else {
|
||||
symsz = 12
|
||||
}
|
||||
symdat := make([]byte, int(hdr.Nsyms)*symsz)
|
||||
if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Loads[i] = st
|
||||
f.Symtab = st
|
||||
|
||||
case LoadCmdDysymtab:
|
||||
var hdr DysymtabCmd
|
||||
b := bytes.NewReader(cmddat)
|
||||
if err := binary.Read(b, bo, &hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dat := make([]byte, hdr.Nindirectsyms*4)
|
||||
if _, err := r.ReadAt(dat, int64(hdr.Indirectsymoff)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := make([]uint32, hdr.Nindirectsyms)
|
||||
if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st := new(Dysymtab)
|
||||
st.LoadBytes = LoadBytes(cmddat)
|
||||
st.DysymtabCmd = hdr
|
||||
st.IndirectSyms = x
|
||||
f.Loads[i] = st
|
||||
f.Dysymtab = st
|
||||
|
||||
case LoadCmdSegment:
|
||||
var seg32 Segment32
|
||||
b := bytes.NewReader(cmddat)
|
||||
if err := binary.Read(b, bo, &seg32); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s = new(Segment)
|
||||
s.LoadBytes = cmddat
|
||||
s.Cmd = cmd
|
||||
s.Len = siz
|
||||
s.Name = cstring(seg32.Name[0:])
|
||||
s.Addr = uint64(seg32.Addr)
|
||||
s.Memsz = uint64(seg32.Memsz)
|
||||
s.Offset = uint64(seg32.Offset)
|
||||
s.Filesz = uint64(seg32.Filesz)
|
||||
s.Maxprot = seg32.Maxprot
|
||||
s.Prot = seg32.Prot
|
||||
s.Nsect = seg32.Nsect
|
||||
s.Flag = seg32.Flag
|
||||
f.Loads[i] = s
|
||||
for i := 0; i < int(s.Nsect); i++ {
|
||||
var sh32 Section32
|
||||
if err := binary.Read(b, bo, &sh32); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh := new(Section)
|
||||
sh.Name = cstring(sh32.Name[0:])
|
||||
sh.Seg = cstring(sh32.Seg[0:])
|
||||
sh.Addr = uint64(sh32.Addr)
|
||||
sh.Size = uint64(sh32.Size)
|
||||
sh.Offset = sh32.Offset
|
||||
sh.Align = sh32.Align
|
||||
sh.Reloff = sh32.Reloff
|
||||
sh.Nreloc = sh32.Nreloc
|
||||
sh.Flags = sh32.Flags
|
||||
if err := f.pushSection(sh, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
case LoadCmdSegment64:
|
||||
var seg64 Segment64
|
||||
b := bytes.NewReader(cmddat)
|
||||
if err := binary.Read(b, bo, &seg64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s = new(Segment)
|
||||
s.LoadBytes = cmddat
|
||||
s.Cmd = cmd
|
||||
s.Len = siz
|
||||
s.Name = cstring(seg64.Name[0:])
|
||||
s.Addr = seg64.Addr
|
||||
s.Memsz = seg64.Memsz
|
||||
s.Offset = seg64.Offset
|
||||
s.Filesz = seg64.Filesz
|
||||
s.Maxprot = seg64.Maxprot
|
||||
s.Prot = seg64.Prot
|
||||
s.Nsect = seg64.Nsect
|
||||
s.Flag = seg64.Flag
|
||||
f.Loads[i] = s
|
||||
for i := 0; i < int(s.Nsect); i++ {
|
||||
var sh64 Section64
|
||||
if err := binary.Read(b, bo, &sh64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh := new(Section)
|
||||
sh.Name = cstring(sh64.Name[0:])
|
||||
sh.Seg = cstring(sh64.Seg[0:])
|
||||
sh.Addr = sh64.Addr
|
||||
sh.Size = sh64.Size
|
||||
sh.Offset = sh64.Offset
|
||||
sh.Align = sh64.Align
|
||||
sh.Reloff = sh64.Reloff
|
||||
sh.Nreloc = sh64.Nreloc
|
||||
sh.Flags = sh64.Flags
|
||||
if err := f.pushSection(sh, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if s != nil {
|
||||
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
|
||||
s.ReaderAt = s.sr
|
||||
}
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *SymtabCmd, offset int64) (*Symtab, error) {
|
||||
bo := f.ByteOrder
|
||||
symtab := make([]Symbol, hdr.Nsyms)
|
||||
b := bytes.NewReader(symdat)
|
||||
for i := range symtab {
|
||||
var n Nlist64
|
||||
if f.Magic == Magic64 {
|
||||
if err := binary.Read(b, bo, &n); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
var n32 Nlist32
|
||||
if err := binary.Read(b, bo, &n32); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.Name = n32.Name
|
||||
n.Type = n32.Type
|
||||
n.Sect = n32.Sect
|
||||
n.Desc = n32.Desc
|
||||
n.Value = uint64(n32.Value)
|
||||
}
|
||||
sym := &symtab[i]
|
||||
if n.Name >= uint32(len(strtab)) {
|
||||
return nil, &FormatError{offset, "invalid name in symbol table", n.Name}
|
||||
}
|
||||
// We add "_" to Go symbols. Strip it here. See issue 33808.
|
||||
name := cstring(strtab[n.Name:])
|
||||
if strings.Contains(name, ".") && name[0] == '_' {
|
||||
name = name[1:]
|
||||
}
|
||||
sym.Name = name
|
||||
sym.Type = n.Type
|
||||
sym.Sect = n.Sect
|
||||
sym.Desc = n.Desc
|
||||
sym.Value = n.Value
|
||||
}
|
||||
st := new(Symtab)
|
||||
st.LoadBytes = LoadBytes(cmddat)
|
||||
st.Syms = symtab
|
||||
return st, nil
|
||||
}
|
||||
|
||||
type relocInfo struct {
|
||||
Addr uint32
|
||||
Symnum uint32
|
||||
}
|
||||
|
||||
func (f *File) pushSection(sh *Section, r io.ReaderAt) error {
|
||||
f.Sections = append(f.Sections, sh)
|
||||
sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size))
|
||||
sh.ReaderAt = sh.sr
|
||||
|
||||
if sh.Nreloc > 0 {
|
||||
reldat := make([]byte, int(sh.Nreloc)*8)
|
||||
if _, err := r.ReadAt(reldat, int64(sh.Reloff)); err != nil {
|
||||
return err
|
||||
}
|
||||
b := bytes.NewReader(reldat)
|
||||
|
||||
bo := f.ByteOrder
|
||||
|
||||
sh.Relocs = make([]Reloc, sh.Nreloc)
|
||||
for i := range sh.Relocs {
|
||||
rel := &sh.Relocs[i]
|
||||
|
||||
var ri relocInfo
|
||||
if err := binary.Read(b, bo, &ri); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ri.Addr&(1<<31) != 0 { // scattered
|
||||
rel.Addr = ri.Addr & (1<<24 - 1)
|
||||
rel.Type = uint8((ri.Addr >> 24) & (1<<4 - 1))
|
||||
rel.Len = uint8((ri.Addr >> 28) & (1<<2 - 1))
|
||||
rel.Pcrel = ri.Addr&(1<<30) != 0
|
||||
rel.Value = ri.Symnum
|
||||
rel.Scattered = true
|
||||
} else {
|
||||
switch bo {
|
||||
case binary.LittleEndian:
|
||||
rel.Addr = ri.Addr
|
||||
rel.Value = ri.Symnum & (1<<24 - 1)
|
||||
rel.Pcrel = ri.Symnum&(1<<24) != 0
|
||||
rel.Len = uint8((ri.Symnum >> 25) & (1<<2 - 1))
|
||||
rel.Extern = ri.Symnum&(1<<27) != 0
|
||||
rel.Type = uint8((ri.Symnum >> 28) & (1<<4 - 1))
|
||||
case binary.BigEndian:
|
||||
rel.Addr = ri.Addr
|
||||
rel.Value = ri.Symnum >> 8
|
||||
rel.Pcrel = ri.Symnum&(1<<7) != 0
|
||||
rel.Len = uint8((ri.Symnum >> 5) & (1<<2 - 1))
|
||||
rel.Extern = ri.Symnum&(1<<4) != 0
|
||||
rel.Type = uint8(ri.Symnum & (1<<4 - 1))
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cstring(b []byte) string {
|
||||
i := bytes.IndexByte(b, 0)
|
||||
if i == -1 {
|
||||
i = len(b)
|
||||
}
|
||||
return string(b[0:i])
|
||||
}
|
||||
|
||||
// Segment returns the first Segment with the given name, or nil if no such segment exists.
|
||||
func (f *File) Segment(name string) *Segment {
|
||||
for _, l := range f.Loads {
|
||||
if s, ok := l.(*Segment); ok && s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) DataAfterSection(name string) []byte {
|
||||
data := []byte{}
|
||||
found := false
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
found = true
|
||||
}
|
||||
|
||||
raw, err := s.Data()
|
||||
if found && raw != nil {
|
||||
data = append(data, raw[:]...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Section returns the first section with the given name, or nil if no such
|
||||
// section exists.
|
||||
func (f *File) Section(name string) *Section {
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DWARF returns the DWARF debug information for the Mach-O file.
|
||||
func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
dwarfSuffix := func(s *Section) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s.Name, "__debug_"):
|
||||
return s.Name[8:]
|
||||
case strings.HasPrefix(s.Name, "__zdebug_"):
|
||||
return s.Name[9:]
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
||||
sectionData := func(s *Section) ([]byte, error) {
|
||||
b, err := s.Data()
|
||||
if err != nil && uint64(len(b)) < s.Size {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
|
||||
dlen := binary.BigEndian.Uint64(b[4:12])
|
||||
dbuf := make([]byte, dlen)
|
||||
r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(r, dbuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = dbuf
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// There are many other DWARF sections, but these
|
||||
// are the ones the debug/dwarf package uses.
|
||||
// Don't bother loading others.
|
||||
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
|
||||
for _, s := range f.Sections {
|
||||
suffix := dwarfSuffix(s)
|
||||
if suffix == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := dat[suffix]; !ok {
|
||||
continue
|
||||
}
|
||||
b, err := sectionData(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dat[suffix] = b
|
||||
}
|
||||
|
||||
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Look for DWARF4 .debug_types sections.
|
||||
for i, s := range f.Sections {
|
||||
suffix := dwarfSuffix(s)
|
||||
if suffix != "types" {
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := sectionData(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ImportedSymbols returns the names of all symbols
|
||||
// referred to by the binary f that are expected to be
|
||||
// satisfied by other libraries at dynamic load time.
|
||||
func (f *File) ImportedSymbols() ([]string, error) {
|
||||
if f.Dysymtab == nil || f.Symtab == nil {
|
||||
return nil, &FormatError{0, "missing symbol table", nil}
|
||||
}
|
||||
|
||||
st := f.Symtab
|
||||
dt := f.Dysymtab
|
||||
var all []string
|
||||
for _, s := range st.Syms[dt.Iundefsym : dt.Iundefsym+dt.Nundefsym] {
|
||||
all = append(all, s.Name)
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// ImportedLibraries returns the paths of all libraries
|
||||
// referred to by the binary f that are expected to be
|
||||
// linked with the binary at dynamic link time.
|
||||
func (f *File) ImportedLibraries() ([]string, error) {
|
||||
var all []string
|
||||
for _, l := range f.Loads {
|
||||
if lib, ok := l.(*Dylib); ok {
|
||||
all = append(all, lib.Name)
|
||||
}
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Mach-O header data structures
|
||||
// Originally at:
|
||||
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html (since deleted by Apply)
|
||||
// Archived copy at:
|
||||
// https://web.archive.org/web/20090819232456/http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/index.html
|
||||
// For cloned PDF see:
|
||||
// https://github.com/aidansteele/osx-abi-macho-file-format-reference
|
||||
|
||||
package macho
|
||||
|
||||
import "strconv"
|
||||
|
||||
// A FileHeader represents a Mach-O file header.
|
||||
type FileHeader struct {
|
||||
Magic uint32
|
||||
Cpu Cpu
|
||||
SubCpu uint32
|
||||
Type Type
|
||||
Ncmd uint32
|
||||
Cmdsz uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
const (
|
||||
fileHeaderSize32 = 7 * 4
|
||||
fileHeaderSize64 = 8 * 4
|
||||
)
|
||||
|
||||
const (
|
||||
Magic32 uint32 = 0xfeedface
|
||||
Magic64 uint32 = 0xfeedfacf
|
||||
MagicFat uint32 = 0xcafebabe
|
||||
)
|
||||
|
||||
// A Type is the Mach-O file type, e.g. an object file, executable, or dynamic library.
|
||||
type Type uint32
|
||||
|
||||
const (
|
||||
TypeObj Type = 1
|
||||
TypeExec Type = 2
|
||||
TypeDylib Type = 6
|
||||
TypeBundle Type = 8
|
||||
)
|
||||
|
||||
var typeStrings = []intName{
|
||||
{uint32(TypeObj), "Obj"},
|
||||
{uint32(TypeExec), "Exec"},
|
||||
{uint32(TypeDylib), "Dylib"},
|
||||
{uint32(TypeBundle), "Bundle"},
|
||||
}
|
||||
|
||||
func (t Type) String() string { return stringName(uint32(t), typeStrings, false) }
|
||||
func (t Type) GoString() string { return stringName(uint32(t), typeStrings, true) }
|
||||
|
||||
// A Cpu is a Mach-O cpu type.
|
||||
type Cpu uint32
|
||||
|
||||
const cpuArch64 = 0x01000000
|
||||
|
||||
const (
|
||||
Cpu386 Cpu = 7
|
||||
CpuAmd64 Cpu = Cpu386 | cpuArch64
|
||||
CpuArm Cpu = 12
|
||||
CpuArm64 Cpu = CpuArm | cpuArch64
|
||||
CpuPpc Cpu = 18
|
||||
CpuPpc64 Cpu = CpuPpc | cpuArch64
|
||||
)
|
||||
|
||||
var cpuStrings = []intName{
|
||||
{uint32(Cpu386), "Cpu386"},
|
||||
{uint32(CpuAmd64), "CpuAmd64"},
|
||||
{uint32(CpuArm), "CpuArm"},
|
||||
{uint32(CpuArm64), "CpuArm64"},
|
||||
{uint32(CpuPpc), "CpuPpc"},
|
||||
{uint32(CpuPpc64), "CpuPpc64"},
|
||||
}
|
||||
|
||||
func (i Cpu) String() string { return stringName(uint32(i), cpuStrings, false) }
|
||||
func (i Cpu) GoString() string { return stringName(uint32(i), cpuStrings, true) }
|
||||
|
||||
// A LoadCmd is a Mach-O load command.
|
||||
type LoadCmd uint32
|
||||
|
||||
const (
|
||||
LoadCmdSegment LoadCmd = 0x1
|
||||
LoadCmdSymtab LoadCmd = 0x2
|
||||
LoadCmdThread LoadCmd = 0x4
|
||||
LoadCmdUnixThread LoadCmd = 0x5 // thread+stack
|
||||
LoadCmdDysymtab LoadCmd = 0xb
|
||||
LoadCmdDylib LoadCmd = 0xc // load dylib command
|
||||
LoadCmdDylinker LoadCmd = 0xf // id dylinker command (not load dylinker command)
|
||||
LoadCmdSegment64 LoadCmd = 0x19
|
||||
LoadCmdRpath LoadCmd = 0x8000001c
|
||||
)
|
||||
|
||||
var cmdStrings = []intName{
|
||||
{uint32(LoadCmdSegment), "LoadCmdSegment"},
|
||||
{uint32(LoadCmdThread), "LoadCmdThread"},
|
||||
{uint32(LoadCmdUnixThread), "LoadCmdUnixThread"},
|
||||
{uint32(LoadCmdDylib), "LoadCmdDylib"},
|
||||
{uint32(LoadCmdSegment64), "LoadCmdSegment64"},
|
||||
{uint32(LoadCmdRpath), "LoadCmdRpath"},
|
||||
}
|
||||
|
||||
func (i LoadCmd) String() string { return stringName(uint32(i), cmdStrings, false) }
|
||||
func (i LoadCmd) GoString() string { return stringName(uint32(i), cmdStrings, true) }
|
||||
|
||||
type (
|
||||
// A Segment32 is a 32-bit Mach-O segment load command.
|
||||
Segment32 struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Name [16]byte
|
||||
Addr uint32
|
||||
Memsz uint32
|
||||
Offset uint32
|
||||
Filesz uint32
|
||||
Maxprot uint32
|
||||
Prot uint32
|
||||
Nsect uint32
|
||||
Flag uint32
|
||||
}
|
||||
|
||||
// A Segment64 is a 64-bit Mach-O segment load command.
|
||||
Segment64 struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Name [16]byte
|
||||
Addr uint64
|
||||
Memsz uint64
|
||||
Offset uint64
|
||||
Filesz uint64
|
||||
Maxprot uint32
|
||||
Prot uint32
|
||||
Nsect uint32
|
||||
Flag uint32
|
||||
}
|
||||
|
||||
// A SymtabCmd is a Mach-O symbol table command.
|
||||
SymtabCmd struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Symoff uint32
|
||||
Nsyms uint32
|
||||
Stroff uint32
|
||||
Strsize uint32
|
||||
}
|
||||
|
||||
// A DysymtabCmd is a Mach-O dynamic symbol table command.
|
||||
DysymtabCmd struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Ilocalsym uint32
|
||||
Nlocalsym uint32
|
||||
Iextdefsym uint32
|
||||
Nextdefsym uint32
|
||||
Iundefsym uint32
|
||||
Nundefsym uint32
|
||||
Tocoffset uint32
|
||||
Ntoc uint32
|
||||
Modtaboff uint32
|
||||
Nmodtab uint32
|
||||
Extrefsymoff uint32
|
||||
Nextrefsyms uint32
|
||||
Indirectsymoff uint32
|
||||
Nindirectsyms uint32
|
||||
Extreloff uint32
|
||||
Nextrel uint32
|
||||
Locreloff uint32
|
||||
Nlocrel uint32
|
||||
}
|
||||
|
||||
// A DylibCmd is a Mach-O load dynamic library command.
|
||||
DylibCmd struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Name uint32
|
||||
Time uint32
|
||||
CurrentVersion uint32
|
||||
CompatVersion uint32
|
||||
}
|
||||
|
||||
// A RpathCmd is a Mach-O rpath command.
|
||||
RpathCmd struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Path uint32
|
||||
}
|
||||
|
||||
// A Thread is a Mach-O thread state command.
|
||||
Thread struct {
|
||||
Cmd LoadCmd
|
||||
Len uint32
|
||||
Type uint32
|
||||
Data []uint32
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
FlagNoUndefs uint32 = 0x1
|
||||
FlagIncrLink uint32 = 0x2
|
||||
FlagDyldLink uint32 = 0x4
|
||||
FlagBindAtLoad uint32 = 0x8
|
||||
FlagPrebound uint32 = 0x10
|
||||
FlagSplitSegs uint32 = 0x20
|
||||
FlagLazyInit uint32 = 0x40
|
||||
FlagTwoLevel uint32 = 0x80
|
||||
FlagForceFlat uint32 = 0x100
|
||||
FlagNoMultiDefs uint32 = 0x200
|
||||
FlagNoFixPrebinding uint32 = 0x400
|
||||
FlagPrebindable uint32 = 0x800
|
||||
FlagAllModsBound uint32 = 0x1000
|
||||
FlagSubsectionsViaSymbols uint32 = 0x2000
|
||||
FlagCanonical uint32 = 0x4000
|
||||
FlagWeakDefines uint32 = 0x8000
|
||||
FlagBindsToWeak uint32 = 0x10000
|
||||
FlagAllowStackExecution uint32 = 0x20000
|
||||
FlagRootSafe uint32 = 0x40000
|
||||
FlagSetuidSafe uint32 = 0x80000
|
||||
FlagNoReexportedDylibs uint32 = 0x100000
|
||||
FlagPIE uint32 = 0x200000
|
||||
FlagDeadStrippableDylib uint32 = 0x400000
|
||||
FlagHasTLVDescriptors uint32 = 0x800000
|
||||
FlagNoHeapExecution uint32 = 0x1000000
|
||||
FlagAppExtensionSafe uint32 = 0x2000000
|
||||
)
|
||||
|
||||
// A Section32 is a 32-bit Mach-O section header.
|
||||
type Section32 struct {
|
||||
Name [16]byte
|
||||
Seg [16]byte
|
||||
Addr uint32
|
||||
Size uint32
|
||||
Offset uint32
|
||||
Align uint32
|
||||
Reloff uint32
|
||||
Nreloc uint32
|
||||
Flags uint32
|
||||
Reserve1 uint32
|
||||
Reserve2 uint32
|
||||
}
|
||||
|
||||
// A Section64 is a 64-bit Mach-O section header.
|
||||
type Section64 struct {
|
||||
Name [16]byte
|
||||
Seg [16]byte
|
||||
Addr uint64
|
||||
Size uint64
|
||||
Offset uint32
|
||||
Align uint32
|
||||
Reloff uint32
|
||||
Nreloc uint32
|
||||
Flags uint32
|
||||
Reserve1 uint32
|
||||
Reserve2 uint32
|
||||
Reserve3 uint32
|
||||
}
|
||||
|
||||
// An Nlist32 is a Mach-O 32-bit symbol table entry.
|
||||
type Nlist32 struct {
|
||||
Name uint32
|
||||
Type uint8
|
||||
Sect uint8
|
||||
Desc uint16
|
||||
Value uint32
|
||||
}
|
||||
|
||||
// An Nlist64 is a Mach-O 64-bit symbol table entry.
|
||||
type Nlist64 struct {
|
||||
Name uint32
|
||||
Type uint8
|
||||
Sect uint8
|
||||
Desc uint16
|
||||
Value uint64
|
||||
}
|
||||
|
||||
// Regs386 is the Mach-O 386 register structure.
|
||||
type Regs386 struct {
|
||||
AX uint32
|
||||
BX uint32
|
||||
CX uint32
|
||||
DX uint32
|
||||
DI uint32
|
||||
SI uint32
|
||||
BP uint32
|
||||
SP uint32
|
||||
SS uint32
|
||||
FLAGS uint32
|
||||
IP uint32
|
||||
CS uint32
|
||||
DS uint32
|
||||
ES uint32
|
||||
FS uint32
|
||||
GS uint32
|
||||
}
|
||||
|
||||
// RegsAMD64 is the Mach-O AMD64 register structure.
|
||||
type RegsAMD64 struct {
|
||||
AX uint64
|
||||
BX uint64
|
||||
CX uint64
|
||||
DX uint64
|
||||
DI uint64
|
||||
SI uint64
|
||||
BP uint64
|
||||
SP uint64
|
||||
R8 uint64
|
||||
R9 uint64
|
||||
R10 uint64
|
||||
R11 uint64
|
||||
R12 uint64
|
||||
R13 uint64
|
||||
R14 uint64
|
||||
R15 uint64
|
||||
IP uint64
|
||||
FLAGS uint64
|
||||
CS uint64
|
||||
FS uint64
|
||||
GS uint64
|
||||
}
|
||||
|
||||
type intName struct {
|
||||
i uint32
|
||||
s string
|
||||
}
|
||||
|
||||
func stringName(i uint32, names []intName, goSyntax bool) string {
|
||||
for _, n := range names {
|
||||
if n.i == i {
|
||||
if goSyntax {
|
||||
return "macho." + n.s
|
||||
}
|
||||
return n.s
|
||||
}
|
||||
}
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package macho
|
||||
|
||||
//go:generate stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go
|
||||
|
||||
type RelocTypeGeneric int
|
||||
|
||||
const (
|
||||
GENERIC_RELOC_VANILLA RelocTypeGeneric = 0
|
||||
GENERIC_RELOC_PAIR RelocTypeGeneric = 1
|
||||
GENERIC_RELOC_SECTDIFF RelocTypeGeneric = 2
|
||||
GENERIC_RELOC_PB_LA_PTR RelocTypeGeneric = 3
|
||||
GENERIC_RELOC_LOCAL_SECTDIFF RelocTypeGeneric = 4
|
||||
GENERIC_RELOC_TLV RelocTypeGeneric = 5
|
||||
)
|
||||
|
||||
func (r RelocTypeGeneric) GoString() string { return "macho." + r.String() }
|
||||
|
||||
type RelocTypeX86_64 int
|
||||
|
||||
const (
|
||||
X86_64_RELOC_UNSIGNED RelocTypeX86_64 = 0
|
||||
X86_64_RELOC_SIGNED RelocTypeX86_64 = 1
|
||||
X86_64_RELOC_BRANCH RelocTypeX86_64 = 2
|
||||
X86_64_RELOC_GOT_LOAD RelocTypeX86_64 = 3
|
||||
X86_64_RELOC_GOT RelocTypeX86_64 = 4
|
||||
X86_64_RELOC_SUBTRACTOR RelocTypeX86_64 = 5
|
||||
X86_64_RELOC_SIGNED_1 RelocTypeX86_64 = 6
|
||||
X86_64_RELOC_SIGNED_2 RelocTypeX86_64 = 7
|
||||
X86_64_RELOC_SIGNED_4 RelocTypeX86_64 = 8
|
||||
X86_64_RELOC_TLV RelocTypeX86_64 = 9
|
||||
)
|
||||
|
||||
func (r RelocTypeX86_64) GoString() string { return "macho." + r.String() }
|
||||
|
||||
type RelocTypeARM int
|
||||
|
||||
const (
|
||||
ARM_RELOC_VANILLA RelocTypeARM = 0
|
||||
ARM_RELOC_PAIR RelocTypeARM = 1
|
||||
ARM_RELOC_SECTDIFF RelocTypeARM = 2
|
||||
ARM_RELOC_LOCAL_SECTDIFF RelocTypeARM = 3
|
||||
ARM_RELOC_PB_LA_PTR RelocTypeARM = 4
|
||||
ARM_RELOC_BR24 RelocTypeARM = 5
|
||||
ARM_THUMB_RELOC_BR22 RelocTypeARM = 6
|
||||
ARM_THUMB_32BIT_BRANCH RelocTypeARM = 7
|
||||
ARM_RELOC_HALF RelocTypeARM = 8
|
||||
ARM_RELOC_HALF_SECTDIFF RelocTypeARM = 9
|
||||
)
|
||||
|
||||
func (r RelocTypeARM) GoString() string { return "macho." + r.String() }
|
||||
|
||||
type RelocTypeARM64 int
|
||||
|
||||
const (
|
||||
ARM64_RELOC_UNSIGNED RelocTypeARM64 = 0
|
||||
ARM64_RELOC_SUBTRACTOR RelocTypeARM64 = 1
|
||||
ARM64_RELOC_BRANCH26 RelocTypeARM64 = 2
|
||||
ARM64_RELOC_PAGE21 RelocTypeARM64 = 3
|
||||
ARM64_RELOC_PAGEOFF12 RelocTypeARM64 = 4
|
||||
ARM64_RELOC_GOT_LOAD_PAGE21 RelocTypeARM64 = 5
|
||||
ARM64_RELOC_GOT_LOAD_PAGEOFF12 RelocTypeARM64 = 6
|
||||
ARM64_RELOC_POINTER_TO_GOT RelocTypeARM64 = 7
|
||||
ARM64_RELOC_TLVP_LOAD_PAGE21 RelocTypeARM64 = 8
|
||||
ARM64_RELOC_TLVP_LOAD_PAGEOFF12 RelocTypeARM64 = 9
|
||||
ARM64_RELOC_ADDEND RelocTypeARM64 = 10
|
||||
)
|
||||
|
||||
func (r RelocTypeARM64) GoString() string { return "macho." + r.String() }
|
||||
@@ -0,0 +1,49 @@
|
||||
// Code generated by "stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go"; DO NOT EDIT.
|
||||
|
||||
package macho
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _RelocTypeGeneric_name = "GENERIC_RELOC_VANILLAGENERIC_RELOC_PAIRGENERIC_RELOC_SECTDIFFGENERIC_RELOC_PB_LA_PTRGENERIC_RELOC_LOCAL_SECTDIFFGENERIC_RELOC_TLV"
|
||||
|
||||
var _RelocTypeGeneric_index = [...]uint8{0, 21, 39, 61, 84, 112, 129}
|
||||
|
||||
func (i RelocTypeGeneric) String() string {
|
||||
if i < 0 || i >= RelocTypeGeneric(len(_RelocTypeGeneric_index)-1) {
|
||||
return "RelocTypeGeneric(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RelocTypeGeneric_name[_RelocTypeGeneric_index[i]:_RelocTypeGeneric_index[i+1]]
|
||||
}
|
||||
|
||||
const _RelocTypeX86_64_name = "X86_64_RELOC_UNSIGNEDX86_64_RELOC_SIGNEDX86_64_RELOC_BRANCHX86_64_RELOC_GOT_LOADX86_64_RELOC_GOTX86_64_RELOC_SUBTRACTORX86_64_RELOC_SIGNED_1X86_64_RELOC_SIGNED_2X86_64_RELOC_SIGNED_4X86_64_RELOC_TLV"
|
||||
|
||||
var _RelocTypeX86_64_index = [...]uint8{0, 21, 40, 59, 80, 96, 119, 140, 161, 182, 198}
|
||||
|
||||
func (i RelocTypeX86_64) String() string {
|
||||
if i < 0 || i >= RelocTypeX86_64(len(_RelocTypeX86_64_index)-1) {
|
||||
return "RelocTypeX86_64(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RelocTypeX86_64_name[_RelocTypeX86_64_index[i]:_RelocTypeX86_64_index[i+1]]
|
||||
}
|
||||
|
||||
const _RelocTypeARM_name = "ARM_RELOC_VANILLAARM_RELOC_PAIRARM_RELOC_SECTDIFFARM_RELOC_LOCAL_SECTDIFFARM_RELOC_PB_LA_PTRARM_RELOC_BR24ARM_THUMB_RELOC_BR22ARM_THUMB_32BIT_BRANCHARM_RELOC_HALFARM_RELOC_HALF_SECTDIFF"
|
||||
|
||||
var _RelocTypeARM_index = [...]uint8{0, 17, 31, 49, 73, 92, 106, 126, 148, 162, 185}
|
||||
|
||||
func (i RelocTypeARM) String() string {
|
||||
if i < 0 || i >= RelocTypeARM(len(_RelocTypeARM_index)-1) {
|
||||
return "RelocTypeARM(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RelocTypeARM_name[_RelocTypeARM_index[i]:_RelocTypeARM_index[i+1]]
|
||||
}
|
||||
|
||||
const _RelocTypeARM64_name = "ARM64_RELOC_UNSIGNEDARM64_RELOC_SUBTRACTORARM64_RELOC_BRANCH26ARM64_RELOC_PAGE21ARM64_RELOC_PAGEOFF12ARM64_RELOC_GOT_LOAD_PAGE21ARM64_RELOC_GOT_LOAD_PAGEOFF12ARM64_RELOC_POINTER_TO_GOTARM64_RELOC_TLVP_LOAD_PAGE21ARM64_RELOC_TLVP_LOAD_PAGEOFF12ARM64_RELOC_ADDEND"
|
||||
|
||||
var _RelocTypeARM64_index = [...]uint16{0, 20, 42, 62, 80, 101, 128, 158, 184, 212, 243, 261}
|
||||
|
||||
func (i RelocTypeARM64) String() string {
|
||||
if i < 0 || i >= RelocTypeARM64(len(_RelocTypeARM64_index)-1) {
|
||||
return "RelocTypeARM64(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RelocTypeARM64_name[_RelocTypeARM64_index[i]:_RelocTypeARM64_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Package pe implements access to PE (Microsoft Windows Portable Executable) files.
|
||||
package pe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mandiant/GoReSym/debug/dwarf"
|
||||
)
|
||||
|
||||
// Avoid use of post-Go 1.4 io features, to make safe for toolchain bootstrap.
|
||||
const seekStart = 0
|
||||
|
||||
// A File represents an open PE file.
|
||||
type File struct {
|
||||
FileHeader
|
||||
OptionalHeader interface{} // of type *OptionalHeader32 or *OptionalHeader64
|
||||
Sections []*Section
|
||||
Symbols []*Symbol // COFF symbols with auxiliary symbol records removed
|
||||
COFFSymbols []COFFSymbol // all COFF symbols (including auxiliary symbol records)
|
||||
StringTable StringTable
|
||||
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
// Open opens the named file using os.Open and prepares it for use as a PE binary.
|
||||
func Open(name string) (*File, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ff, err := NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
ff.closer = f
|
||||
return ff, nil
|
||||
}
|
||||
|
||||
// Close closes the File.
|
||||
// If the File was created using NewFile directly instead of Open,
|
||||
// Close has no effect.
|
||||
func (f *File) Close() error {
|
||||
var err error
|
||||
if f.closer != nil {
|
||||
err = f.closer.Close()
|
||||
f.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(brainman): add Load function, as a replacement for NewFile, that does not call removeAuxSymbols (for performance)
|
||||
|
||||
// NewFile creates a new File for accessing a PE binary in an underlying reader.
|
||||
func NewFile(r io.ReaderAt) (*File, error) {
|
||||
f := new(File)
|
||||
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
||||
|
||||
var dosheader [96]byte
|
||||
if _, err := r.ReadAt(dosheader[0:], 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var base int64
|
||||
if dosheader[0] == 'M' && dosheader[1] == 'Z' {
|
||||
signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:]))
|
||||
var sign [4]byte
|
||||
r.ReadAt(sign[:], signoff)
|
||||
if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) {
|
||||
return nil, fmt.Errorf("Invalid PE COFF file signature of %v.", sign)
|
||||
}
|
||||
base = signoff + 4
|
||||
} else {
|
||||
base = int64(0)
|
||||
}
|
||||
sr.Seek(base, seekStart)
|
||||
if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch f.FileHeader.Machine {
|
||||
case IMAGE_FILE_MACHINE_UNKNOWN, IMAGE_FILE_MACHINE_ARMNT, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386:
|
||||
default:
|
||||
return nil, fmt.Errorf("Unrecognised COFF file header machine value of 0x%x.", f.FileHeader.Machine)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Read string table.
|
||||
f.StringTable, err = readStringTable(&f.FileHeader, sr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read symbol table.
|
||||
f.COFFSymbols, err = readCOFFSymbols(&f.FileHeader, sr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Symbols, err = removeAuxSymbols(f.COFFSymbols, f.StringTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Seek past file header.
|
||||
_, err = sr.Seek(base+int64(binary.Size(f.FileHeader)), seekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to seek past the file header: %v", err)
|
||||
}
|
||||
|
||||
// Read optional header.
|
||||
f.OptionalHeader, err = readOptionalHeader(sr, f.FileHeader.SizeOfOptionalHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Process sections.
|
||||
f.Sections = make([]*Section, f.FileHeader.NumberOfSections)
|
||||
for i := 0; i < int(f.FileHeader.NumberOfSections); i++ {
|
||||
sh := new(SectionHeader32)
|
||||
if err := binary.Read(sr, binary.LittleEndian, sh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, err := sh.fullName(f.StringTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := new(Section)
|
||||
s.SectionHeader = SectionHeader{
|
||||
Name: name,
|
||||
VirtualSize: sh.VirtualSize,
|
||||
VirtualAddress: sh.VirtualAddress,
|
||||
Size: sh.SizeOfRawData,
|
||||
Offset: sh.PointerToRawData,
|
||||
PointerToRelocations: sh.PointerToRelocations,
|
||||
PointerToLineNumbers: sh.PointerToLineNumbers,
|
||||
NumberOfRelocations: sh.NumberOfRelocations,
|
||||
NumberOfLineNumbers: sh.NumberOfLineNumbers,
|
||||
Characteristics: sh.Characteristics,
|
||||
}
|
||||
r2 := r
|
||||
if sh.PointerToRawData == 0 { // .bss must have all 0s
|
||||
r2 = zeroReaderAt{}
|
||||
}
|
||||
s.sr = io.NewSectionReader(r2, int64(s.SectionHeader.Offset), int64(s.SectionHeader.Size))
|
||||
s.ReaderAt = s.sr
|
||||
f.Sections[i] = s
|
||||
}
|
||||
for i := range f.Sections {
|
||||
var err error
|
||||
f.Sections[i].Relocs, err = readRelocs(&f.Sections[i].SectionHeader, sr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// zeroReaderAt is ReaderAt that reads 0s.
|
||||
type zeroReaderAt struct{}
|
||||
|
||||
// ReadAt writes len(p) 0s into p.
|
||||
func (w zeroReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
for i := range p {
|
||||
p[i] = 0
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// getString extracts a string from symbol string table.
|
||||
func getString(section []byte, start int) (string, bool) {
|
||||
if start < 0 || start >= len(section) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
for end := start; end < len(section); end++ {
|
||||
if section[end] == 0 {
|
||||
return string(section[start:end]), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Section returns the first section with the given name, or nil if no such
|
||||
// section exists.
|
||||
func (f *File) Section(name string) *Section {
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) DataAfterSection(name string) []byte {
|
||||
data := []byte{}
|
||||
found := false
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
found = true
|
||||
}
|
||||
|
||||
raw, err := s.Data()
|
||||
if found && raw != nil {
|
||||
data = append(data, raw[:]...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
dwarfSuffix := func(s *Section) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s.Name, ".debug_"):
|
||||
return s.Name[7:]
|
||||
case strings.HasPrefix(s.Name, ".zdebug_"):
|
||||
return s.Name[8:]
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// sectionData gets the data for s and checks its size.
|
||||
sectionData := func(s *Section) ([]byte, error) {
|
||||
b, err := s.Data()
|
||||
if err != nil && uint32(len(b)) < s.Size {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if 0 < s.VirtualSize && s.VirtualSize < s.Size {
|
||||
b = b[:s.VirtualSize]
|
||||
}
|
||||
|
||||
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
|
||||
dlen := binary.BigEndian.Uint64(b[4:12])
|
||||
dbuf := make([]byte, dlen)
|
||||
r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(r, dbuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = dbuf
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// There are many other DWARF sections, but these
|
||||
// are the ones the debug/dwarf package uses.
|
||||
// Don't bother loading others.
|
||||
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
|
||||
for _, s := range f.Sections {
|
||||
suffix := dwarfSuffix(s)
|
||||
if suffix == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := dat[suffix]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := sectionData(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dat[suffix] = b
|
||||
}
|
||||
|
||||
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Look for DWARF4 .debug_types sections.
|
||||
for i, s := range f.Sections {
|
||||
suffix := dwarfSuffix(s)
|
||||
if suffix != "types" {
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := sectionData(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// TODO(brainman): document ImportDirectory once we decide what to do with it.
|
||||
|
||||
type ImportDirectory struct {
|
||||
OriginalFirstThunk uint32
|
||||
TimeDateStamp uint32
|
||||
ForwarderChain uint32
|
||||
Name uint32
|
||||
FirstThunk uint32
|
||||
|
||||
dll string
|
||||
}
|
||||
|
||||
// ImportedSymbols returns the names of all symbols
|
||||
// referred to by the binary f that are expected to be
|
||||
// satisfied by other libraries at dynamic load time.
|
||||
// It does not return weak symbols.
|
||||
func (f *File) ImportedSymbols() ([]string, error) {
|
||||
if f.OptionalHeader == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pe64 := f.Machine == IMAGE_FILE_MACHINE_AMD64
|
||||
|
||||
// grab the number of data directory entries
|
||||
var dd_length uint32
|
||||
if pe64 {
|
||||
dd_length = f.OptionalHeader.(*OptionalHeader64).NumberOfRvaAndSizes
|
||||
} else {
|
||||
dd_length = f.OptionalHeader.(*OptionalHeader32).NumberOfRvaAndSizes
|
||||
}
|
||||
|
||||
// check that the length of data directory entries is large
|
||||
// enough to include the imports directory.
|
||||
if dd_length < IMAGE_DIRECTORY_ENTRY_IMPORT+1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// grab the import data directory entry
|
||||
var idd DataDirectory
|
||||
if pe64 {
|
||||
idd = f.OptionalHeader.(*OptionalHeader64).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
|
||||
} else {
|
||||
idd = f.OptionalHeader.(*OptionalHeader32).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
|
||||
}
|
||||
|
||||
// figure out which section contains the import directory table
|
||||
var ds *Section
|
||||
ds = nil
|
||||
for _, s := range f.Sections {
|
||||
if s.VirtualAddress <= idd.VirtualAddress && idd.VirtualAddress < s.VirtualAddress+s.VirtualSize {
|
||||
ds = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// didn't find a section, so no import libraries were found
|
||||
if ds == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
d, err := ds.Data()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// seek to the virtual address specified in the import data directory
|
||||
d = d[idd.VirtualAddress-ds.VirtualAddress:]
|
||||
|
||||
// start decoding the import directory
|
||||
var ida []ImportDirectory
|
||||
for len(d) >= 20 {
|
||||
var dt ImportDirectory
|
||||
dt.OriginalFirstThunk = binary.LittleEndian.Uint32(d[0:4])
|
||||
dt.TimeDateStamp = binary.LittleEndian.Uint32(d[4:8])
|
||||
dt.ForwarderChain = binary.LittleEndian.Uint32(d[8:12])
|
||||
dt.Name = binary.LittleEndian.Uint32(d[12:16])
|
||||
dt.FirstThunk = binary.LittleEndian.Uint32(d[16:20])
|
||||
d = d[20:]
|
||||
if dt.OriginalFirstThunk == 0 {
|
||||
break
|
||||
}
|
||||
ida = append(ida, dt)
|
||||
}
|
||||
// TODO(brainman): this needs to be rewritten
|
||||
// ds.Data() returns contents of section containing import table. Why store in variable called "names"?
|
||||
// Why we are retrieving it second time? We already have it in "d", and it is not modified anywhere.
|
||||
// getString does not extracts a string from symbol string table (as getString doco says).
|
||||
// Why ds.Data() called again and again in the loop?
|
||||
// Needs test before rewrite.
|
||||
names, _ := ds.Data()
|
||||
var all []string
|
||||
for _, dt := range ida {
|
||||
dt.dll, _ = getString(names, int(dt.Name-ds.VirtualAddress))
|
||||
d, _ = ds.Data()
|
||||
// seek to OriginalFirstThunk
|
||||
d = d[dt.OriginalFirstThunk-ds.VirtualAddress:]
|
||||
for len(d) > 0 {
|
||||
if pe64 { // 64bit
|
||||
va := binary.LittleEndian.Uint64(d[0:8])
|
||||
d = d[8:]
|
||||
if va == 0 {
|
||||
break
|
||||
}
|
||||
if va&0x8000000000000000 > 0 { // is Ordinal
|
||||
// TODO add dynimport ordinal support.
|
||||
} else {
|
||||
fn, _ := getString(names, int(uint32(va)-ds.VirtualAddress+2))
|
||||
all = append(all, fn+":"+dt.dll)
|
||||
}
|
||||
} else { // 32bit
|
||||
va := binary.LittleEndian.Uint32(d[0:4])
|
||||
d = d[4:]
|
||||
if va == 0 {
|
||||
break
|
||||
}
|
||||
if va&0x80000000 > 0 { // is Ordinal
|
||||
// TODO add dynimport ordinal support.
|
||||
//ord := va&0x0000FFFF
|
||||
} else {
|
||||
fn, _ := getString(names, int(va-ds.VirtualAddress+2))
|
||||
all = append(all, fn+":"+dt.dll)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// ImportedLibraries returns the names of all libraries
|
||||
// referred to by the binary f that are expected to be
|
||||
// linked with the binary at dynamic link time.
|
||||
func (f *File) ImportedLibraries() ([]string, error) {
|
||||
// TODO
|
||||
// cgo -dynimport don't use this for windows PE, so just return.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FormatError is unused.
|
||||
// The type is retained for compatibility.
|
||||
type FormatError struct {
|
||||
}
|
||||
|
||||
func (e *FormatError) Error() string {
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
// readOptionalHeader accepts a io.ReadSeeker pointing to optional header in the PE file
|
||||
// and its size as seen in the file header.
|
||||
// It parses the given size of bytes and returns optional header. It infers whether the
|
||||
// bytes being parsed refer to 32 bit or 64 bit version of optional header.
|
||||
func readOptionalHeader(r io.ReadSeeker, sz uint16) (interface{}, error) {
|
||||
// If optional header size is 0, return empty optional header.
|
||||
if sz == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// First couple of bytes in option header state its type.
|
||||
// We need to read them first to determine the type and
|
||||
// validity of optional header.
|
||||
ohMagic uint16
|
||||
ohMagicSz = binary.Size(ohMagic)
|
||||
)
|
||||
|
||||
// If optional header size is greater than 0 but less than its magic size, return error.
|
||||
if sz < uint16(ohMagicSz) {
|
||||
return nil, fmt.Errorf("optional header size is less than optional header magic size")
|
||||
}
|
||||
|
||||
// read reads from io.ReadSeeke, r, into data.
|
||||
var err error
|
||||
read := func(data interface{}) bool {
|
||||
err = binary.Read(r, binary.LittleEndian, data)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
if !read(&ohMagic) {
|
||||
return nil, fmt.Errorf("failure to read optional header magic: %v", err)
|
||||
|
||||
}
|
||||
|
||||
switch ohMagic {
|
||||
case 0x10b: // PE32
|
||||
var (
|
||||
oh32 OptionalHeader32
|
||||
// There can be 0 or more data directories. So the minimum size of optional
|
||||
// header is calculated by subtracting oh32.DataDirectory size from oh32 size.
|
||||
oh32MinSz = binary.Size(oh32) - binary.Size(oh32.DataDirectory)
|
||||
)
|
||||
|
||||
if sz < uint16(oh32MinSz) {
|
||||
return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) of PE32 optional header", sz, oh32MinSz)
|
||||
}
|
||||
|
||||
// Init oh32 fields
|
||||
oh32.Magic = ohMagic
|
||||
if !read(&oh32.MajorLinkerVersion) ||
|
||||
!read(&oh32.MinorLinkerVersion) ||
|
||||
!read(&oh32.SizeOfCode) ||
|
||||
!read(&oh32.SizeOfInitializedData) ||
|
||||
!read(&oh32.SizeOfUninitializedData) ||
|
||||
!read(&oh32.AddressOfEntryPoint) ||
|
||||
!read(&oh32.BaseOfCode) ||
|
||||
!read(&oh32.BaseOfData) ||
|
||||
!read(&oh32.ImageBase) ||
|
||||
!read(&oh32.SectionAlignment) ||
|
||||
!read(&oh32.FileAlignment) ||
|
||||
!read(&oh32.MajorOperatingSystemVersion) ||
|
||||
!read(&oh32.MinorOperatingSystemVersion) ||
|
||||
!read(&oh32.MajorImageVersion) ||
|
||||
!read(&oh32.MinorImageVersion) ||
|
||||
!read(&oh32.MajorSubsystemVersion) ||
|
||||
!read(&oh32.MinorSubsystemVersion) ||
|
||||
!read(&oh32.Win32VersionValue) ||
|
||||
!read(&oh32.SizeOfImage) ||
|
||||
!read(&oh32.SizeOfHeaders) ||
|
||||
!read(&oh32.CheckSum) ||
|
||||
!read(&oh32.Subsystem) ||
|
||||
!read(&oh32.DllCharacteristics) ||
|
||||
!read(&oh32.SizeOfStackReserve) ||
|
||||
!read(&oh32.SizeOfStackCommit) ||
|
||||
!read(&oh32.SizeOfHeapReserve) ||
|
||||
!read(&oh32.SizeOfHeapCommit) ||
|
||||
!read(&oh32.LoaderFlags) ||
|
||||
!read(&oh32.NumberOfRvaAndSizes) {
|
||||
return nil, fmt.Errorf("failure to read PE32 optional header: %v", err)
|
||||
}
|
||||
|
||||
dd, err := readDataDirectories(r, sz-uint16(oh32MinSz), oh32.NumberOfRvaAndSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
copy(oh32.DataDirectory[:], dd)
|
||||
|
||||
return &oh32, nil
|
||||
case 0x20b: // PE32+
|
||||
var (
|
||||
oh64 OptionalHeader64
|
||||
// There can be 0 or more data directories. So the minimum size of optional
|
||||
// header is calculated by subtracting oh64.DataDirectory size from oh64 size.
|
||||
oh64MinSz = binary.Size(oh64) - binary.Size(oh64.DataDirectory)
|
||||
)
|
||||
|
||||
if sz < uint16(oh64MinSz) {
|
||||
return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) for PE32+ optional header", sz, oh64MinSz)
|
||||
}
|
||||
|
||||
// Init oh64 fields
|
||||
oh64.Magic = ohMagic
|
||||
if !read(&oh64.MajorLinkerVersion) ||
|
||||
!read(&oh64.MinorLinkerVersion) ||
|
||||
!read(&oh64.SizeOfCode) ||
|
||||
!read(&oh64.SizeOfInitializedData) ||
|
||||
!read(&oh64.SizeOfUninitializedData) ||
|
||||
!read(&oh64.AddressOfEntryPoint) ||
|
||||
!read(&oh64.BaseOfCode) ||
|
||||
!read(&oh64.ImageBase) ||
|
||||
!read(&oh64.SectionAlignment) ||
|
||||
!read(&oh64.FileAlignment) ||
|
||||
!read(&oh64.MajorOperatingSystemVersion) ||
|
||||
!read(&oh64.MinorOperatingSystemVersion) ||
|
||||
!read(&oh64.MajorImageVersion) ||
|
||||
!read(&oh64.MinorImageVersion) ||
|
||||
!read(&oh64.MajorSubsystemVersion) ||
|
||||
!read(&oh64.MinorSubsystemVersion) ||
|
||||
!read(&oh64.Win32VersionValue) ||
|
||||
!read(&oh64.SizeOfImage) ||
|
||||
!read(&oh64.SizeOfHeaders) ||
|
||||
!read(&oh64.CheckSum) ||
|
||||
!read(&oh64.Subsystem) ||
|
||||
!read(&oh64.DllCharacteristics) ||
|
||||
!read(&oh64.SizeOfStackReserve) ||
|
||||
!read(&oh64.SizeOfStackCommit) ||
|
||||
!read(&oh64.SizeOfHeapReserve) ||
|
||||
!read(&oh64.SizeOfHeapCommit) ||
|
||||
!read(&oh64.LoaderFlags) ||
|
||||
!read(&oh64.NumberOfRvaAndSizes) {
|
||||
return nil, fmt.Errorf("failure to read PE32+ optional header: %v", err)
|
||||
}
|
||||
|
||||
dd, err := readDataDirectories(r, sz-uint16(oh64MinSz), oh64.NumberOfRvaAndSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
copy(oh64.DataDirectory[:], dd)
|
||||
|
||||
return &oh64, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("optional header has unexpected Magic of 0x%x", ohMagic)
|
||||
}
|
||||
}
|
||||
|
||||
// readDataDirectories accepts a io.ReadSeeker pointing to data directories in the PE file,
|
||||
// its size and number of data directories as seen in optional header.
|
||||
// It parses the given size of bytes and returns given number of data directories.
|
||||
func readDataDirectories(r io.ReadSeeker, sz uint16, n uint32) ([]DataDirectory, error) {
|
||||
ddSz := binary.Size(DataDirectory{})
|
||||
if uint32(sz) != n*uint32(ddSz) {
|
||||
return nil, fmt.Errorf("size of data directories(%d) is inconsistent with number of data directories(%d)", sz, n)
|
||||
}
|
||||
|
||||
dd := make([]DataDirectory, n)
|
||||
if err := binary.Read(r, binary.LittleEndian, dd); err != nil {
|
||||
return nil, fmt.Errorf("failure to read data directories: %v", err)
|
||||
}
|
||||
|
||||
return dd, nil
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package pe
|
||||
|
||||
type FileHeader struct {
|
||||
Machine uint16
|
||||
NumberOfSections uint16
|
||||
TimeDateStamp uint32
|
||||
PointerToSymbolTable uint32
|
||||
NumberOfSymbols uint32
|
||||
SizeOfOptionalHeader uint16
|
||||
Characteristics uint16
|
||||
}
|
||||
|
||||
type DataDirectory struct {
|
||||
VirtualAddress uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type OptionalHeader32 struct {
|
||||
Magic uint16
|
||||
MajorLinkerVersion uint8
|
||||
MinorLinkerVersion uint8
|
||||
SizeOfCode uint32
|
||||
SizeOfInitializedData uint32
|
||||
SizeOfUninitializedData uint32
|
||||
AddressOfEntryPoint uint32
|
||||
BaseOfCode uint32
|
||||
BaseOfData uint32
|
||||
ImageBase uint32
|
||||
SectionAlignment uint32
|
||||
FileAlignment uint32
|
||||
MajorOperatingSystemVersion uint16
|
||||
MinorOperatingSystemVersion uint16
|
||||
MajorImageVersion uint16
|
||||
MinorImageVersion uint16
|
||||
MajorSubsystemVersion uint16
|
||||
MinorSubsystemVersion uint16
|
||||
Win32VersionValue uint32
|
||||
SizeOfImage uint32
|
||||
SizeOfHeaders uint32
|
||||
CheckSum uint32
|
||||
Subsystem uint16
|
||||
DllCharacteristics uint16
|
||||
SizeOfStackReserve uint32
|
||||
SizeOfStackCommit uint32
|
||||
SizeOfHeapReserve uint32
|
||||
SizeOfHeapCommit uint32
|
||||
LoaderFlags uint32
|
||||
NumberOfRvaAndSizes uint32
|
||||
DataDirectory [16]DataDirectory
|
||||
}
|
||||
|
||||
type OptionalHeader64 struct {
|
||||
Magic uint16
|
||||
MajorLinkerVersion uint8
|
||||
MinorLinkerVersion uint8
|
||||
SizeOfCode uint32
|
||||
SizeOfInitializedData uint32
|
||||
SizeOfUninitializedData uint32
|
||||
AddressOfEntryPoint uint32
|
||||
BaseOfCode uint32
|
||||
ImageBase uint64
|
||||
SectionAlignment uint32
|
||||
FileAlignment uint32
|
||||
MajorOperatingSystemVersion uint16
|
||||
MinorOperatingSystemVersion uint16
|
||||
MajorImageVersion uint16
|
||||
MinorImageVersion uint16
|
||||
MajorSubsystemVersion uint16
|
||||
MinorSubsystemVersion uint16
|
||||
Win32VersionValue uint32
|
||||
SizeOfImage uint32
|
||||
SizeOfHeaders uint32
|
||||
CheckSum uint32
|
||||
Subsystem uint16
|
||||
DllCharacteristics uint16
|
||||
SizeOfStackReserve uint64
|
||||
SizeOfStackCommit uint64
|
||||
SizeOfHeapReserve uint64
|
||||
SizeOfHeapCommit uint64
|
||||
LoaderFlags uint32
|
||||
NumberOfRvaAndSizes uint32
|
||||
DataDirectory [16]DataDirectory
|
||||
}
|
||||
|
||||
const (
|
||||
IMAGE_FILE_MACHINE_UNKNOWN = 0x0
|
||||
IMAGE_FILE_MACHINE_AM33 = 0x1d3
|
||||
IMAGE_FILE_MACHINE_AMD64 = 0x8664
|
||||
IMAGE_FILE_MACHINE_ARM = 0x1c0
|
||||
IMAGE_FILE_MACHINE_ARMNT = 0x1c4
|
||||
IMAGE_FILE_MACHINE_ARM64 = 0xaa64
|
||||
IMAGE_FILE_MACHINE_EBC = 0xebc
|
||||
IMAGE_FILE_MACHINE_I386 = 0x14c
|
||||
IMAGE_FILE_MACHINE_IA64 = 0x200
|
||||
IMAGE_FILE_MACHINE_M32R = 0x9041
|
||||
IMAGE_FILE_MACHINE_MIPS16 = 0x266
|
||||
IMAGE_FILE_MACHINE_MIPSFPU = 0x366
|
||||
IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466
|
||||
IMAGE_FILE_MACHINE_POWERPC = 0x1f0
|
||||
IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1
|
||||
IMAGE_FILE_MACHINE_R4000 = 0x166
|
||||
IMAGE_FILE_MACHINE_SH3 = 0x1a2
|
||||
IMAGE_FILE_MACHINE_SH3DSP = 0x1a3
|
||||
IMAGE_FILE_MACHINE_SH4 = 0x1a6
|
||||
IMAGE_FILE_MACHINE_SH5 = 0x1a8
|
||||
IMAGE_FILE_MACHINE_THUMB = 0x1c2
|
||||
IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169
|
||||
)
|
||||
|
||||
// IMAGE_DIRECTORY_ENTRY constants
|
||||
const (
|
||||
IMAGE_DIRECTORY_ENTRY_EXPORT = 0
|
||||
IMAGE_DIRECTORY_ENTRY_IMPORT = 1
|
||||
IMAGE_DIRECTORY_ENTRY_RESOURCE = 2
|
||||
IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3
|
||||
IMAGE_DIRECTORY_ENTRY_SECURITY = 4
|
||||
IMAGE_DIRECTORY_ENTRY_BASERELOC = 5
|
||||
IMAGE_DIRECTORY_ENTRY_DEBUG = 6
|
||||
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7
|
||||
IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8
|
||||
IMAGE_DIRECTORY_ENTRY_TLS = 9
|
||||
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10
|
||||
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11
|
||||
IMAGE_DIRECTORY_ENTRY_IAT = 12
|
||||
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13
|
||||
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14
|
||||
)
|
||||
|
||||
// Values of IMAGE_FILE_HEADER.Characteristics. These can be combined together.
|
||||
const (
|
||||
IMAGE_FILE_RELOCS_STRIPPED = 0x0001
|
||||
IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002
|
||||
IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004
|
||||
IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008
|
||||
IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010
|
||||
IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020
|
||||
IMAGE_FILE_BYTES_REVERSED_LO = 0x0080
|
||||
IMAGE_FILE_32BIT_MACHINE = 0x0100
|
||||
IMAGE_FILE_DEBUG_STRIPPED = 0x0200
|
||||
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400
|
||||
IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800
|
||||
IMAGE_FILE_SYSTEM = 0x1000
|
||||
IMAGE_FILE_DLL = 0x2000
|
||||
IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000
|
||||
IMAGE_FILE_BYTES_REVERSED_HI = 0x8000
|
||||
)
|
||||
|
||||
// OptionalHeader64.Subsystem and OptionalHeader32.Subsystem values.
|
||||
const (
|
||||
IMAGE_SUBSYSTEM_UNKNOWN = 0
|
||||
IMAGE_SUBSYSTEM_NATIVE = 1
|
||||
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2
|
||||
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
|
||||
IMAGE_SUBSYSTEM_OS2_CUI = 5
|
||||
IMAGE_SUBSYSTEM_POSIX_CUI = 7
|
||||
IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8
|
||||
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9
|
||||
IMAGE_SUBSYSTEM_EFI_APPLICATION = 10
|
||||
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11
|
||||
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12
|
||||
IMAGE_SUBSYSTEM_EFI_ROM = 13
|
||||
IMAGE_SUBSYSTEM_XBOX = 14
|
||||
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16
|
||||
)
|
||||
|
||||
// OptionalHeader64.DllCharacteristics and OptionalHeader32.DllCharacteristics
|
||||
// values. These can be combined together.
|
||||
const (
|
||||
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
|
||||
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
|
||||
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080
|
||||
IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100
|
||||
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200
|
||||
IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400
|
||||
IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800
|
||||
IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000
|
||||
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000
|
||||
IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
|
||||
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package pe
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SectionHeader32 represents real PE COFF section header.
|
||||
type SectionHeader32 struct {
|
||||
Name [8]uint8
|
||||
VirtualSize uint32
|
||||
VirtualAddress uint32
|
||||
SizeOfRawData uint32
|
||||
PointerToRawData uint32
|
||||
PointerToRelocations uint32
|
||||
PointerToLineNumbers uint32
|
||||
NumberOfRelocations uint16
|
||||
NumberOfLineNumbers uint16
|
||||
Characteristics uint32
|
||||
}
|
||||
|
||||
// fullName finds real name of section sh. Normally name is stored
|
||||
// in sh.Name, but if it is longer then 8 characters, it is stored
|
||||
// in COFF string table st instead.
|
||||
func (sh *SectionHeader32) fullName(st StringTable) (string, error) {
|
||||
if sh.Name[0] != '/' {
|
||||
return cstring(sh.Name[:]), nil
|
||||
}
|
||||
i, err := strconv.Atoi(cstring(sh.Name[1:]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return st.String(uint32(i))
|
||||
}
|
||||
|
||||
// TODO(brainman): copy all IMAGE_REL_* consts from ldpe.go here
|
||||
|
||||
// Reloc represents a PE COFF relocation.
|
||||
// Each section contains its own relocation list.
|
||||
type Reloc struct {
|
||||
VirtualAddress uint32
|
||||
SymbolTableIndex uint32
|
||||
Type uint16
|
||||
}
|
||||
|
||||
func readRelocs(sh *SectionHeader, r io.ReadSeeker) ([]Reloc, error) {
|
||||
if sh.NumberOfRelocations <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
_, err := r.Seek(int64(sh.PointerToRelocations), seekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to seek to %q section relocations: %v", sh.Name, err)
|
||||
}
|
||||
relocs := make([]Reloc, sh.NumberOfRelocations)
|
||||
err = binary.Read(r, binary.LittleEndian, relocs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to read section relocations: %v", err)
|
||||
}
|
||||
return relocs, nil
|
||||
}
|
||||
|
||||
// SectionHeader is similar to SectionHeader32 with Name
|
||||
// field replaced by Go string.
|
||||
type SectionHeader struct {
|
||||
Name string
|
||||
VirtualSize uint32
|
||||
VirtualAddress uint32
|
||||
Size uint32
|
||||
Offset uint32
|
||||
PointerToRelocations uint32
|
||||
PointerToLineNumbers uint32
|
||||
NumberOfRelocations uint16
|
||||
NumberOfLineNumbers uint16
|
||||
Characteristics uint32
|
||||
}
|
||||
|
||||
// Section provides access to PE COFF section.
|
||||
type Section struct {
|
||||
SectionHeader
|
||||
Relocs []Reloc
|
||||
|
||||
// Embed ReaderAt for ReadAt method.
|
||||
// Do not embed SectionReader directly
|
||||
// to avoid having Read and Seek.
|
||||
// If a client wants Read and Seek it must use
|
||||
// Open() to avoid fighting over the seek offset
|
||||
// with other clients.
|
||||
io.ReaderAt
|
||||
sr *io.SectionReader
|
||||
}
|
||||
|
||||
// Data reads and returns the contents of the PE section s.
|
||||
func (s *Section) Data() ([]byte, error) {
|
||||
dat := make([]byte, s.sr.Size())
|
||||
n, err := s.sr.ReadAt(dat, 0)
|
||||
if n == len(dat) {
|
||||
err = nil
|
||||
}
|
||||
return dat[0:n], err
|
||||
}
|
||||
|
||||
// Open returns a new ReadSeeker reading the PE section s.
|
||||
func (s *Section) Open() io.ReadSeeker {
|
||||
return io.NewSectionReader(s.sr, 0, 1<<63-1)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package pe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// cstring converts ASCII byte sequence b to string.
|
||||
// It stops once it finds 0 or reaches end of b.
|
||||
func cstring(b []byte) string {
|
||||
i := bytes.IndexByte(b, 0)
|
||||
if i == -1 {
|
||||
i = len(b)
|
||||
}
|
||||
return string(b[:i])
|
||||
}
|
||||
|
||||
// StringTable is a COFF string table.
|
||||
type StringTable []byte
|
||||
|
||||
func readStringTable(fh *FileHeader, r io.ReadSeeker) (StringTable, error) {
|
||||
// COFF string table is located right after COFF symbol table.
|
||||
if fh.PointerToSymbolTable <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
offset := fh.PointerToSymbolTable + COFFSymbolSize*fh.NumberOfSymbols
|
||||
_, err := r.Seek(int64(offset), seekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to seek to string table: %v", err)
|
||||
}
|
||||
var l uint32
|
||||
err = binary.Read(r, binary.LittleEndian, &l)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to read string table length: %v", err)
|
||||
}
|
||||
// string table length includes itself
|
||||
if l <= 4 {
|
||||
return nil, nil
|
||||
}
|
||||
l -= 4
|
||||
buf := make([]byte, l)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to read string table: %v", err)
|
||||
}
|
||||
return StringTable(buf), nil
|
||||
}
|
||||
|
||||
// TODO(brainman): decide if start parameter should be int instead of uint32
|
||||
|
||||
// String extracts string from COFF string table st at offset start.
|
||||
func (st StringTable) String(start uint32) (string, error) {
|
||||
// start includes 4 bytes of string table length
|
||||
if start < 4 {
|
||||
return "", fmt.Errorf("offset %d is before the start of string table", start)
|
||||
}
|
||||
start -= 4
|
||||
if int(start) > len(st) {
|
||||
return "", fmt.Errorf("offset %d is beyond the end of string table", start)
|
||||
}
|
||||
return cstring(st[start:]), nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package pe
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const COFFSymbolSize = 18
|
||||
|
||||
// COFFSymbol represents single COFF symbol table record.
|
||||
type COFFSymbol struct {
|
||||
Name [8]uint8
|
||||
Value uint32
|
||||
SectionNumber int16
|
||||
Type uint16
|
||||
StorageClass uint8
|
||||
NumberOfAuxSymbols uint8
|
||||
}
|
||||
|
||||
func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) {
|
||||
if fh.PointerToSymbolTable == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if fh.NumberOfSymbols <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
_, err := r.Seek(int64(fh.PointerToSymbolTable), seekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to seek to symbol table: %v", err)
|
||||
}
|
||||
syms := make([]COFFSymbol, fh.NumberOfSymbols)
|
||||
err = binary.Read(r, binary.LittleEndian, syms)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to read symbol table: %v", err)
|
||||
}
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
// isSymNameOffset checks symbol name if it is encoded as offset into string table.
|
||||
func isSymNameOffset(name [8]byte) (bool, uint32) {
|
||||
if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 {
|
||||
return true, binary.LittleEndian.Uint32(name[4:])
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// FullName finds real name of symbol sym. Normally name is stored
|
||||
// in sym.Name, but if it is longer then 8 characters, it is stored
|
||||
// in COFF string table st instead.
|
||||
func (sym *COFFSymbol) FullName(st StringTable) (string, error) {
|
||||
if ok, offset := isSymNameOffset(sym.Name); ok {
|
||||
return st.String(offset)
|
||||
}
|
||||
return cstring(sym.Name[:]), nil
|
||||
}
|
||||
|
||||
func removeAuxSymbols(allsyms []COFFSymbol, st StringTable) ([]*Symbol, error) {
|
||||
if len(allsyms) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
syms := make([]*Symbol, 0)
|
||||
aux := uint8(0)
|
||||
for _, sym := range allsyms {
|
||||
if aux > 0 {
|
||||
aux--
|
||||
continue
|
||||
}
|
||||
name, err := sym.FullName(st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aux = sym.NumberOfAuxSymbols
|
||||
s := &Symbol{
|
||||
Name: name,
|
||||
Value: sym.Value,
|
||||
SectionNumber: sym.SectionNumber,
|
||||
Type: sym.Type,
|
||||
StorageClass: sym.StorageClass,
|
||||
}
|
||||
syms = append(syms, s)
|
||||
}
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
// Symbol is similar to COFFSymbol with Name field replaced
|
||||
// by Go string. Symbol also does not have NumberOfAuxSymbols.
|
||||
type Symbol struct {
|
||||
Name string
|
||||
Value uint32
|
||||
SectionNumber int16
|
||||
Type uint16
|
||||
StorageClass uint8
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Package plan9obj implements access to Plan 9 a.out object files.
|
||||
package plan9obj
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A FileHeader represents a Plan 9 a.out file header.
|
||||
type FileHeader struct {
|
||||
Magic uint32
|
||||
Bss uint32
|
||||
Entry uint64
|
||||
PtrSize int
|
||||
LoadAddress uint64
|
||||
HdrSize uint64
|
||||
}
|
||||
|
||||
// A File represents an open Plan 9 a.out file.
|
||||
type File struct {
|
||||
FileHeader
|
||||
Sections []*Section
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
// A SectionHeader represents a single Plan 9 a.out section header.
|
||||
// This structure doesn't exist on-disk, but eases navigation
|
||||
// through the object file.
|
||||
type SectionHeader struct {
|
||||
Name string
|
||||
Size uint32
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
// A Section represents a single section in a Plan 9 a.out file.
|
||||
type Section struct {
|
||||
SectionHeader
|
||||
|
||||
// Embed ReaderAt for ReadAt method.
|
||||
// Do not embed SectionReader directly
|
||||
// to avoid having Read and Seek.
|
||||
// If a client wants Read and Seek it must use
|
||||
// Open() to avoid fighting over the seek offset
|
||||
// with other clients.
|
||||
io.ReaderAt
|
||||
sr *io.SectionReader
|
||||
}
|
||||
|
||||
// Data reads and returns the contents of the Plan 9 a.out section.
|
||||
func (s *Section) Data() ([]byte, error) {
|
||||
dat := make([]byte, s.sr.Size())
|
||||
n, err := s.sr.ReadAt(dat, 0)
|
||||
if n == len(dat) {
|
||||
err = nil
|
||||
}
|
||||
return dat[0:n], err
|
||||
}
|
||||
|
||||
// Open returns a new ReadSeeker reading the Plan 9 a.out section.
|
||||
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
|
||||
|
||||
// A Symbol represents an entry in a Plan 9 a.out symbol table section.
|
||||
type Sym struct {
|
||||
Value uint64
|
||||
Type rune
|
||||
Name string
|
||||
}
|
||||
|
||||
/*
|
||||
* Plan 9 a.out reader
|
||||
*/
|
||||
|
||||
// formatError is returned by some operations if the data does
|
||||
// not have the correct format for an object file.
|
||||
type formatError struct {
|
||||
off int
|
||||
msg string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func (e *formatError) Error() string {
|
||||
msg := e.msg
|
||||
if e.val != nil {
|
||||
msg += fmt.Sprintf(" '%v'", e.val)
|
||||
}
|
||||
msg += fmt.Sprintf(" in record at byte %#x", e.off)
|
||||
return msg
|
||||
}
|
||||
|
||||
// Open opens the named file using os.Open and prepares it for use as a Plan 9 a.out binary.
|
||||
func Open(name string) (*File, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ff, err := NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
ff.closer = f
|
||||
return ff, nil
|
||||
}
|
||||
|
||||
// Close closes the File.
|
||||
// If the File was created using NewFile directly instead of Open,
|
||||
// Close has no effect.
|
||||
func (f *File) Close() error {
|
||||
var err error
|
||||
if f.closer != nil {
|
||||
err = f.closer.Close()
|
||||
f.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseMagic(magic []byte) (uint32, error) {
|
||||
m := binary.BigEndian.Uint32(magic)
|
||||
switch m {
|
||||
case Magic386, MagicAMD64, MagicARM:
|
||||
return m, nil
|
||||
}
|
||||
return 0, &formatError{0, "bad magic number", magic}
|
||||
}
|
||||
|
||||
// NewFile creates a new File for accessing a Plan 9 binary in an underlying reader.
|
||||
// The Plan 9 binary is expected to start at position 0 in the ReaderAt.
|
||||
func NewFile(r io.ReaderAt) (*File, error) {
|
||||
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
||||
// Read and decode Plan 9 magic
|
||||
var magic [4]byte
|
||||
if _, err := r.ReadAt(magic[:], 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err := parseMagic(magic[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ph := new(prog)
|
||||
if err := binary.Read(sr, binary.BigEndian, ph); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := &File{FileHeader: FileHeader{
|
||||
Magic: ph.Magic,
|
||||
Bss: ph.Bss,
|
||||
Entry: uint64(ph.Entry),
|
||||
PtrSize: 4,
|
||||
LoadAddress: 0x1000,
|
||||
HdrSize: 4 * 8,
|
||||
}}
|
||||
|
||||
if ph.Magic&Magic64 != 0 {
|
||||
if err := binary.Read(sr, binary.BigEndian, &f.Entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.PtrSize = 8
|
||||
f.LoadAddress = 0x200000
|
||||
f.HdrSize += 8
|
||||
}
|
||||
|
||||
var sects = []struct {
|
||||
name string
|
||||
size uint32
|
||||
}{
|
||||
{"text", ph.Text},
|
||||
{"data", ph.Data},
|
||||
{"syms", ph.Syms},
|
||||
{"spsz", ph.Spsz},
|
||||
{"pcsz", ph.Pcsz},
|
||||
}
|
||||
|
||||
f.Sections = make([]*Section, 5)
|
||||
|
||||
off := uint32(f.HdrSize)
|
||||
|
||||
for i, sect := range sects {
|
||||
s := new(Section)
|
||||
s.SectionHeader = SectionHeader{
|
||||
Name: sect.name,
|
||||
Size: sect.size,
|
||||
Offset: off,
|
||||
}
|
||||
off += sect.size
|
||||
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Size))
|
||||
s.ReaderAt = s.sr
|
||||
f.Sections[i] = s
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func walksymtab(data []byte, ptrsz int, fn func(sym) error) error {
|
||||
var order binary.ByteOrder = binary.BigEndian
|
||||
var s sym
|
||||
p := data
|
||||
for len(p) >= 4 {
|
||||
// Symbol type, value.
|
||||
if len(p) < ptrsz {
|
||||
return &formatError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// fixed-width value
|
||||
if ptrsz == 8 {
|
||||
s.value = order.Uint64(p[0:8])
|
||||
p = p[8:]
|
||||
} else {
|
||||
s.value = uint64(order.Uint32(p[0:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
|
||||
var typ byte
|
||||
typ = p[0] & 0x7F
|
||||
s.typ = typ
|
||||
p = p[1:]
|
||||
|
||||
// Name.
|
||||
var i int
|
||||
var nnul int
|
||||
for i = 0; i < len(p); i++ {
|
||||
if p[i] == 0 {
|
||||
nnul = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
switch typ {
|
||||
case 'z', 'Z':
|
||||
p = p[i+nnul:]
|
||||
for i = 0; i+2 <= len(p); i += 2 {
|
||||
if p[i] == 0 && p[i+1] == 0 {
|
||||
nnul = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p) < i+nnul {
|
||||
return &formatError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
s.name = p[0:i]
|
||||
i += nnul
|
||||
p = p[i:]
|
||||
|
||||
fn(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTable decodes the Go symbol table in data,
|
||||
// returning an in-memory representation.
|
||||
func newTable(symtab []byte, ptrsz int) ([]Sym, error) {
|
||||
var n int
|
||||
err := walksymtab(symtab, ptrsz, func(s sym) error {
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fname := make(map[uint16]string)
|
||||
syms := make([]Sym, 0, n)
|
||||
err = walksymtab(symtab, ptrsz, func(s sym) error {
|
||||
n := len(syms)
|
||||
syms = syms[0 : n+1]
|
||||
ts := &syms[n]
|
||||
ts.Type = rune(s.typ)
|
||||
ts.Value = s.value
|
||||
switch s.typ {
|
||||
default:
|
||||
ts.Name = string(s.name)
|
||||
case 'z', 'Z':
|
||||
for i := 0; i < len(s.name); i += 2 {
|
||||
eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
|
||||
elt, ok := fname[eltIdx]
|
||||
if !ok {
|
||||
return &formatError{-1, "bad filename code", eltIdx}
|
||||
}
|
||||
if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
|
||||
ts.Name += "/"
|
||||
}
|
||||
ts.Name += elt
|
||||
}
|
||||
}
|
||||
switch s.typ {
|
||||
case 'f':
|
||||
fname[uint16(s.value)] = ts.Name
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
// Symbols returns the symbol table for f.
|
||||
func (f *File) Symbols() ([]Sym, error) {
|
||||
symtabSection := f.Section("syms")
|
||||
if symtabSection == nil {
|
||||
return nil, errors.New("no symbol section")
|
||||
}
|
||||
|
||||
symtab, err := symtabSection.Data()
|
||||
if err != nil {
|
||||
return nil, errors.New("cannot load symbol section")
|
||||
}
|
||||
|
||||
return newTable(symtab, f.PtrSize)
|
||||
}
|
||||
|
||||
func (f *File) DataAfterSection(name string) []byte {
|
||||
data := []byte{}
|
||||
found := false
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
found = true
|
||||
}
|
||||
|
||||
raw, err := s.Data()
|
||||
if found && raw != nil {
|
||||
data = append(data, raw[:]...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Section returns a section with the given name, or nil if no such
|
||||
// section exists.
|
||||
func (f *File) Section(name string) *Section {
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
/*
|
||||
* Plan 9 a.out constants and data structures
|
||||
*/
|
||||
|
||||
package plan9obj
|
||||
|
||||
// Plan 9 Program header.
|
||||
type prog struct {
|
||||
Magic uint32 /* magic number */
|
||||
Text uint32 /* size of text segment */
|
||||
Data uint32 /* size of initialized data */
|
||||
Bss uint32 /* size of uninitialized data */
|
||||
Syms uint32 /* size of symbol table */
|
||||
Entry uint32 /* entry point */
|
||||
Spsz uint32 /* size of pc/sp offset table */
|
||||
Pcsz uint32 /* size of pc/line number table */
|
||||
}
|
||||
|
||||
// Plan 9 symbol table entries.
|
||||
type sym struct {
|
||||
value uint64
|
||||
typ byte
|
||||
name []byte
|
||||
}
|
||||
|
||||
const (
|
||||
Magic64 = 0x8000 // 64-bit expanded header
|
||||
|
||||
Magic386 = (4*11+0)*11 + 7
|
||||
MagicAMD64 = (4*26+0)*26 + 7 + Magic64
|
||||
MagicARM = (4*20+0)*20 + 7
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
import http.client
|
||||
import json
|
||||
|
||||
# this generates stdpackages.go from the go github source tree
|
||||
def remove_prefix(text, prefix):
|
||||
return text[text.startswith(prefix) and len(prefix):]
|
||||
|
||||
conn = http.client.HTTPSConnection("api.github.com")
|
||||
payload = ''
|
||||
headers = {'User-Agent': 'python'}
|
||||
conn.request("GET", "/repos/golang/go/git/trees/master?recursive=0", payload, headers)
|
||||
res = conn.getresponse()
|
||||
txt = res.read().decode('utf-8')
|
||||
data = json.loads(txt)
|
||||
|
||||
|
||||
with open('stdpackages.go', 'w') as f:
|
||||
f.write("package main\nvar stdPkgs = []string{")
|
||||
|
||||
tree = data['tree']
|
||||
for item in tree:
|
||||
if item['type'] != 'tree':
|
||||
continue
|
||||
|
||||
path = item['path']
|
||||
if not path.startswith('src'):
|
||||
continue
|
||||
|
||||
if path == "src":
|
||||
continue
|
||||
|
||||
path = remove_prefix(path, 'src/')
|
||||
f.write("\"{}\",".format(path))
|
||||
|
||||
f.write("\"\"}")
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module github.com/mandiant/GoReSym
|
||||
|
||||
go 1.17
|
||||
|
||||
require golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff
|
||||
|
||||
require github.com/elliotchance/orderedmap v1.4.0 // indirect
|
||||
@@ -0,0 +1,11 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A=
|
||||
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff h1:XmKBi9R6duxOB3lfc72wyrwiOY7X2Jl1wuI+RFOyMDE=
|
||||
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package goobj
|
||||
|
||||
// Builtin (compiler-generated) function references appear
|
||||
// frequently. We assign special indices for them, so they
|
||||
// don't need to be referenced by name.
|
||||
|
||||
// NBuiltin returns the number of listed builtin
|
||||
// symbols.
|
||||
func NBuiltin() int {
|
||||
return len(builtins)
|
||||
}
|
||||
|
||||
// BuiltinName returns the name and ABI of the i-th
|
||||
// builtin symbol.
|
||||
func BuiltinName(i int) (string, int) {
|
||||
return builtins[i].name, builtins[i].abi
|
||||
}
|
||||
|
||||
// BuiltinIdx returns the index of the builtin with the
|
||||
// given name and abi, or -1 if it is not a builtin.
|
||||
func BuiltinIdx(name string, abi int) int {
|
||||
i, ok := builtinMap[name]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
if builtins[i].abi != abi {
|
||||
return -1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
//go:generate go run mkbuiltin.go
|
||||
|
||||
var builtinMap map[string]int
|
||||
|
||||
func init() {
|
||||
builtinMap = make(map[string]int, len(builtins))
|
||||
for i, b := range builtins {
|
||||
builtinMap[b.name] = i
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// Code generated by mkbuiltin.go. DO NOT EDIT.
|
||||
|
||||
package goobj
|
||||
|
||||
var builtins = [...]struct {
|
||||
name string
|
||||
abi int
|
||||
}{
|
||||
{"runtime.newobject", 1},
|
||||
{"runtime.mallocgc", 1},
|
||||
{"runtime.panicdivide", 1},
|
||||
{"runtime.panicshift", 1},
|
||||
{"runtime.panicmakeslicelen", 1},
|
||||
{"runtime.panicmakeslicecap", 1},
|
||||
{"runtime.throwinit", 1},
|
||||
{"runtime.panicwrap", 1},
|
||||
{"runtime.gopanic", 1},
|
||||
{"runtime.gorecover", 1},
|
||||
{"runtime.goschedguarded", 1},
|
||||
{"runtime.goPanicIndex", 1},
|
||||
{"runtime.goPanicIndexU", 1},
|
||||
{"runtime.goPanicSliceAlen", 1},
|
||||
{"runtime.goPanicSliceAlenU", 1},
|
||||
{"runtime.goPanicSliceAcap", 1},
|
||||
{"runtime.goPanicSliceAcapU", 1},
|
||||
{"runtime.goPanicSliceB", 1},
|
||||
{"runtime.goPanicSliceBU", 1},
|
||||
{"runtime.goPanicSlice3Alen", 1},
|
||||
{"runtime.goPanicSlice3AlenU", 1},
|
||||
{"runtime.goPanicSlice3Acap", 1},
|
||||
{"runtime.goPanicSlice3AcapU", 1},
|
||||
{"runtime.goPanicSlice3B", 1},
|
||||
{"runtime.goPanicSlice3BU", 1},
|
||||
{"runtime.goPanicSlice3C", 1},
|
||||
{"runtime.goPanicSlice3CU", 1},
|
||||
{"runtime.printbool", 1},
|
||||
{"runtime.printfloat", 1},
|
||||
{"runtime.printint", 1},
|
||||
{"runtime.printhex", 1},
|
||||
{"runtime.printuint", 1},
|
||||
{"runtime.printcomplex", 1},
|
||||
{"runtime.printstring", 1},
|
||||
{"runtime.printpointer", 1},
|
||||
{"runtime.printiface", 1},
|
||||
{"runtime.printeface", 1},
|
||||
{"runtime.printslice", 1},
|
||||
{"runtime.printnl", 1},
|
||||
{"runtime.printsp", 1},
|
||||
{"runtime.printlock", 1},
|
||||
{"runtime.printunlock", 1},
|
||||
{"runtime.concatstring2", 1},
|
||||
{"runtime.concatstring3", 1},
|
||||
{"runtime.concatstring4", 1},
|
||||
{"runtime.concatstring5", 1},
|
||||
{"runtime.concatstrings", 1},
|
||||
{"runtime.cmpstring", 1},
|
||||
{"runtime.intstring", 1},
|
||||
{"runtime.slicebytetostring", 1},
|
||||
{"runtime.slicebytetostringtmp", 1},
|
||||
{"runtime.slicerunetostring", 1},
|
||||
{"runtime.stringtoslicebyte", 1},
|
||||
{"runtime.stringtoslicerune", 1},
|
||||
{"runtime.slicecopy", 1},
|
||||
{"runtime.slicestringcopy", 1},
|
||||
{"runtime.decoderune", 1},
|
||||
{"runtime.countrunes", 1},
|
||||
{"runtime.convI2I", 1},
|
||||
{"runtime.convT16", 1},
|
||||
{"runtime.convT32", 1},
|
||||
{"runtime.convT64", 1},
|
||||
{"runtime.convTstring", 1},
|
||||
{"runtime.convTslice", 1},
|
||||
{"runtime.convT2E", 1},
|
||||
{"runtime.convT2Enoptr", 1},
|
||||
{"runtime.convT2I", 1},
|
||||
{"runtime.convT2Inoptr", 1},
|
||||
{"runtime.assertE2I", 1},
|
||||
{"runtime.assertE2I2", 1},
|
||||
{"runtime.assertI2I", 1},
|
||||
{"runtime.assertI2I2", 1},
|
||||
{"runtime.panicdottypeE", 1},
|
||||
{"runtime.panicdottypeI", 1},
|
||||
{"runtime.panicnildottype", 1},
|
||||
{"runtime.ifaceeq", 1},
|
||||
{"runtime.efaceeq", 1},
|
||||
{"runtime.fastrand", 1},
|
||||
{"runtime.makemap64", 1},
|
||||
{"runtime.makemap", 1},
|
||||
{"runtime.makemap_small", 1},
|
||||
{"runtime.mapaccess1", 1},
|
||||
{"runtime.mapaccess1_fast32", 1},
|
||||
{"runtime.mapaccess1_fast64", 1},
|
||||
{"runtime.mapaccess1_faststr", 1},
|
||||
{"runtime.mapaccess1_fat", 1},
|
||||
{"runtime.mapaccess2", 1},
|
||||
{"runtime.mapaccess2_fast32", 1},
|
||||
{"runtime.mapaccess2_fast64", 1},
|
||||
{"runtime.mapaccess2_faststr", 1},
|
||||
{"runtime.mapaccess2_fat", 1},
|
||||
{"runtime.mapassign", 1},
|
||||
{"runtime.mapassign_fast32", 1},
|
||||
{"runtime.mapassign_fast32ptr", 1},
|
||||
{"runtime.mapassign_fast64", 1},
|
||||
{"runtime.mapassign_fast64ptr", 1},
|
||||
{"runtime.mapassign_faststr", 1},
|
||||
{"runtime.mapiterinit", 1},
|
||||
{"runtime.mapdelete", 1},
|
||||
{"runtime.mapdelete_fast32", 1},
|
||||
{"runtime.mapdelete_fast64", 1},
|
||||
{"runtime.mapdelete_faststr", 1},
|
||||
{"runtime.mapiternext", 1},
|
||||
{"runtime.mapclear", 1},
|
||||
{"runtime.makechan64", 1},
|
||||
{"runtime.makechan", 1},
|
||||
{"runtime.chanrecv1", 1},
|
||||
{"runtime.chanrecv2", 1},
|
||||
{"runtime.chansend1", 1},
|
||||
{"runtime.closechan", 1},
|
||||
{"runtime.writeBarrier", 0},
|
||||
{"runtime.typedmemmove", 1},
|
||||
{"runtime.typedmemclr", 1},
|
||||
{"runtime.typedslicecopy", 1},
|
||||
{"runtime.selectnbsend", 1},
|
||||
{"runtime.selectnbrecv", 1},
|
||||
{"runtime.selectnbrecv2", 1},
|
||||
{"runtime.selectsetpc", 1},
|
||||
{"runtime.selectgo", 1},
|
||||
{"runtime.block", 1},
|
||||
{"runtime.makeslice", 1},
|
||||
{"runtime.makeslice64", 1},
|
||||
{"runtime.makeslicecopy", 1},
|
||||
{"runtime.growslice", 1},
|
||||
{"runtime.memmove", 1},
|
||||
{"runtime.memclrNoHeapPointers", 1},
|
||||
{"runtime.memclrHasPointers", 1},
|
||||
{"runtime.memequal", 1},
|
||||
{"runtime.memequal0", 1},
|
||||
{"runtime.memequal8", 1},
|
||||
{"runtime.memequal16", 1},
|
||||
{"runtime.memequal32", 1},
|
||||
{"runtime.memequal64", 1},
|
||||
{"runtime.memequal128", 1},
|
||||
{"runtime.f32equal", 1},
|
||||
{"runtime.f64equal", 1},
|
||||
{"runtime.c64equal", 1},
|
||||
{"runtime.c128equal", 1},
|
||||
{"runtime.strequal", 1},
|
||||
{"runtime.interequal", 1},
|
||||
{"runtime.nilinterequal", 1},
|
||||
{"runtime.memhash", 1},
|
||||
{"runtime.memhash0", 1},
|
||||
{"runtime.memhash8", 1},
|
||||
{"runtime.memhash16", 1},
|
||||
{"runtime.memhash32", 1},
|
||||
{"runtime.memhash64", 1},
|
||||
{"runtime.memhash128", 1},
|
||||
{"runtime.f32hash", 1},
|
||||
{"runtime.f64hash", 1},
|
||||
{"runtime.c64hash", 1},
|
||||
{"runtime.c128hash", 1},
|
||||
{"runtime.strhash", 1},
|
||||
{"runtime.interhash", 1},
|
||||
{"runtime.nilinterhash", 1},
|
||||
{"runtime.int64div", 1},
|
||||
{"runtime.uint64div", 1},
|
||||
{"runtime.int64mod", 1},
|
||||
{"runtime.uint64mod", 1},
|
||||
{"runtime.float64toint64", 1},
|
||||
{"runtime.float64touint64", 1},
|
||||
{"runtime.float64touint32", 1},
|
||||
{"runtime.int64tofloat64", 1},
|
||||
{"runtime.uint64tofloat64", 1},
|
||||
{"runtime.uint32tofloat64", 1},
|
||||
{"runtime.complex128div", 1},
|
||||
{"runtime.racefuncenter", 1},
|
||||
{"runtime.racefuncenterfp", 1},
|
||||
{"runtime.racefuncexit", 1},
|
||||
{"runtime.raceread", 1},
|
||||
{"runtime.racewrite", 1},
|
||||
{"runtime.racereadrange", 1},
|
||||
{"runtime.racewriterange", 1},
|
||||
{"runtime.msanread", 1},
|
||||
{"runtime.msanwrite", 1},
|
||||
{"runtime.checkptrAlignment", 1},
|
||||
{"runtime.checkptrArithmetic", 1},
|
||||
{"runtime.libfuzzerTraceCmp1", 1},
|
||||
{"runtime.libfuzzerTraceCmp2", 1},
|
||||
{"runtime.libfuzzerTraceCmp4", 1},
|
||||
{"runtime.libfuzzerTraceCmp8", 1},
|
||||
{"runtime.libfuzzerTraceConstCmp1", 1},
|
||||
{"runtime.libfuzzerTraceConstCmp2", 1},
|
||||
{"runtime.libfuzzerTraceConstCmp4", 1},
|
||||
{"runtime.libfuzzerTraceConstCmp8", 1},
|
||||
{"runtime.x86HasPOPCNT", 0},
|
||||
{"runtime.x86HasSSE41", 0},
|
||||
{"runtime.x86HasFMA", 0},
|
||||
{"runtime.armHasVFPv4", 0},
|
||||
{"runtime.arm64HasATOMICS", 0},
|
||||
{"runtime.deferproc", 1},
|
||||
{"runtime.deferprocStack", 1},
|
||||
{"runtime.deferreturn", 1},
|
||||
{"runtime.newproc", 1},
|
||||
{"runtime.panicoverflow", 1},
|
||||
{"runtime.sigpanic", 1},
|
||||
{"runtime.gcWriteBarrier", 0},
|
||||
{"runtime.morestack", 0},
|
||||
{"runtime.morestackc", 0},
|
||||
{"runtime.morestack_noctxt", 0},
|
||||
{"type.int8", 0},
|
||||
{"type.*int8", 0},
|
||||
{"type.uint8", 0},
|
||||
{"type.*uint8", 0},
|
||||
{"type.int16", 0},
|
||||
{"type.*int16", 0},
|
||||
{"type.uint16", 0},
|
||||
{"type.*uint16", 0},
|
||||
{"type.int32", 0},
|
||||
{"type.*int32", 0},
|
||||
{"type.uint32", 0},
|
||||
{"type.*uint32", 0},
|
||||
{"type.int64", 0},
|
||||
{"type.*int64", 0},
|
||||
{"type.uint64", 0},
|
||||
{"type.*uint64", 0},
|
||||
{"type.float32", 0},
|
||||
{"type.*float32", 0},
|
||||
{"type.float64", 0},
|
||||
{"type.*float64", 0},
|
||||
{"type.complex64", 0},
|
||||
{"type.*complex64", 0},
|
||||
{"type.complex128", 0},
|
||||
{"type.*complex128", 0},
|
||||
{"type.unsafe.Pointer", 0},
|
||||
{"type.*unsafe.Pointer", 0},
|
||||
{"type.uintptr", 0},
|
||||
{"type.*uintptr", 0},
|
||||
{"type.bool", 0},
|
||||
{"type.*bool", 0},
|
||||
{"type.string", 0},
|
||||
{"type.*string", 0},
|
||||
{"type.error", 0},
|
||||
{"type.*error", 0},
|
||||
{"type.func(error) string", 0},
|
||||
{"type.*func(error) string", 0},
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package goobj
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/mandiant/GoReSym/objabi"
|
||||
)
|
||||
|
||||
// CUFileIndex is used to index the filenames that are stored in the
|
||||
// per-package/per-CU FileList.
|
||||
type CUFileIndex uint32
|
||||
|
||||
// FuncInfo is serialized as a symbol (aux symbol). The symbol data is
|
||||
// the binary encoding of the struct below.
|
||||
//
|
||||
// TODO: make each pcdata a separate symbol?
|
||||
type FuncInfo struct {
|
||||
Args uint32
|
||||
Locals uint32
|
||||
FuncID objabi.FuncID
|
||||
|
||||
Pcsp SymRef
|
||||
Pcfile SymRef
|
||||
Pcline SymRef
|
||||
Pcinline SymRef
|
||||
Pcdata []SymRef
|
||||
Funcdataoff []uint32
|
||||
File []CUFileIndex
|
||||
|
||||
InlTree []InlTreeNode
|
||||
}
|
||||
|
||||
func (a *FuncInfo) Write(w *bytes.Buffer) {
|
||||
var b [4]byte
|
||||
writeUint32 := func(x uint32) {
|
||||
binary.LittleEndian.PutUint32(b[:], x)
|
||||
w.Write(b[:])
|
||||
}
|
||||
writeSymRef := func(s SymRef) {
|
||||
writeUint32(s.PkgIdx)
|
||||
writeUint32(s.SymIdx)
|
||||
}
|
||||
|
||||
writeUint32(a.Args)
|
||||
writeUint32(a.Locals)
|
||||
writeUint32(uint32(a.FuncID))
|
||||
|
||||
writeSymRef(a.Pcsp)
|
||||
writeSymRef(a.Pcfile)
|
||||
writeSymRef(a.Pcline)
|
||||
writeSymRef(a.Pcinline)
|
||||
writeUint32(uint32(len(a.Pcdata)))
|
||||
for _, sym := range a.Pcdata {
|
||||
writeSymRef(sym)
|
||||
}
|
||||
|
||||
writeUint32(uint32(len(a.Funcdataoff)))
|
||||
for _, x := range a.Funcdataoff {
|
||||
writeUint32(x)
|
||||
}
|
||||
writeUint32(uint32(len(a.File)))
|
||||
for _, f := range a.File {
|
||||
writeUint32(uint32(f))
|
||||
}
|
||||
writeUint32(uint32(len(a.InlTree)))
|
||||
for i := range a.InlTree {
|
||||
a.InlTree[i].Write(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FuncInfo) Read(b []byte) {
|
||||
readUint32 := func() uint32 {
|
||||
x := binary.LittleEndian.Uint32(b)
|
||||
b = b[4:]
|
||||
return x
|
||||
}
|
||||
readSymIdx := func() SymRef {
|
||||
return SymRef{readUint32(), readUint32()}
|
||||
}
|
||||
|
||||
a.Args = readUint32()
|
||||
a.Locals = readUint32()
|
||||
a.FuncID = objabi.FuncID(readUint32())
|
||||
|
||||
a.Pcsp = readSymIdx()
|
||||
a.Pcfile = readSymIdx()
|
||||
a.Pcline = readSymIdx()
|
||||
a.Pcinline = readSymIdx()
|
||||
a.Pcdata = make([]SymRef, readUint32())
|
||||
for i := range a.Pcdata {
|
||||
a.Pcdata[i] = readSymIdx()
|
||||
}
|
||||
|
||||
funcdataofflen := readUint32()
|
||||
a.Funcdataoff = make([]uint32, funcdataofflen)
|
||||
for i := range a.Funcdataoff {
|
||||
a.Funcdataoff[i] = readUint32()
|
||||
}
|
||||
filelen := readUint32()
|
||||
a.File = make([]CUFileIndex, filelen)
|
||||
for i := range a.File {
|
||||
a.File[i] = CUFileIndex(readUint32())
|
||||
}
|
||||
inltreelen := readUint32()
|
||||
a.InlTree = make([]InlTreeNode, inltreelen)
|
||||
for i := range a.InlTree {
|
||||
b = a.InlTree[i].Read(b)
|
||||
}
|
||||
}
|
||||
|
||||
// FuncInfoLengths is a cache containing a roadmap of offsets and
|
||||
// lengths for things within a serialized FuncInfo. Each length field
|
||||
// stores the number of items (e.g. files, inltree nodes, etc), and the
|
||||
// corresponding "off" field stores the byte offset of the start of
|
||||
// the items in question.
|
||||
type FuncInfoLengths struct {
|
||||
NumPcdata uint32
|
||||
PcdataOff uint32
|
||||
NumFuncdataoff uint32
|
||||
FuncdataoffOff uint32
|
||||
NumFile uint32
|
||||
FileOff uint32
|
||||
NumInlTree uint32
|
||||
InlTreeOff uint32
|
||||
Initialized bool
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadFuncInfoLengths(b []byte) FuncInfoLengths {
|
||||
var result FuncInfoLengths
|
||||
|
||||
// Offset to the number of pcdata values. This value is determined by counting
|
||||
// the number of bytes until we write pcdata to the file.
|
||||
const numpcdataOff = 44
|
||||
result.NumPcdata = binary.LittleEndian.Uint32(b[numpcdataOff:])
|
||||
result.PcdataOff = numpcdataOff + 4
|
||||
|
||||
numfuncdataoffOff := result.PcdataOff + 8*result.NumPcdata
|
||||
result.NumFuncdataoff = binary.LittleEndian.Uint32(b[numfuncdataoffOff:])
|
||||
result.FuncdataoffOff = numfuncdataoffOff + 4
|
||||
|
||||
numfileOff := result.FuncdataoffOff + 4*result.NumFuncdataoff
|
||||
result.NumFile = binary.LittleEndian.Uint32(b[numfileOff:])
|
||||
result.FileOff = numfileOff + 4
|
||||
|
||||
numinltreeOff := result.FileOff + 4*result.NumFile
|
||||
result.NumInlTree = binary.LittleEndian.Uint32(b[numinltreeOff:])
|
||||
result.InlTreeOff = numinltreeOff + 4
|
||||
|
||||
result.Initialized = true
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadArgs(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||
|
||||
func (*FuncInfo) ReadLocals(b []byte) uint32 { return binary.LittleEndian.Uint32(b[4:]) }
|
||||
|
||||
func (*FuncInfo) ReadFuncID(b []byte) uint32 { return binary.LittleEndian.Uint32(b[8:]) }
|
||||
|
||||
func (*FuncInfo) ReadPcsp(b []byte) SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(b[12:]), binary.LittleEndian.Uint32(b[16:])}
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadPcfile(b []byte) SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(b[20:]), binary.LittleEndian.Uint32(b[24:])}
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadPcline(b []byte) SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(b[28:]), binary.LittleEndian.Uint32(b[32:])}
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadPcinline(b []byte) SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(b[36:]), binary.LittleEndian.Uint32(b[40:])}
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadPcdata(b []byte) []SymRef {
|
||||
syms := make([]SymRef, binary.LittleEndian.Uint32(b[44:]))
|
||||
for i := range syms {
|
||||
syms[i] = SymRef{binary.LittleEndian.Uint32(b[48+i*8:]), binary.LittleEndian.Uint32(b[52+i*8:])}
|
||||
}
|
||||
return syms
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadFuncdataoff(b []byte, funcdataofffoff uint32, k uint32) int64 {
|
||||
return int64(binary.LittleEndian.Uint32(b[funcdataofffoff+4*k:]))
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadFile(b []byte, filesoff uint32, k uint32) CUFileIndex {
|
||||
return CUFileIndex(binary.LittleEndian.Uint32(b[filesoff+4*k:]))
|
||||
}
|
||||
|
||||
func (*FuncInfo) ReadInlTree(b []byte, inltreeoff uint32, k uint32) InlTreeNode {
|
||||
const inlTreeNodeSize = 4 * 6
|
||||
var result InlTreeNode
|
||||
result.Read(b[inltreeoff+k*inlTreeNodeSize:])
|
||||
return result
|
||||
}
|
||||
|
||||
// InlTreeNode is the serialized form of FileInfo.InlTree.
|
||||
type InlTreeNode struct {
|
||||
Parent int32
|
||||
File CUFileIndex
|
||||
Line int32
|
||||
Func SymRef
|
||||
ParentPC int32
|
||||
}
|
||||
|
||||
func (inl *InlTreeNode) Write(w *bytes.Buffer) {
|
||||
var b [4]byte
|
||||
writeUint32 := func(x uint32) {
|
||||
binary.LittleEndian.PutUint32(b[:], x)
|
||||
w.Write(b[:])
|
||||
}
|
||||
writeUint32(uint32(inl.Parent))
|
||||
writeUint32(uint32(inl.File))
|
||||
writeUint32(uint32(inl.Line))
|
||||
writeUint32(inl.Func.PkgIdx)
|
||||
writeUint32(inl.Func.SymIdx)
|
||||
writeUint32(uint32(inl.ParentPC))
|
||||
}
|
||||
|
||||
// Read an InlTreeNode from b, return the remaining bytes.
|
||||
func (inl *InlTreeNode) Read(b []byte) []byte {
|
||||
readUint32 := func() uint32 {
|
||||
x := binary.LittleEndian.Uint32(b)
|
||||
b = b[4:]
|
||||
return x
|
||||
}
|
||||
inl.Parent = int32(readUint32())
|
||||
inl.File = CUFileIndex(readUint32())
|
||||
inl.Line = int32(readUint32())
|
||||
inl.Func = SymRef{readUint32(), readUint32()}
|
||||
inl.ParentPC = int32(readUint32())
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Generate builtinlist.go from cmd/compile/internal/gc/builtin/runtime.go.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var stdout = flag.Bool("stdout", false, "write to stdout instead of builtinlist.go")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintln(&b, "// Code generated by mkbuiltin.go. DO NOT EDIT.")
|
||||
fmt.Fprintln(&b)
|
||||
fmt.Fprintln(&b, "package goobj")
|
||||
|
||||
mkbuiltin(&b)
|
||||
|
||||
out, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if *stdout {
|
||||
_, err = os.Stdout.Write(out)
|
||||
} else {
|
||||
err = ioutil.WriteFile("builtinlist.go", out, 0666)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mkbuiltin(w io.Writer) {
|
||||
pkg := "runtime"
|
||||
fset := token.NewFileSet()
|
||||
path := filepath.Join("..", "..", "compile", "internal", "gc", "builtin", "runtime.go")
|
||||
f, err := parser.ParseFile(fset, path, nil, 0)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decls := make(map[string]bool)
|
||||
|
||||
fmt.Fprintf(w, "var builtins = [...]struct{ name string; abi int }{\n")
|
||||
for _, decl := range f.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if decl.Recv != nil {
|
||||
log.Fatal("methods unsupported")
|
||||
}
|
||||
if decl.Body != nil {
|
||||
log.Fatal("unexpected function body")
|
||||
}
|
||||
declName := pkg + "." + decl.Name.Name
|
||||
decls[declName] = true
|
||||
fmt.Fprintf(w, "{%q, 1},\n", declName) // functions are ABIInternal (1)
|
||||
case *ast.GenDecl:
|
||||
if decl.Tok == token.IMPORT {
|
||||
continue
|
||||
}
|
||||
if decl.Tok != token.VAR {
|
||||
log.Fatal("unhandled declaration kind", decl.Tok)
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ValueSpec)
|
||||
if len(spec.Values) != 0 {
|
||||
log.Fatal("unexpected values")
|
||||
}
|
||||
for _, name := range spec.Names {
|
||||
declName := pkg + "." + name.Name
|
||||
decls[declName] = true
|
||||
fmt.Fprintf(w, "{%q, 0},\n", declName) // variables are ABI0
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Fatal("unhandled decl type", decl)
|
||||
}
|
||||
}
|
||||
|
||||
// The list above only contains ones that are used by the frontend.
|
||||
// The backend may create more references of builtin functions.
|
||||
// We also want to include predefined types.
|
||||
// Add them.
|
||||
extras := append(fextras[:], enumerateBasicTypes()...)
|
||||
for _, b := range extras {
|
||||
prefix := ""
|
||||
if !strings.HasPrefix(b.name, "type.") {
|
||||
prefix = pkg + "."
|
||||
}
|
||||
name := prefix + b.name
|
||||
if decls[name] {
|
||||
log.Fatalf("%q already added -- mkbuiltin.go out of sync?", name)
|
||||
}
|
||||
fmt.Fprintf(w, "{%q, %d},\n", name, b.abi)
|
||||
}
|
||||
fmt.Fprintln(w, "}")
|
||||
}
|
||||
|
||||
// addBasicTypes returns the symbol names for basic types that are
|
||||
// defined in the runtime and referenced in other packages.
|
||||
// Needs to be kept in sync with reflect.go:dumpbasictypes() and
|
||||
// reflect.go:dtypesym() in the compiler.
|
||||
func enumerateBasicTypes() []extra {
|
||||
names := [...]string{
|
||||
"int8", "uint8", "int16", "uint16",
|
||||
"int32", "uint32", "int64", "uint64",
|
||||
"float32", "float64", "complex64", "complex128",
|
||||
"unsafe.Pointer", "uintptr", "bool", "string", "error",
|
||||
"func(error) string"}
|
||||
result := []extra{}
|
||||
for _, n := range names {
|
||||
result = append(result, extra{"type." + n, 0})
|
||||
result = append(result, extra{"type.*" + n, 0})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type extra struct {
|
||||
name string
|
||||
abi int
|
||||
}
|
||||
|
||||
var fextras = [...]extra{
|
||||
// compiler frontend inserted calls (sysfunc)
|
||||
{"deferproc", 1},
|
||||
{"deferprocStack", 1},
|
||||
{"deferreturn", 1},
|
||||
{"newproc", 1},
|
||||
{"panicoverflow", 1},
|
||||
{"sigpanic", 1},
|
||||
|
||||
// compiler backend inserted calls
|
||||
{"gcWriteBarrier", 0}, // asm function, ABI0
|
||||
|
||||
// assembler backend inserted calls
|
||||
{"morestack", 0}, // asm function, ABI0
|
||||
{"morestackc", 0}, // asm function, ABI0
|
||||
{"morestack_noctxt", 0}, // asm function, ABI0
|
||||
}
|
||||
@@ -0,0 +1,872 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// This package defines the Go object file format, and provide "low-level" functions
|
||||
// for reading and writing object files.
|
||||
|
||||
// The object file is understood by the compiler, assembler, linker, and tools. They
|
||||
// have "high level" code that operates on object files, handling application-specific
|
||||
// logics, and use this package for the actual reading and writing. Specifically, the
|
||||
// code below:
|
||||
//
|
||||
// - cmd/internal/obj/objfile.go (used by cmd/asm and cmd/compile)
|
||||
// - cmd/internal/objfile/goobj.go (used cmd/nm, cmd/objdump)
|
||||
// - cmd/link/internal/loader package (used by cmd/link)
|
||||
//
|
||||
// If the object file format changes, they may (or may not) need to change.
|
||||
|
||||
package goobj
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mandiant/GoReSym/bio"
|
||||
"github.com/mandiant/GoReSym/unsafeheader"
|
||||
)
|
||||
|
||||
// New object file format.
|
||||
//
|
||||
// Header struct {
|
||||
// Magic [...]byte // "\x00go116ld"
|
||||
// Fingerprint [8]byte
|
||||
// Flags uint32
|
||||
// Offsets [...]uint32 // byte offset of each block below
|
||||
// }
|
||||
//
|
||||
// Strings [...]struct {
|
||||
// Data [...]byte
|
||||
// }
|
||||
//
|
||||
// Autolib [...]struct { // imported packages (for file loading)
|
||||
// Pkg string
|
||||
// Fingerprint [8]byte
|
||||
// }
|
||||
//
|
||||
// PkgIndex [...]string // referenced packages by index
|
||||
//
|
||||
// Files [...]string
|
||||
//
|
||||
// SymbolDefs [...]struct {
|
||||
// Name string
|
||||
// ABI uint16
|
||||
// Type uint8
|
||||
// Flag uint8
|
||||
// Flag2 uint8
|
||||
// Size uint32
|
||||
// }
|
||||
// Hashed64Defs [...]struct { // short hashed (content-addressable) symbol definitions
|
||||
// ... // same as SymbolDefs
|
||||
// }
|
||||
// HashedDefs [...]struct { // hashed (content-addressable) symbol definitions
|
||||
// ... // same as SymbolDefs
|
||||
// }
|
||||
// NonPkgDefs [...]struct { // non-pkg symbol definitions
|
||||
// ... // same as SymbolDefs
|
||||
// }
|
||||
// NonPkgRefs [...]struct { // non-pkg symbol references
|
||||
// ... // same as SymbolDefs
|
||||
// }
|
||||
//
|
||||
// RefFlags [...]struct { // referenced symbol flags
|
||||
// Sym symRef
|
||||
// Flag uint8
|
||||
// Flag2 uint8
|
||||
// }
|
||||
//
|
||||
// Hash64 [...][8]byte
|
||||
// Hash [...][N]byte
|
||||
//
|
||||
// RelocIndex [...]uint32 // index to Relocs
|
||||
// AuxIndex [...]uint32 // index to Aux
|
||||
// DataIndex [...]uint32 // offset to Data
|
||||
//
|
||||
// Relocs [...]struct {
|
||||
// Off int32
|
||||
// Size uint8
|
||||
// Type uint8
|
||||
// Add int64
|
||||
// Sym symRef
|
||||
// }
|
||||
//
|
||||
// Aux [...]struct {
|
||||
// Type uint8
|
||||
// Sym symRef
|
||||
// }
|
||||
//
|
||||
// Data [...]byte
|
||||
// Pcdata [...]byte
|
||||
//
|
||||
// // blocks only used by tools (objdump, nm)
|
||||
//
|
||||
// RefNames [...]struct { // referenced symbol names
|
||||
// Sym symRef
|
||||
// Name string
|
||||
// // TODO: include ABI version as well?
|
||||
// }
|
||||
//
|
||||
// string is encoded as is a uint32 length followed by a uint32 offset
|
||||
// that points to the corresponding string bytes.
|
||||
//
|
||||
// symRef is struct { PkgIdx, SymIdx uint32 }.
|
||||
//
|
||||
// Slice type (e.g. []symRef) is encoded as a length prefix (uint32)
|
||||
// followed by that number of elements.
|
||||
//
|
||||
// The types below correspond to the encoded data structure in the
|
||||
// object file.
|
||||
|
||||
// Symbol indexing.
|
||||
//
|
||||
// Each symbol is referenced with a pair of indices, { PkgIdx, SymIdx },
|
||||
// as the symRef struct above.
|
||||
//
|
||||
// PkgIdx is either a predeclared index (see PkgIdxNone below) or
|
||||
// an index of an imported package. For the latter case, PkgIdx is the
|
||||
// index of the package in the PkgIndex array. 0 is an invalid index.
|
||||
//
|
||||
// SymIdx is the index of the symbol in the given package.
|
||||
// - If PkgIdx is PkgIdxSelf, SymIdx is the index of the symbol in the
|
||||
// SymbolDefs array.
|
||||
// - If PkgIdx is PkgIdxHashed64, SymIdx is the index of the symbol in the
|
||||
// Hashed64Defs array.
|
||||
// - If PkgIdx is PkgIdxHashed, SymIdx is the index of the symbol in the
|
||||
// HashedDefs array.
|
||||
// - If PkgIdx is PkgIdxNone, SymIdx is the index of the symbol in the
|
||||
// NonPkgDefs array (could natually overflow to NonPkgRefs array).
|
||||
// - Otherwise, SymIdx is the index of the symbol in some other package's
|
||||
// SymbolDefs array.
|
||||
//
|
||||
// {0, 0} represents a nil symbol. Otherwise PkgIdx should not be 0.
|
||||
//
|
||||
// Hash contains the content hashes of content-addressable symbols, of
|
||||
// which PkgIdx is PkgIdxHashed, in the same order of HashedDefs array.
|
||||
// Hash64 is similar, for PkgIdxHashed64 symbols.
|
||||
//
|
||||
// RelocIndex, AuxIndex, and DataIndex contains indices/offsets to
|
||||
// Relocs/Aux/Data blocks, one element per symbol, first for all the
|
||||
// defined symbols, then all the defined hashed and non-package symbols,
|
||||
// in the same order of SymbolDefs/Hashed64Defs/HashedDefs/NonPkgDefs
|
||||
// arrays. For N total defined symbols, the array is of length N+1. The
|
||||
// last element is the total number of relocations (aux symbols, data
|
||||
// blocks, etc.).
|
||||
//
|
||||
// They can be accessed by index. For the i-th symbol, its relocations
|
||||
// are the RelocIndex[i]-th (inclusive) to RelocIndex[i+1]-th (exclusive)
|
||||
// elements in the Relocs array. Aux/Data are likewise. (The index is
|
||||
// 0-based.)
|
||||
|
||||
// Auxiliary symbols.
|
||||
//
|
||||
// Each symbol may (or may not) be associated with a number of auxiliary
|
||||
// symbols. They are described in the Aux block. See Aux struct below.
|
||||
// Currently a symbol's Gotype, FuncInfo, and associated DWARF symbols
|
||||
// are auxiliary symbols.
|
||||
|
||||
const stringRefSize = 8 // two uint32s
|
||||
|
||||
type FingerprintType [8]byte
|
||||
|
||||
func (fp FingerprintType) IsZero() bool { return fp == FingerprintType{} }
|
||||
|
||||
// Package Index.
|
||||
const (
|
||||
PkgIdxNone = (1<<31 - 1) - iota // Non-package symbols
|
||||
PkgIdxHashed64 // Short hashed (content-addressable) symbols
|
||||
PkgIdxHashed // Hashed (content-addressable) symbols
|
||||
PkgIdxBuiltin // Predefined runtime symbols (ex: runtime.newobject)
|
||||
PkgIdxSelf // Symbols defined in the current package
|
||||
PkgIdxInvalid = 0
|
||||
// The index of other referenced packages starts from 1.
|
||||
)
|
||||
|
||||
// Blocks
|
||||
const (
|
||||
BlkAutolib = iota
|
||||
BlkPkgIdx
|
||||
BlkFile
|
||||
BlkSymdef
|
||||
BlkHashed64def
|
||||
BlkHasheddef
|
||||
BlkNonpkgdef
|
||||
BlkNonpkgref
|
||||
BlkRefFlags
|
||||
BlkHash64
|
||||
BlkHash
|
||||
BlkRelocIdx
|
||||
BlkAuxIdx
|
||||
BlkDataIdx
|
||||
BlkReloc
|
||||
BlkAux
|
||||
BlkData
|
||||
BlkPcdata
|
||||
BlkRefName
|
||||
BlkEnd
|
||||
NBlk
|
||||
)
|
||||
|
||||
// File header.
|
||||
// TODO: probably no need to export this.
|
||||
type Header struct {
|
||||
Magic string
|
||||
Fingerprint FingerprintType
|
||||
Flags uint32
|
||||
Offsets [NBlk]uint32
|
||||
}
|
||||
|
||||
const Magic = "\x00go116ld"
|
||||
|
||||
func (h *Header) Write(w *Writer) {
|
||||
w.RawString(h.Magic)
|
||||
w.Bytes(h.Fingerprint[:])
|
||||
w.Uint32(h.Flags)
|
||||
for _, x := range h.Offsets {
|
||||
w.Uint32(x)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Header) Read(r *Reader) error {
|
||||
b := r.BytesAt(0, len(Magic))
|
||||
h.Magic = string(b)
|
||||
if h.Magic != Magic {
|
||||
return errors.New("wrong magic, not a Go object file")
|
||||
}
|
||||
off := uint32(len(h.Magic))
|
||||
copy(h.Fingerprint[:], r.BytesAt(off, len(h.Fingerprint)))
|
||||
off += 8
|
||||
h.Flags = r.uint32At(off)
|
||||
off += 4
|
||||
for i := range h.Offsets {
|
||||
h.Offsets[i] = r.uint32At(off)
|
||||
off += 4
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Header) Size() int {
|
||||
return len(h.Magic) + 4 + 4*len(h.Offsets)
|
||||
}
|
||||
|
||||
// Autolib
|
||||
type ImportedPkg struct {
|
||||
Pkg string
|
||||
Fingerprint FingerprintType
|
||||
}
|
||||
|
||||
const importedPkgSize = stringRefSize + 8
|
||||
|
||||
func (p *ImportedPkg) Write(w *Writer) {
|
||||
w.StringRef(p.Pkg)
|
||||
w.Bytes(p.Fingerprint[:])
|
||||
}
|
||||
|
||||
// Symbol definition.
|
||||
//
|
||||
// Serialized format:
|
||||
// Sym struct {
|
||||
// Name string
|
||||
// ABI uint16
|
||||
// Type uint8
|
||||
// Flag uint8
|
||||
// Flag2 uint8
|
||||
// Siz uint32
|
||||
// Align uint32
|
||||
// }
|
||||
type Sym [SymSize]byte
|
||||
|
||||
const SymSize = stringRefSize + 2 + 1 + 1 + 1 + 4 + 4
|
||||
|
||||
const SymABIstatic = ^uint16(0)
|
||||
|
||||
const (
|
||||
ObjFlagShared = 1 << iota // this object is built with -shared
|
||||
ObjFlagNeedNameExpansion // the linker needs to expand `"".` to package path in symbol names
|
||||
ObjFlagFromAssembly // object is from asm src, not go
|
||||
)
|
||||
|
||||
// Sym.Flag
|
||||
const (
|
||||
SymFlagDupok = 1 << iota
|
||||
SymFlagLocal
|
||||
SymFlagTypelink
|
||||
SymFlagLeaf
|
||||
SymFlagNoSplit
|
||||
SymFlagReflectMethod
|
||||
SymFlagGoType
|
||||
SymFlagTopFrame
|
||||
)
|
||||
|
||||
// Sym.Flag2
|
||||
const (
|
||||
SymFlagUsedInIface = 1 << iota
|
||||
SymFlagItab
|
||||
)
|
||||
|
||||
// Returns the length of the name of the symbol.
|
||||
func (s *Sym) NameLen(r *Reader) int {
|
||||
return int(binary.LittleEndian.Uint32(s[:]))
|
||||
}
|
||||
|
||||
func (s *Sym) Name(r *Reader) string {
|
||||
len := binary.LittleEndian.Uint32(s[:])
|
||||
off := binary.LittleEndian.Uint32(s[4:])
|
||||
return r.StringAt(off, len)
|
||||
}
|
||||
|
||||
func (s *Sym) ABI() uint16 { return binary.LittleEndian.Uint16(s[8:]) }
|
||||
func (s *Sym) Type() uint8 { return s[10] }
|
||||
func (s *Sym) Flag() uint8 { return s[11] }
|
||||
func (s *Sym) Flag2() uint8 { return s[12] }
|
||||
func (s *Sym) Siz() uint32 { return binary.LittleEndian.Uint32(s[13:]) }
|
||||
func (s *Sym) Align() uint32 { return binary.LittleEndian.Uint32(s[17:]) }
|
||||
|
||||
func (s *Sym) Dupok() bool { return s.Flag()&SymFlagDupok != 0 }
|
||||
func (s *Sym) Local() bool { return s.Flag()&SymFlagLocal != 0 }
|
||||
func (s *Sym) Typelink() bool { return s.Flag()&SymFlagTypelink != 0 }
|
||||
func (s *Sym) Leaf() bool { return s.Flag()&SymFlagLeaf != 0 }
|
||||
func (s *Sym) NoSplit() bool { return s.Flag()&SymFlagNoSplit != 0 }
|
||||
func (s *Sym) ReflectMethod() bool { return s.Flag()&SymFlagReflectMethod != 0 }
|
||||
func (s *Sym) IsGoType() bool { return s.Flag()&SymFlagGoType != 0 }
|
||||
func (s *Sym) TopFrame() bool { return s.Flag()&SymFlagTopFrame != 0 }
|
||||
func (s *Sym) UsedInIface() bool { return s.Flag2()&SymFlagUsedInIface != 0 }
|
||||
func (s *Sym) IsItab() bool { return s.Flag2()&SymFlagItab != 0 }
|
||||
|
||||
func (s *Sym) SetName(x string, w *Writer) {
|
||||
binary.LittleEndian.PutUint32(s[:], uint32(len(x)))
|
||||
binary.LittleEndian.PutUint32(s[4:], w.stringOff(x))
|
||||
}
|
||||
|
||||
func (s *Sym) SetABI(x uint16) { binary.LittleEndian.PutUint16(s[8:], x) }
|
||||
func (s *Sym) SetType(x uint8) { s[10] = x }
|
||||
func (s *Sym) SetFlag(x uint8) { s[11] = x }
|
||||
func (s *Sym) SetFlag2(x uint8) { s[12] = x }
|
||||
func (s *Sym) SetSiz(x uint32) { binary.LittleEndian.PutUint32(s[13:], x) }
|
||||
func (s *Sym) SetAlign(x uint32) { binary.LittleEndian.PutUint32(s[17:], x) }
|
||||
|
||||
func (s *Sym) Write(w *Writer) { w.Bytes(s[:]) }
|
||||
|
||||
// for testing
|
||||
func (s *Sym) fromBytes(b []byte) { copy(s[:], b) }
|
||||
|
||||
// Symbol reference.
|
||||
type SymRef struct {
|
||||
PkgIdx uint32
|
||||
SymIdx uint32
|
||||
}
|
||||
|
||||
// Hash64
|
||||
type Hash64Type [Hash64Size]byte
|
||||
|
||||
const Hash64Size = 8
|
||||
|
||||
// Hash
|
||||
type HashType [HashSize]byte
|
||||
|
||||
const HashSize = sha1.Size
|
||||
|
||||
// Relocation.
|
||||
//
|
||||
// Serialized format:
|
||||
// Reloc struct {
|
||||
// Off int32
|
||||
// Siz uint8
|
||||
// Type uint8
|
||||
// Add int64
|
||||
// Sym SymRef
|
||||
// }
|
||||
type Reloc [RelocSize]byte
|
||||
|
||||
const RelocSize = 4 + 1 + 1 + 8 + 8
|
||||
|
||||
func (r *Reloc) Off() int32 { return int32(binary.LittleEndian.Uint32(r[:])) }
|
||||
func (r *Reloc) Siz() uint8 { return r[4] }
|
||||
func (r *Reloc) Type() uint8 { return r[5] }
|
||||
func (r *Reloc) Add() int64 { return int64(binary.LittleEndian.Uint64(r[6:])) }
|
||||
func (r *Reloc) Sym() SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(r[14:]), binary.LittleEndian.Uint32(r[18:])}
|
||||
}
|
||||
|
||||
func (r *Reloc) SetOff(x int32) { binary.LittleEndian.PutUint32(r[:], uint32(x)) }
|
||||
func (r *Reloc) SetSiz(x uint8) { r[4] = x }
|
||||
func (r *Reloc) SetType(x uint8) { r[5] = x }
|
||||
func (r *Reloc) SetAdd(x int64) { binary.LittleEndian.PutUint64(r[6:], uint64(x)) }
|
||||
func (r *Reloc) SetSym(x SymRef) {
|
||||
binary.LittleEndian.PutUint32(r[14:], x.PkgIdx)
|
||||
binary.LittleEndian.PutUint32(r[18:], x.SymIdx)
|
||||
}
|
||||
|
||||
func (r *Reloc) Set(off int32, size uint8, typ uint8, add int64, sym SymRef) {
|
||||
r.SetOff(off)
|
||||
r.SetSiz(size)
|
||||
r.SetType(typ)
|
||||
r.SetAdd(add)
|
||||
r.SetSym(sym)
|
||||
}
|
||||
|
||||
func (r *Reloc) Write(w *Writer) { w.Bytes(r[:]) }
|
||||
|
||||
// for testing
|
||||
func (r *Reloc) fromBytes(b []byte) { copy(r[:], b) }
|
||||
|
||||
// Aux symbol info.
|
||||
//
|
||||
// Serialized format:
|
||||
// Aux struct {
|
||||
// Type uint8
|
||||
// Sym SymRef
|
||||
// }
|
||||
type Aux [AuxSize]byte
|
||||
|
||||
const AuxSize = 1 + 8
|
||||
|
||||
// Aux Type
|
||||
const (
|
||||
AuxGotype = iota
|
||||
AuxFuncInfo
|
||||
AuxFuncdata
|
||||
AuxDwarfInfo
|
||||
AuxDwarfLoc
|
||||
AuxDwarfRanges
|
||||
AuxDwarfLines
|
||||
AuxPcsp
|
||||
AuxPcfile
|
||||
AuxPcline
|
||||
AuxPcinline
|
||||
AuxPcdata
|
||||
)
|
||||
|
||||
func (a *Aux) Type() uint8 { return a[0] }
|
||||
func (a *Aux) Sym() SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(a[1:]), binary.LittleEndian.Uint32(a[5:])}
|
||||
}
|
||||
|
||||
func (a *Aux) SetType(x uint8) { a[0] = x }
|
||||
func (a *Aux) SetSym(x SymRef) {
|
||||
binary.LittleEndian.PutUint32(a[1:], x.PkgIdx)
|
||||
binary.LittleEndian.PutUint32(a[5:], x.SymIdx)
|
||||
}
|
||||
|
||||
func (a *Aux) Write(w *Writer) { w.Bytes(a[:]) }
|
||||
|
||||
// for testing
|
||||
func (a *Aux) fromBytes(b []byte) { copy(a[:], b) }
|
||||
|
||||
// Referenced symbol flags.
|
||||
//
|
||||
// Serialized format:
|
||||
// RefFlags struct {
|
||||
// Sym symRef
|
||||
// Flag uint8
|
||||
// Flag2 uint8
|
||||
// }
|
||||
type RefFlags [RefFlagsSize]byte
|
||||
|
||||
const RefFlagsSize = 8 + 1 + 1
|
||||
|
||||
func (r *RefFlags) Sym() SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(r[:]), binary.LittleEndian.Uint32(r[4:])}
|
||||
}
|
||||
func (r *RefFlags) Flag() uint8 { return r[8] }
|
||||
func (r *RefFlags) Flag2() uint8 { return r[9] }
|
||||
|
||||
func (r *RefFlags) SetSym(x SymRef) {
|
||||
binary.LittleEndian.PutUint32(r[:], x.PkgIdx)
|
||||
binary.LittleEndian.PutUint32(r[4:], x.SymIdx)
|
||||
}
|
||||
func (r *RefFlags) SetFlag(x uint8) { r[8] = x }
|
||||
func (r *RefFlags) SetFlag2(x uint8) { r[9] = x }
|
||||
|
||||
func (r *RefFlags) Write(w *Writer) { w.Bytes(r[:]) }
|
||||
|
||||
// Referenced symbol name.
|
||||
//
|
||||
// Serialized format:
|
||||
// RefName struct {
|
||||
// Sym symRef
|
||||
// Name string
|
||||
// }
|
||||
type RefName [RefNameSize]byte
|
||||
|
||||
const RefNameSize = 8 + stringRefSize
|
||||
|
||||
func (n *RefName) Sym() SymRef {
|
||||
return SymRef{binary.LittleEndian.Uint32(n[:]), binary.LittleEndian.Uint32(n[4:])}
|
||||
}
|
||||
func (n *RefName) Name(r *Reader) string {
|
||||
len := binary.LittleEndian.Uint32(n[8:])
|
||||
off := binary.LittleEndian.Uint32(n[12:])
|
||||
return r.StringAt(off, len)
|
||||
}
|
||||
|
||||
func (n *RefName) SetSym(x SymRef) {
|
||||
binary.LittleEndian.PutUint32(n[:], x.PkgIdx)
|
||||
binary.LittleEndian.PutUint32(n[4:], x.SymIdx)
|
||||
}
|
||||
func (n *RefName) SetName(x string, w *Writer) {
|
||||
binary.LittleEndian.PutUint32(n[8:], uint32(len(x)))
|
||||
binary.LittleEndian.PutUint32(n[12:], w.stringOff(x))
|
||||
}
|
||||
|
||||
func (n *RefName) Write(w *Writer) { w.Bytes(n[:]) }
|
||||
|
||||
type Writer struct {
|
||||
wr *bio.Writer
|
||||
stringMap map[string]uint32
|
||||
off uint32 // running offset
|
||||
}
|
||||
|
||||
func NewWriter(wr *bio.Writer) *Writer {
|
||||
return &Writer{wr: wr, stringMap: make(map[string]uint32)}
|
||||
}
|
||||
|
||||
func (w *Writer) AddString(s string) {
|
||||
if _, ok := w.stringMap[s]; ok {
|
||||
return
|
||||
}
|
||||
w.stringMap[s] = w.off
|
||||
w.RawString(s)
|
||||
}
|
||||
|
||||
func (w *Writer) stringOff(s string) uint32 {
|
||||
off, ok := w.stringMap[s]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("writeStringRef: string not added: %q", s))
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
func (w *Writer) StringRef(s string) {
|
||||
w.Uint32(uint32(len(s)))
|
||||
w.Uint32(w.stringOff(s))
|
||||
}
|
||||
|
||||
func (w *Writer) RawString(s string) {
|
||||
w.wr.WriteString(s)
|
||||
w.off += uint32(len(s))
|
||||
}
|
||||
|
||||
func (w *Writer) Bytes(s []byte) {
|
||||
w.wr.Write(s)
|
||||
w.off += uint32(len(s))
|
||||
}
|
||||
|
||||
func (w *Writer) Uint64(x uint64) {
|
||||
var b [8]byte
|
||||
binary.LittleEndian.PutUint64(b[:], x)
|
||||
w.wr.Write(b[:])
|
||||
w.off += 8
|
||||
}
|
||||
|
||||
func (w *Writer) Uint32(x uint32) {
|
||||
var b [4]byte
|
||||
binary.LittleEndian.PutUint32(b[:], x)
|
||||
w.wr.Write(b[:])
|
||||
w.off += 4
|
||||
}
|
||||
|
||||
func (w *Writer) Uint16(x uint16) {
|
||||
var b [2]byte
|
||||
binary.LittleEndian.PutUint16(b[:], x)
|
||||
w.wr.Write(b[:])
|
||||
w.off += 2
|
||||
}
|
||||
|
||||
func (w *Writer) Uint8(x uint8) {
|
||||
w.wr.WriteByte(x)
|
||||
w.off++
|
||||
}
|
||||
|
||||
func (w *Writer) Offset() uint32 {
|
||||
return w.off
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
b []byte // mmapped bytes, if not nil
|
||||
readonly bool // whether b is backed with read-only memory
|
||||
|
||||
rd io.ReaderAt
|
||||
start uint32
|
||||
h Header // keep block offsets
|
||||
}
|
||||
|
||||
func NewReaderFromBytes(b []byte, readonly bool) *Reader {
|
||||
r := &Reader{b: b, readonly: readonly, rd: bytes.NewReader(b), start: 0}
|
||||
err := r.h.Read(r)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Reader) BytesAt(off uint32, len int) []byte {
|
||||
if len == 0 {
|
||||
return nil
|
||||
}
|
||||
end := int(off) + len
|
||||
return r.b[int(off):end:end]
|
||||
}
|
||||
|
||||
func (r *Reader) uint64At(off uint32) uint64 {
|
||||
b := r.BytesAt(off, 8)
|
||||
return binary.LittleEndian.Uint64(b)
|
||||
}
|
||||
|
||||
func (r *Reader) int64At(off uint32) int64 {
|
||||
return int64(r.uint64At(off))
|
||||
}
|
||||
|
||||
func (r *Reader) uint32At(off uint32) uint32 {
|
||||
b := r.BytesAt(off, 4)
|
||||
return binary.LittleEndian.Uint32(b)
|
||||
}
|
||||
|
||||
func (r *Reader) int32At(off uint32) int32 {
|
||||
return int32(r.uint32At(off))
|
||||
}
|
||||
|
||||
func (r *Reader) uint16At(off uint32) uint16 {
|
||||
b := r.BytesAt(off, 2)
|
||||
return binary.LittleEndian.Uint16(b)
|
||||
}
|
||||
|
||||
func (r *Reader) uint8At(off uint32) uint8 {
|
||||
b := r.BytesAt(off, 1)
|
||||
return b[0]
|
||||
}
|
||||
|
||||
func (r *Reader) StringAt(off uint32, len uint32) string {
|
||||
b := r.b[off : off+len]
|
||||
if r.readonly {
|
||||
return toString(b) // backed by RO memory, ok to make unsafe string
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func toString(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var s string
|
||||
hdr := (*unsafeheader.String)(unsafe.Pointer(&s))
|
||||
hdr.Data = unsafe.Pointer(&b[0])
|
||||
hdr.Len = len(b)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Reader) StringRef(off uint32) string {
|
||||
l := r.uint32At(off)
|
||||
return r.StringAt(r.uint32At(off+4), l)
|
||||
}
|
||||
|
||||
func (r *Reader) Fingerprint() FingerprintType {
|
||||
return r.h.Fingerprint
|
||||
}
|
||||
|
||||
func (r *Reader) Autolib() []ImportedPkg {
|
||||
n := (r.h.Offsets[BlkAutolib+1] - r.h.Offsets[BlkAutolib]) / importedPkgSize
|
||||
s := make([]ImportedPkg, n)
|
||||
off := r.h.Offsets[BlkAutolib]
|
||||
for i := range s {
|
||||
s[i].Pkg = r.StringRef(off)
|
||||
copy(s[i].Fingerprint[:], r.BytesAt(off+stringRefSize, len(s[i].Fingerprint)))
|
||||
off += importedPkgSize
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Reader) Pkglist() []string {
|
||||
n := (r.h.Offsets[BlkPkgIdx+1] - r.h.Offsets[BlkPkgIdx]) / stringRefSize
|
||||
s := make([]string, n)
|
||||
off := r.h.Offsets[BlkPkgIdx]
|
||||
for i := range s {
|
||||
s[i] = r.StringRef(off)
|
||||
off += stringRefSize
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Reader) NPkg() int {
|
||||
return int(r.h.Offsets[BlkPkgIdx+1]-r.h.Offsets[BlkPkgIdx]) / stringRefSize
|
||||
}
|
||||
|
||||
func (r *Reader) Pkg(i int) string {
|
||||
off := r.h.Offsets[BlkPkgIdx] + uint32(i)*stringRefSize
|
||||
return r.StringRef(off)
|
||||
}
|
||||
|
||||
func (r *Reader) NFile() int {
|
||||
return int(r.h.Offsets[BlkFile+1]-r.h.Offsets[BlkFile]) / stringRefSize
|
||||
}
|
||||
|
||||
func (r *Reader) File(i int) string {
|
||||
off := r.h.Offsets[BlkFile] + uint32(i)*stringRefSize
|
||||
return r.StringRef(off)
|
||||
}
|
||||
|
||||
func (r *Reader) NSym() int {
|
||||
return int(r.h.Offsets[BlkSymdef+1]-r.h.Offsets[BlkSymdef]) / SymSize
|
||||
}
|
||||
|
||||
func (r *Reader) NHashed64def() int {
|
||||
return int(r.h.Offsets[BlkHashed64def+1]-r.h.Offsets[BlkHashed64def]) / SymSize
|
||||
}
|
||||
|
||||
func (r *Reader) NHasheddef() int {
|
||||
return int(r.h.Offsets[BlkHasheddef+1]-r.h.Offsets[BlkHasheddef]) / SymSize
|
||||
}
|
||||
|
||||
func (r *Reader) NNonpkgdef() int {
|
||||
return int(r.h.Offsets[BlkNonpkgdef+1]-r.h.Offsets[BlkNonpkgdef]) / SymSize
|
||||
}
|
||||
|
||||
func (r *Reader) NNonpkgref() int {
|
||||
return int(r.h.Offsets[BlkNonpkgref+1]-r.h.Offsets[BlkNonpkgref]) / SymSize
|
||||
}
|
||||
|
||||
// SymOff returns the offset of the i-th symbol.
|
||||
func (r *Reader) SymOff(i uint32) uint32 {
|
||||
return r.h.Offsets[BlkSymdef] + uint32(i*SymSize)
|
||||
}
|
||||
|
||||
// Sym returns a pointer to the i-th symbol.
|
||||
func (r *Reader) Sym(i uint32) *Sym {
|
||||
off := r.SymOff(i)
|
||||
return (*Sym)(unsafe.Pointer(&r.b[off]))
|
||||
}
|
||||
|
||||
// NRefFlags returns the number of referenced symbol flags.
|
||||
func (r *Reader) NRefFlags() int {
|
||||
return int(r.h.Offsets[BlkRefFlags+1]-r.h.Offsets[BlkRefFlags]) / RefFlagsSize
|
||||
}
|
||||
|
||||
// RefFlags returns a pointer to the i-th referenced symbol flags.
|
||||
// Note: here i is not a local symbol index, just a counter.
|
||||
func (r *Reader) RefFlags(i int) *RefFlags {
|
||||
off := r.h.Offsets[BlkRefFlags] + uint32(i*RefFlagsSize)
|
||||
return (*RefFlags)(unsafe.Pointer(&r.b[off]))
|
||||
}
|
||||
|
||||
// Hash64 returns the i-th short hashed symbol's hash.
|
||||
// Note: here i is the index of short hashed symbols, not all symbols
|
||||
// (unlike other accessors).
|
||||
func (r *Reader) Hash64(i uint32) uint64 {
|
||||
off := r.h.Offsets[BlkHash64] + uint32(i*Hash64Size)
|
||||
return r.uint64At(off)
|
||||
}
|
||||
|
||||
// Hash returns a pointer to the i-th hashed symbol's hash.
|
||||
// Note: here i is the index of hashed symbols, not all symbols
|
||||
// (unlike other accessors).
|
||||
func (r *Reader) Hash(i uint32) *HashType {
|
||||
off := r.h.Offsets[BlkHash] + uint32(i*HashSize)
|
||||
return (*HashType)(unsafe.Pointer(&r.b[off]))
|
||||
}
|
||||
|
||||
// NReloc returns the number of relocations of the i-th symbol.
|
||||
func (r *Reader) NReloc(i uint32) int {
|
||||
relocIdxOff := r.h.Offsets[BlkRelocIdx] + uint32(i*4)
|
||||
return int(r.uint32At(relocIdxOff+4) - r.uint32At(relocIdxOff))
|
||||
}
|
||||
|
||||
// RelocOff returns the offset of the j-th relocation of the i-th symbol.
|
||||
func (r *Reader) RelocOff(i uint32, j int) uint32 {
|
||||
relocIdxOff := r.h.Offsets[BlkRelocIdx] + uint32(i*4)
|
||||
relocIdx := r.uint32At(relocIdxOff)
|
||||
return r.h.Offsets[BlkReloc] + (relocIdx+uint32(j))*uint32(RelocSize)
|
||||
}
|
||||
|
||||
// Reloc returns a pointer to the j-th relocation of the i-th symbol.
|
||||
func (r *Reader) Reloc(i uint32, j int) *Reloc {
|
||||
off := r.RelocOff(i, j)
|
||||
return (*Reloc)(unsafe.Pointer(&r.b[off]))
|
||||
}
|
||||
|
||||
// Relocs returns a pointer to the relocations of the i-th symbol.
|
||||
func (r *Reader) Relocs(i uint32) []Reloc {
|
||||
off := r.RelocOff(i, 0)
|
||||
n := r.NReloc(i)
|
||||
return (*[1 << 20]Reloc)(unsafe.Pointer(&r.b[off]))[:n:n]
|
||||
}
|
||||
|
||||
// NAux returns the number of aux symbols of the i-th symbol.
|
||||
func (r *Reader) NAux(i uint32) int {
|
||||
auxIdxOff := r.h.Offsets[BlkAuxIdx] + i*4
|
||||
return int(r.uint32At(auxIdxOff+4) - r.uint32At(auxIdxOff))
|
||||
}
|
||||
|
||||
// AuxOff returns the offset of the j-th aux symbol of the i-th symbol.
|
||||
func (r *Reader) AuxOff(i uint32, j int) uint32 {
|
||||
auxIdxOff := r.h.Offsets[BlkAuxIdx] + i*4
|
||||
auxIdx := r.uint32At(auxIdxOff)
|
||||
return r.h.Offsets[BlkAux] + (auxIdx+uint32(j))*uint32(AuxSize)
|
||||
}
|
||||
|
||||
// Aux returns a pointer to the j-th aux symbol of the i-th symbol.
|
||||
func (r *Reader) Aux(i uint32, j int) *Aux {
|
||||
off := r.AuxOff(i, j)
|
||||
return (*Aux)(unsafe.Pointer(&r.b[off]))
|
||||
}
|
||||
|
||||
// Auxs returns the aux symbols of the i-th symbol.
|
||||
func (r *Reader) Auxs(i uint32) []Aux {
|
||||
off := r.AuxOff(i, 0)
|
||||
n := r.NAux(i)
|
||||
return (*[1 << 20]Aux)(unsafe.Pointer(&r.b[off]))[:n:n]
|
||||
}
|
||||
|
||||
// DataOff returns the offset of the i-th symbol's data.
|
||||
func (r *Reader) DataOff(i uint32) uint32 {
|
||||
dataIdxOff := r.h.Offsets[BlkDataIdx] + i*4
|
||||
return r.h.Offsets[BlkData] + r.uint32At(dataIdxOff)
|
||||
}
|
||||
|
||||
// DataSize returns the size of the i-th symbol's data.
|
||||
func (r *Reader) DataSize(i uint32) int {
|
||||
dataIdxOff := r.h.Offsets[BlkDataIdx] + i*4
|
||||
return int(r.uint32At(dataIdxOff+4) - r.uint32At(dataIdxOff))
|
||||
}
|
||||
|
||||
// Data returns the i-th symbol's data.
|
||||
func (r *Reader) Data(i uint32) []byte {
|
||||
dataIdxOff := r.h.Offsets[BlkDataIdx] + i*4
|
||||
base := r.h.Offsets[BlkData]
|
||||
off := r.uint32At(dataIdxOff)
|
||||
end := r.uint32At(dataIdxOff + 4)
|
||||
return r.BytesAt(base+off, int(end-off))
|
||||
}
|
||||
|
||||
// NRefName returns the number of referenced symbol names.
|
||||
func (r *Reader) NRefName() int {
|
||||
return int(r.h.Offsets[BlkRefName+1]-r.h.Offsets[BlkRefName]) / RefNameSize
|
||||
}
|
||||
|
||||
// RefName returns a pointer to the i-th referenced symbol name.
|
||||
// Note: here i is not a local symbol index, just a counter.
|
||||
func (r *Reader) RefName(i int) *RefName {
|
||||
off := r.h.Offsets[BlkRefName] + uint32(i*RefNameSize)
|
||||
return (*RefName)(unsafe.Pointer(&r.b[off]))
|
||||
}
|
||||
|
||||
// ReadOnly returns whether r.BytesAt returns read-only bytes.
|
||||
func (r *Reader) ReadOnly() bool {
|
||||
return r.readonly
|
||||
}
|
||||
|
||||
// Flags returns the flag bits read from the object file header.
|
||||
func (r *Reader) Flags() uint32 {
|
||||
return r.h.Flags
|
||||
}
|
||||
|
||||
func (r *Reader) Shared() bool { return r.Flags()&ObjFlagShared != 0 }
|
||||
func (r *Reader) NeedNameExpansion() bool { return r.Flags()&ObjFlagNeedNameExpansion != 0 }
|
||||
func (r *Reader) FromAssembly() bool { return r.Flags()&ObjFlagFromAssembly != 0 }
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Package fs defines basic interfaces to a file system.
|
||||
// A file system can be provided by the host operating system
|
||||
// but also by other packages.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mandiant/GoReSym/oserror"
|
||||
)
|
||||
|
||||
// An FS provides access to a hierarchical file system.
|
||||
//
|
||||
// The FS interface is the minimum implementation required of the file system.
|
||||
// A file system may implement additional interfaces,
|
||||
// such as fsutil.ReadFileFS, to provide additional or optimized functionality.
|
||||
// See io/fsutil for details.
|
||||
type FS interface {
|
||||
// Open opens the named file.
|
||||
//
|
||||
// When Open returns an error, it should be of type *PathError
|
||||
// with the Op field set to "open", the Path field set to name,
|
||||
// and the Err field describing the problem.
|
||||
//
|
||||
// Open should reject attempts to open names that do not satisfy
|
||||
// ValidPath(name), returning a *PathError with Err set to
|
||||
// ErrInvalid or ErrNotExist.
|
||||
Open(name string) (File, error)
|
||||
}
|
||||
|
||||
// ValidPath reports whether the given path name
|
||||
// is valid for use in a call to Open.
|
||||
// Path names passed to open are unrooted, slash-separated
|
||||
// sequences of path elements, like “x/y/z”.
|
||||
// Path names must not contain a “.” or “..” or empty element,
|
||||
// except for the special case that the root directory is named “.”.
|
||||
//
|
||||
// Paths are slash-separated on all systems, even Windows.
|
||||
// Backslashes must not appear in path names.
|
||||
func ValidPath(name string) bool {
|
||||
if name == "." {
|
||||
// special case
|
||||
return true
|
||||
}
|
||||
|
||||
// Iterate over elements in name, checking each.
|
||||
for {
|
||||
i := 0
|
||||
for i < len(name) && name[i] != '/' {
|
||||
if name[i] == '\\' {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
elem := name[:i]
|
||||
if elem == "" || elem == "." || elem == ".." {
|
||||
return false
|
||||
}
|
||||
if i == len(name) {
|
||||
return true // reached clean ending
|
||||
}
|
||||
name = name[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// A File provides access to a single file.
|
||||
// The File interface is the minimum implementation required of the file.
|
||||
// A file may implement additional interfaces, such as
|
||||
// ReadDirFile, ReaderAt, or Seeker, to provide additional or optimized functionality.
|
||||
type File interface {
|
||||
Stat() (FileInfo, error)
|
||||
Read([]byte) (int, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// A DirEntry is an entry read from a directory
|
||||
// (using the ReadDir function or a ReadDirFile's ReadDir method).
|
||||
type DirEntry interface {
|
||||
// Name returns the name of the file (or subdirectory) described by the entry.
|
||||
// This name is only the final element of the path (the base name), not the entire path.
|
||||
// For example, Name would return "hello.go" not "/home/gopher/hello.go".
|
||||
Name() string
|
||||
|
||||
// IsDir reports whether the entry describes a directory.
|
||||
IsDir() bool
|
||||
|
||||
// Type returns the type bits for the entry.
|
||||
// The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
|
||||
Type() FileMode
|
||||
|
||||
// Info returns the FileInfo for the file or subdirectory described by the entry.
|
||||
// The returned FileInfo may be from the time of the original directory read
|
||||
// or from the time of the call to Info. If the file has been removed or renamed
|
||||
// since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
|
||||
// If the entry denotes a symbolic link, Info reports the information about the link itself,
|
||||
// not the link's target.
|
||||
Info() (FileInfo, error)
|
||||
}
|
||||
|
||||
// A ReadDirFile is a directory file whose entries can be read with the ReadDir method.
|
||||
// Every directory file should implement this interface.
|
||||
// (It is permissible for any file to implement this interface,
|
||||
// but if so ReadDir should return an error for non-directories.)
|
||||
type ReadDirFile interface {
|
||||
File
|
||||
|
||||
// ReadDir reads the contents of the directory and returns
|
||||
// a slice of up to n DirEntry values in directory order.
|
||||
// Subsequent calls on the same file will yield further DirEntry values.
|
||||
//
|
||||
// If n > 0, ReadDir returns at most n DirEntry structures.
|
||||
// In this case, if ReadDir returns an empty slice, it will return
|
||||
// a non-nil error explaining why.
|
||||
// At the end of a directory, the error is io.EOF.
|
||||
//
|
||||
// If n <= 0, ReadDir returns all the DirEntry values from the directory
|
||||
// in a single slice. In this case, if ReadDir succeeds (reads all the way
|
||||
// to the end of the directory), it returns the slice and a nil error.
|
||||
// If it encounters an error before the end of the directory,
|
||||
// ReadDir returns the DirEntry list read until that point and a non-nil error.
|
||||
ReadDir(n int) ([]DirEntry, error)
|
||||
}
|
||||
|
||||
// Generic file system errors.
|
||||
// Errors returned by file systems can be tested against these errors
|
||||
// using errors.Is.
|
||||
var (
|
||||
ErrInvalid = errInvalid() // "invalid argument"
|
||||
ErrPermission = errPermission() // "permission denied"
|
||||
ErrExist = errExist() // "file already exists"
|
||||
ErrNotExist = errNotExist() // "file does not exist"
|
||||
ErrClosed = errClosed() // "file already closed"
|
||||
)
|
||||
|
||||
func errInvalid() error { return oserror.ErrInvalid }
|
||||
func errPermission() error { return oserror.ErrPermission }
|
||||
func errExist() error { return oserror.ErrExist }
|
||||
func errNotExist() error { return oserror.ErrNotExist }
|
||||
func errClosed() error { return oserror.ErrClosed }
|
||||
|
||||
// A FileInfo describes a file and is returned by Stat.
|
||||
type FileInfo interface {
|
||||
Name() string // base name of the file
|
||||
Size() int64 // length in bytes for regular files; system-dependent for others
|
||||
Mode() FileMode // file mode bits
|
||||
ModTime() time.Time // modification time
|
||||
IsDir() bool // abbreviation for Mode().IsDir()
|
||||
Sys() interface{} // underlying data source (can return nil)
|
||||
}
|
||||
|
||||
// A FileMode represents a file's mode and permission bits.
|
||||
// The bits have the same definition on all systems, so that
|
||||
// information about files can be moved from one system
|
||||
// to another portably. Not all bits apply to all systems.
|
||||
// The only required bit is ModeDir for directories.
|
||||
type FileMode uint32
|
||||
|
||||
// The defined file mode bits are the most significant bits of the FileMode.
|
||||
// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
|
||||
// The values of these bits should be considered part of the public API and
|
||||
// may be used in wire protocols or disk representations: they must not be
|
||||
// changed, although new bits might be added.
|
||||
const (
|
||||
// The single letters are the abbreviations
|
||||
// used by the String method's formatting.
|
||||
ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
|
||||
ModeAppend // a: append-only
|
||||
ModeExclusive // l: exclusive use
|
||||
ModeTemporary // T: temporary file; Plan 9 only
|
||||
ModeSymlink // L: symbolic link
|
||||
ModeDevice // D: device file
|
||||
ModeNamedPipe // p: named pipe (FIFO)
|
||||
ModeSocket // S: Unix domain socket
|
||||
ModeSetuid // u: setuid
|
||||
ModeSetgid // g: setgid
|
||||
ModeCharDevice // c: Unix character device, when ModeDevice is set
|
||||
ModeSticky // t: sticky
|
||||
ModeIrregular // ?: non-regular file; nothing else is known about this file
|
||||
|
||||
// Mask for the type bits. For regular files, none will be set.
|
||||
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
|
||||
|
||||
ModePerm FileMode = 0777 // Unix permission bits
|
||||
)
|
||||
|
||||
func (m FileMode) String() string {
|
||||
const str = "dalTLDpSugct?"
|
||||
var buf [32]byte // Mode is uint32.
|
||||
w := 0
|
||||
for i, c := range str {
|
||||
if m&(1<<uint(32-1-i)) != 0 {
|
||||
buf[w] = byte(c)
|
||||
w++
|
||||
}
|
||||
}
|
||||
if w == 0 {
|
||||
buf[w] = '-'
|
||||
w++
|
||||
}
|
||||
const rwx = "rwxrwxrwx"
|
||||
for i, c := range rwx {
|
||||
if m&(1<<uint(9-1-i)) != 0 {
|
||||
buf[w] = byte(c)
|
||||
} else {
|
||||
buf[w] = '-'
|
||||
}
|
||||
w++
|
||||
}
|
||||
return string(buf[:w])
|
||||
}
|
||||
|
||||
// IsDir reports whether m describes a directory.
|
||||
// That is, it tests for the ModeDir bit being set in m.
|
||||
func (m FileMode) IsDir() bool {
|
||||
return m&ModeDir != 0
|
||||
}
|
||||
|
||||
// IsRegular reports whether m describes a regular file.
|
||||
// That is, it tests that no mode type bits are set.
|
||||
func (m FileMode) IsRegular() bool {
|
||||
return m&ModeType == 0
|
||||
}
|
||||
|
||||
// Perm returns the Unix permission bits in m (m & ModePerm).
|
||||
func (m FileMode) Perm() FileMode {
|
||||
return m & ModePerm
|
||||
}
|
||||
|
||||
// Type returns type bits in m (m & ModeType).
|
||||
func (m FileMode) Type() FileMode {
|
||||
return m & ModeType
|
||||
}
|
||||
|
||||
// PathError records an error and the operation and file path that caused it.
|
||||
type PathError struct {
|
||||
Op string
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
|
||||
|
||||
func (e *PathError) Unwrap() error { return e.Err }
|
||||
|
||||
// Timeout reports whether this error represents a timeout.
|
||||
func (e *PathError) Timeout() bool {
|
||||
t, ok := e.Err.(interface{ Timeout() bool })
|
||||
return ok && t.Timeout()
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
// Copyright 2010 The Go 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 fs
|
||||
|
||||
import (
|
||||
"path"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// A GlobFS is a file system with a Glob method.
|
||||
type GlobFS interface {
|
||||
FS
|
||||
|
||||
// Glob returns the names of all files matching pattern,
|
||||
// providing an implementation of the top-level
|
||||
// Glob function.
|
||||
Glob(pattern string) ([]string, error)
|
||||
}
|
||||
|
||||
// Glob returns the names of all files matching pattern or nil
|
||||
// if there is no matching file. The syntax of patterns is the same
|
||||
// as in path.Match. The pattern may describe hierarchical names such as
|
||||
// /usr/*/bin/ed (assuming the Separator is '/').
|
||||
//
|
||||
// Glob ignores file system errors such as I/O errors reading directories.
|
||||
// The only possible returned error is path.ErrBadPattern, reporting that
|
||||
// the pattern is malformed.
|
||||
//
|
||||
// If fs implements GlobFS, Glob calls fs.Glob.
|
||||
// Otherwise, Glob uses ReadDir to traverse the directory tree
|
||||
// and look for matches for the pattern.
|
||||
func Glob(fsys FS, pattern string) (matches []string, err error) {
|
||||
if fsys, ok := fsys.(GlobFS); ok {
|
||||
return fsys.Glob(pattern)
|
||||
}
|
||||
|
||||
// Check pattern is well-formed.
|
||||
if _, err := path.Match(pattern, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasMeta(pattern) {
|
||||
if _, err = Stat(fsys, pattern); err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{pattern}, nil
|
||||
}
|
||||
|
||||
dir, file := path.Split(pattern)
|
||||
dir = cleanGlobPath(dir)
|
||||
|
||||
if !hasMeta(dir) {
|
||||
return glob(fsys, dir, file, nil)
|
||||
}
|
||||
|
||||
// Prevent infinite recursion. See issue 15879.
|
||||
if dir == pattern {
|
||||
return nil, path.ErrBadPattern
|
||||
}
|
||||
|
||||
var m []string
|
||||
m, err = Glob(fsys, dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, d := range m {
|
||||
matches, err = glob(fsys, d, file, matches)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cleanGlobPath prepares path for glob matching.
|
||||
func cleanGlobPath(path string) string {
|
||||
switch path {
|
||||
case "":
|
||||
return "."
|
||||
default:
|
||||
return path[0 : len(path)-1] // chop off trailing separator
|
||||
}
|
||||
}
|
||||
|
||||
// glob searches for files matching pattern in the directory dir
|
||||
// and appends them to matches, returning the updated slice.
|
||||
// If the directory cannot be opened, glob returns the existing matches.
|
||||
// New matches are added in lexicographical order.
|
||||
func glob(fs FS, dir, pattern string, matches []string) (m []string, e error) {
|
||||
m = matches
|
||||
infos, err := ReadDir(fs, dir)
|
||||
if err != nil {
|
||||
return // ignore I/O error
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
n := info.Name()
|
||||
matched, err := path.Match(pattern, n)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
if matched {
|
||||
m = append(m, path.Join(dir, n))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// hasMeta reports whether path contains any of the magic characters
|
||||
// recognized by path.Match.
|
||||
func hasMeta(path string) bool {
|
||||
for i := 0; i < len(path); i++ {
|
||||
c := path[i]
|
||||
if c == '*' || c == '?' || c == '[' || runtime.GOOS == "windows" && c == '\\' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2020 The Go 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 fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ReadDirFS is the interface implemented by a file system
|
||||
// that provides an optimized implementation of ReadDir.
|
||||
type ReadDirFS interface {
|
||||
FS
|
||||
|
||||
// ReadDir reads the named directory
|
||||
// and returns a list of directory entries sorted by filename.
|
||||
ReadDir(name string) ([]DirEntry, error)
|
||||
}
|
||||
|
||||
// ReadDir reads the named directory
|
||||
// and returns a list of directory entries sorted by filename.
|
||||
//
|
||||
// If fs implements ReadDirFS, ReadDir calls fs.ReadDir.
|
||||
// Otherwise ReadDir calls fs.Open and uses ReadDir and Close
|
||||
// on the returned file.
|
||||
func ReadDir(fsys FS, name string) ([]DirEntry, error) {
|
||||
if fsys, ok := fsys.(ReadDirFS); ok {
|
||||
return fsys.ReadDir(name)
|
||||
}
|
||||
|
||||
file, err := fsys.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
dir, ok := file.(ReadDirFile)
|
||||
if !ok {
|
||||
return nil, &PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")}
|
||||
}
|
||||
|
||||
list, err := dir.ReadDir(-1)
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
|
||||
return list, err
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2020 The Go 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 fs
|
||||
|
||||
import "io"
|
||||
|
||||
// ReadFileFS is the interface implemented by a file system
|
||||
// that provides an optimized implementation of ReadFile.
|
||||
type ReadFileFS interface {
|
||||
FS
|
||||
|
||||
// ReadFile reads the named file and returns its contents.
|
||||
// A successful call returns a nil error, not io.EOF.
|
||||
// (Because ReadFile reads the whole file, the expected EOF
|
||||
// from the final Read is not treated as an error to be reported.)
|
||||
ReadFile(name string) ([]byte, error)
|
||||
}
|
||||
|
||||
// ReadFile reads the named file from the file system fs and returns its contents.
|
||||
// A successful call returns a nil error, not io.EOF.
|
||||
// (Because ReadFile reads the whole file, the expected EOF
|
||||
// from the final Read is not treated as an error to be reported.)
|
||||
//
|
||||
// If fs implements ReadFileFS, ReadFile calls fs.ReadFile.
|
||||
// Otherwise ReadFile calls fs.Open and uses Read and Close
|
||||
// on the returned file.
|
||||
func ReadFile(fsys FS, name string) ([]byte, error) {
|
||||
if fsys, ok := fsys.(ReadFileFS); ok {
|
||||
return fsys.ReadFile(name)
|
||||
}
|
||||
|
||||
file, err := fsys.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var size int
|
||||
if info, err := file.Stat(); err == nil {
|
||||
size64 := info.Size()
|
||||
if int64(int(size64)) == size64 {
|
||||
size = int(size64)
|
||||
}
|
||||
}
|
||||
|
||||
data := make([]byte, 0, size+1)
|
||||
for {
|
||||
if len(data) >= cap(data) {
|
||||
d := append(data[:cap(data)], 0)
|
||||
data = d[:len(data)]
|
||||
}
|
||||
n, err := file.Read(data[len(data):cap(data)])
|
||||
data = data[:len(data)+n]
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2020 The Go 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 fs
|
||||
|
||||
// A StatFS is a file system with a Stat method.
|
||||
type StatFS interface {
|
||||
FS
|
||||
|
||||
// Stat returns a FileInfo describing the file.
|
||||
// If there is an error, it should be of type *PathError.
|
||||
Stat(name string) (FileInfo, error)
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the named file from the file system.
|
||||
//
|
||||
// If fs implements StatFS, Stat calls fs.Stat.
|
||||
// Otherwise, Stat opens the file to stat it.
|
||||
func Stat(fsys FS, name string) (FileInfo, error) {
|
||||
if fsys, ok := fsys.(StatFS); ok {
|
||||
return fsys.Stat(name)
|
||||
}
|
||||
|
||||
file, err := fsys.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return file.Stat()
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright 2020 The Go 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 fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path"
|
||||
)
|
||||
|
||||
// SkipDir is used as a return value from WalkDirFuncs to indicate that
|
||||
// the directory named in the call is to be skipped. It is not returned
|
||||
// as an error by any function.
|
||||
var SkipDir = errors.New("skip this directory")
|
||||
|
||||
// WalkDirFunc is the type of the function called by WalkDir to visit
|
||||
// each each file or directory.
|
||||
//
|
||||
// The path argument contains the argument to Walk as a prefix.
|
||||
// That is, if Walk is called with root argument "dir" and finds a file
|
||||
// named "a" in that directory, the walk function will be called with
|
||||
// argument "dir/a".
|
||||
//
|
||||
// The directory and file are joined with Join, which may clean the
|
||||
// directory name: if Walk is called with the root argument "x/../dir"
|
||||
// and finds a file named "a" in that directory, the walk function will
|
||||
// be called with argument "dir/a", not "x/../dir/a".
|
||||
//
|
||||
// The d argument is the fs.DirEntry for the named path.
|
||||
//
|
||||
// The error result returned by the function controls how WalkDir
|
||||
// continues. If the function returns the special value SkipDir, WalkDir
|
||||
// skips the current directory (path if d.IsDir() is true, otherwise
|
||||
// path's parent directory). Otherwise, if the function returns a non-nil
|
||||
// error, WalkDir stops entirely and returns that error.
|
||||
//
|
||||
// The err argument reports an error related to path, signaling that
|
||||
// WalkDir will not walk into that directory. The function can decide how
|
||||
// to handle that error; as described earlier, returning the error will
|
||||
// cause WalkDir to stop walking the entire tree.
|
||||
//
|
||||
// WalkDir calls the function with a non-nil err argument in two cases.
|
||||
//
|
||||
// First, if the initial os.Lstat on the root directory fails, WalkDir
|
||||
// calls the function with path set to root, d set to nil, and err set to
|
||||
// the error from os.Lstat.
|
||||
//
|
||||
// Second, if a directory's ReadDir method fails, WalkDir calls the
|
||||
// function with path set to the directory's path, d set to an
|
||||
// fs.DirEntry describing the directory, and err set to the error from
|
||||
// ReadDir. In this second case, the function is called twice with the
|
||||
// path of the directory: the first call is before the directory read is
|
||||
// attempted and has err set to nil, giving the function a chance to
|
||||
// return SkipDir and avoid the ReadDir entirely. The second call is
|
||||
// after a failed ReadDir and reports the error from ReadDir.
|
||||
// (If ReadDir succeeds, there is no second call.)
|
||||
//
|
||||
// The differences between WalkDirFunc compared to filepath.WalkFunc are:
|
||||
//
|
||||
// - The second argument has type fs.DirEntry instead of fs.FileInfo.
|
||||
// - The function is called before reading a directory, to allow SkipDir
|
||||
// to bypass the directory read entirely.
|
||||
// - If a directory read fails, the function is called a second time
|
||||
// for that directory to report the error.
|
||||
//
|
||||
type WalkDirFunc func(path string, d DirEntry, err error) error
|
||||
|
||||
// walkDir recursively descends path, calling walkDirFn.
|
||||
func walkDir(fsys FS, name string, d DirEntry, walkDirFn WalkDirFunc) error {
|
||||
if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() {
|
||||
if err == SkipDir && d.IsDir() {
|
||||
// Successfully skipped directory.
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
dirs, err := ReadDir(fsys, name)
|
||||
if err != nil {
|
||||
// Second call, to report ReadDir error.
|
||||
err = walkDirFn(name, d, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, d1 := range dirs {
|
||||
name1 := path.Join(name, d1.Name())
|
||||
if err := walkDir(fsys, name1, d1, walkDirFn); err != nil {
|
||||
if err == SkipDir {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkDir walks the file tree rooted at root, calling fn for each file or
|
||||
// directory in the tree, including root.
|
||||
//
|
||||
// All errors that arise visiting files and directories are filtered by fn:
|
||||
// see the fs.WalkDirFunc documentation for details.
|
||||
//
|
||||
// The files are walked in lexical order, which makes the output deterministic
|
||||
// but requires WalkDir to read an entire directory into memory before proceeding
|
||||
// to walk that directory.
|
||||
//
|
||||
// WalkDir does not follow symbolic links found in directories,
|
||||
// but if root itself is a symbolic link, its target will be walked.
|
||||
func WalkDir(fsys FS, root string, fn WalkDirFunc) error {
|
||||
info, err := Stat(fsys, root)
|
||||
if err != nil {
|
||||
err = fn(root, nil, err)
|
||||
} else {
|
||||
err = walkDir(fsys, root, &statDirEntry{info}, fn)
|
||||
}
|
||||
if err == SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type statDirEntry struct {
|
||||
info FileInfo
|
||||
}
|
||||
|
||||
func (d *statDirEntry) Name() string { return d.info.Name() }
|
||||
func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
|
||||
func (d *statDirEntry) Type() FileMode { return d.info.Mode().Type() }
|
||||
func (d *statDirEntry) Info() (FileInfo, error) { return d.info, nil }
|
||||
@@ -0,0 +1,642 @@
|
||||
// Copyright 2009 The Go 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 io provides basic interfaces to I/O primitives.
|
||||
// Its primary job is to wrap existing implementations of such primitives,
|
||||
// such as those in package os, into shared public interfaces that
|
||||
// abstract the functionality, plus some other related primitives.
|
||||
//
|
||||
// Because these interfaces and primitives wrap lower-level operations with
|
||||
// various implementations, unless otherwise informed clients should not
|
||||
// assume they are safe for parallel execution.
|
||||
package io
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Seek whence values.
|
||||
const (
|
||||
SeekStart = 0 // seek relative to the origin of the file
|
||||
SeekCurrent = 1 // seek relative to the current offset
|
||||
SeekEnd = 2 // seek relative to the end
|
||||
)
|
||||
|
||||
// ErrShortWrite means that a write accepted fewer bytes than requested
|
||||
// but failed to return an explicit error.
|
||||
var ErrShortWrite = errors.New("short write")
|
||||
|
||||
// errInvalidWrite means that a write returned an impossible count.
|
||||
var errInvalidWrite = errors.New("invalid write result")
|
||||
|
||||
// ErrShortBuffer means that a read required a longer buffer than was provided.
|
||||
var ErrShortBuffer = errors.New("short buffer")
|
||||
|
||||
// EOF is the error returned by Read when no more input is available.
|
||||
// (Read must return EOF itself, not an error wrapping EOF,
|
||||
// because callers will test for EOF using ==.)
|
||||
// Functions should return EOF only to signal a graceful end of input.
|
||||
// If the EOF occurs unexpectedly in a structured data stream,
|
||||
// the appropriate error is either ErrUnexpectedEOF or some other error
|
||||
// giving more detail.
|
||||
var EOF = errors.New("EOF")
|
||||
|
||||
// ErrUnexpectedEOF means that EOF was encountered in the
|
||||
// middle of reading a fixed-size block or data structure.
|
||||
var ErrUnexpectedEOF = errors.New("unexpected EOF")
|
||||
|
||||
// ErrNoProgress is returned by some clients of an Reader when
|
||||
// many calls to Read have failed to return any data or error,
|
||||
// usually the sign of a broken Reader implementation.
|
||||
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
|
||||
|
||||
// Reader is the interface that wraps the basic Read method.
|
||||
//
|
||||
// Read reads up to len(p) bytes into p. It returns the number of bytes
|
||||
// read (0 <= n <= len(p)) and any error encountered. Even if Read
|
||||
// returns n < len(p), it may use all of p as scratch space during the call.
|
||||
// If some data is available but not len(p) bytes, Read conventionally
|
||||
// returns what is available instead of waiting for more.
|
||||
//
|
||||
// When Read encounters an error or end-of-file condition after
|
||||
// successfully reading n > 0 bytes, it returns the number of
|
||||
// bytes read. It may return the (non-nil) error from the same call
|
||||
// or return the error (and n == 0) from a subsequent call.
|
||||
// An instance of this general case is that a Reader returning
|
||||
// a non-zero number of bytes at the end of the input stream may
|
||||
// return either err == EOF or err == nil. The next Read should
|
||||
// return 0, EOF.
|
||||
//
|
||||
// Callers should always process the n > 0 bytes returned before
|
||||
// considering the error err. Doing so correctly handles I/O errors
|
||||
// that happen after reading some bytes and also both of the
|
||||
// allowed EOF behaviors.
|
||||
//
|
||||
// Implementations of Read are discouraged from returning a
|
||||
// zero byte count with a nil error, except when len(p) == 0.
|
||||
// Callers should treat a return of 0 and nil as indicating that
|
||||
// nothing happened; in particular it does not indicate EOF.
|
||||
//
|
||||
// Implementations must not retain p.
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// Writer is the interface that wraps the basic Write method.
|
||||
//
|
||||
// Write writes len(p) bytes from p to the underlying data stream.
|
||||
// It returns the number of bytes written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// Write must return a non-nil error if it returns n < len(p).
|
||||
// Write must not modify the slice data, even temporarily.
|
||||
//
|
||||
// Implementations must not retain p.
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// Closer is the interface that wraps the basic Close method.
|
||||
//
|
||||
// The behavior of Close after the first call is undefined.
|
||||
// Specific implementations may document their own behavior.
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Seeker is the interface that wraps the basic Seek method.
|
||||
//
|
||||
// Seek sets the offset for the next Read or Write to offset,
|
||||
// interpreted according to whence:
|
||||
// SeekStart means relative to the start of the file,
|
||||
// SeekCurrent means relative to the current offset, and
|
||||
// SeekEnd means relative to the end.
|
||||
// Seek returns the new offset relative to the start of the
|
||||
// file and an error, if any.
|
||||
//
|
||||
// Seeking to an offset before the start of the file is an error.
|
||||
// Seeking to any positive offset is legal, but the behavior of subsequent
|
||||
// I/O operations on the underlying object is implementation-dependent.
|
||||
type Seeker interface {
|
||||
Seek(offset int64, whence int) (int64, error)
|
||||
}
|
||||
|
||||
// ReadWriter is the interface that groups the basic Read and Write methods.
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
// ReadCloser is the interface that groups the basic Read and Close methods.
|
||||
type ReadCloser interface {
|
||||
Reader
|
||||
Closer
|
||||
}
|
||||
|
||||
// WriteCloser is the interface that groups the basic Write and Close methods.
|
||||
type WriteCloser interface {
|
||||
Writer
|
||||
Closer
|
||||
}
|
||||
|
||||
// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
|
||||
type ReadWriteCloser interface {
|
||||
Reader
|
||||
Writer
|
||||
Closer
|
||||
}
|
||||
|
||||
// ReadSeeker is the interface that groups the basic Read and Seek methods.
|
||||
type ReadSeeker interface {
|
||||
Reader
|
||||
Seeker
|
||||
}
|
||||
|
||||
// ReadSeekCloser is the interface that groups the basic Read, Seek and Close
|
||||
// methods.
|
||||
type ReadSeekCloser interface {
|
||||
Reader
|
||||
Seeker
|
||||
Closer
|
||||
}
|
||||
|
||||
// WriteSeeker is the interface that groups the basic Write and Seek methods.
|
||||
type WriteSeeker interface {
|
||||
Writer
|
||||
Seeker
|
||||
}
|
||||
|
||||
// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
|
||||
type ReadWriteSeeker interface {
|
||||
Reader
|
||||
Writer
|
||||
Seeker
|
||||
}
|
||||
|
||||
// ReaderFrom is the interface that wraps the ReadFrom method.
|
||||
//
|
||||
// ReadFrom reads data from r until EOF or error.
|
||||
// The return value n is the number of bytes read.
|
||||
// Any error except EOF encountered during the read is also returned.
|
||||
//
|
||||
// The Copy function uses ReaderFrom if available.
|
||||
type ReaderFrom interface {
|
||||
ReadFrom(r Reader) (n int64, err error)
|
||||
}
|
||||
|
||||
// WriterTo is the interface that wraps the WriteTo method.
|
||||
//
|
||||
// WriteTo writes data to w until there's no more data to write or
|
||||
// when an error occurs. The return value n is the number of bytes
|
||||
// written. Any error encountered during the write is also returned.
|
||||
//
|
||||
// The Copy function uses WriterTo if available.
|
||||
type WriterTo interface {
|
||||
WriteTo(w Writer) (n int64, err error)
|
||||
}
|
||||
|
||||
// ReaderAt is the interface that wraps the basic ReadAt method.
|
||||
//
|
||||
// ReadAt reads len(p) bytes into p starting at offset off in the
|
||||
// underlying input source. It returns the number of bytes
|
||||
// read (0 <= n <= len(p)) and any error encountered.
|
||||
//
|
||||
// When ReadAt returns n < len(p), it returns a non-nil error
|
||||
// explaining why more bytes were not returned. In this respect,
|
||||
// ReadAt is stricter than Read.
|
||||
//
|
||||
// Even if ReadAt returns n < len(p), it may use all of p as scratch
|
||||
// space during the call. If some data is available but not len(p) bytes,
|
||||
// ReadAt blocks until either all the data is available or an error occurs.
|
||||
// In this respect ReadAt is different from Read.
|
||||
//
|
||||
// If the n = len(p) bytes returned by ReadAt are at the end of the
|
||||
// input source, ReadAt may return either err == EOF or err == nil.
|
||||
//
|
||||
// If ReadAt is reading from an input source with a seek offset,
|
||||
// ReadAt should not affect nor be affected by the underlying
|
||||
// seek offset.
|
||||
//
|
||||
// Clients of ReadAt can execute parallel ReadAt calls on the
|
||||
// same input source.
|
||||
//
|
||||
// Implementations must not retain p.
|
||||
type ReaderAt interface {
|
||||
ReadAt(p []byte, off int64) (n int, err error)
|
||||
}
|
||||
|
||||
// WriterAt is the interface that wraps the basic WriteAt method.
|
||||
//
|
||||
// WriteAt writes len(p) bytes from p to the underlying data stream
|
||||
// at offset off. It returns the number of bytes written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// WriteAt must return a non-nil error if it returns n < len(p).
|
||||
//
|
||||
// If WriteAt is writing to a destination with a seek offset,
|
||||
// WriteAt should not affect nor be affected by the underlying
|
||||
// seek offset.
|
||||
//
|
||||
// Clients of WriteAt can execute parallel WriteAt calls on the same
|
||||
// destination if the ranges do not overlap.
|
||||
//
|
||||
// Implementations must not retain p.
|
||||
type WriterAt interface {
|
||||
WriteAt(p []byte, off int64) (n int, err error)
|
||||
}
|
||||
|
||||
// ByteReader is the interface that wraps the ReadByte method.
|
||||
//
|
||||
// ReadByte reads and returns the next byte from the input or
|
||||
// any error encountered. If ReadByte returns an error, no input
|
||||
// byte was consumed, and the returned byte value is undefined.
|
||||
//
|
||||
// ReadByte provides an efficient interface for byte-at-time
|
||||
// processing. A Reader that does not implement ByteReader
|
||||
// can be wrapped using bufio.NewReader to add this method.
|
||||
type ByteReader interface {
|
||||
ReadByte() (byte, error)
|
||||
}
|
||||
|
||||
// ByteScanner is the interface that adds the UnreadByte method to the
|
||||
// basic ReadByte method.
|
||||
//
|
||||
// UnreadByte causes the next call to ReadByte to return the same byte
|
||||
// as the previous call to ReadByte.
|
||||
// It may be an error to call UnreadByte twice without an intervening
|
||||
// call to ReadByte.
|
||||
type ByteScanner interface {
|
||||
ByteReader
|
||||
UnreadByte() error
|
||||
}
|
||||
|
||||
// ByteWriter is the interface that wraps the WriteByte method.
|
||||
type ByteWriter interface {
|
||||
WriteByte(c byte) error
|
||||
}
|
||||
|
||||
// RuneReader is the interface that wraps the ReadRune method.
|
||||
//
|
||||
// ReadRune reads a single UTF-8 encoded Unicode character
|
||||
// and returns the rune and its size in bytes. If no character is
|
||||
// available, err will be set.
|
||||
type RuneReader interface {
|
||||
ReadRune() (r rune, size int, err error)
|
||||
}
|
||||
|
||||
// RuneScanner is the interface that adds the UnreadRune method to the
|
||||
// basic ReadRune method.
|
||||
//
|
||||
// UnreadRune causes the next call to ReadRune to return the same rune
|
||||
// as the previous call to ReadRune.
|
||||
// It may be an error to call UnreadRune twice without an intervening
|
||||
// call to ReadRune.
|
||||
type RuneScanner interface {
|
||||
RuneReader
|
||||
UnreadRune() error
|
||||
}
|
||||
|
||||
// StringWriter is the interface that wraps the WriteString method.
|
||||
type StringWriter interface {
|
||||
WriteString(s string) (n int, err error)
|
||||
}
|
||||
|
||||
// WriteString writes the contents of the string s to w, which accepts a slice of bytes.
|
||||
// If w implements StringWriter, its WriteString method is invoked directly.
|
||||
// Otherwise, w.Write is called exactly once.
|
||||
func WriteString(w Writer, s string) (n int, err error) {
|
||||
if sw, ok := w.(StringWriter); ok {
|
||||
return sw.WriteString(s)
|
||||
}
|
||||
return w.Write([]byte(s))
|
||||
}
|
||||
|
||||
// ReadAtLeast reads from r into buf until it has read at least min bytes.
|
||||
// It returns the number of bytes copied and an error if fewer bytes were read.
|
||||
// The error is EOF only if no bytes were read.
|
||||
// If an EOF happens after reading fewer than min bytes,
|
||||
// ReadAtLeast returns ErrUnexpectedEOF.
|
||||
// If min is greater than the length of buf, ReadAtLeast returns ErrShortBuffer.
|
||||
// On return, n >= min if and only if err == nil.
|
||||
// If r returns an error having read at least min bytes, the error is dropped.
|
||||
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
|
||||
if len(buf) < min {
|
||||
return 0, ErrShortBuffer
|
||||
}
|
||||
for n < min && err == nil {
|
||||
var nn int
|
||||
nn, err = r.Read(buf[n:])
|
||||
n += nn
|
||||
}
|
||||
if n >= min {
|
||||
err = nil
|
||||
} else if n > 0 && err == EOF {
|
||||
err = ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadFull reads exactly len(buf) bytes from r into buf.
|
||||
// It returns the number of bytes copied and an error if fewer bytes were read.
|
||||
// The error is EOF only if no bytes were read.
|
||||
// If an EOF happens after reading some but not all the bytes,
|
||||
// ReadFull returns ErrUnexpectedEOF.
|
||||
// On return, n == len(buf) if and only if err == nil.
|
||||
// If r returns an error having read at least len(buf) bytes, the error is dropped.
|
||||
func ReadFull(r Reader, buf []byte) (n int, err error) {
|
||||
return ReadAtLeast(r, buf, len(buf))
|
||||
}
|
||||
|
||||
// CopyN copies n bytes (or until an error) from src to dst.
|
||||
// It returns the number of bytes copied and the earliest
|
||||
// error encountered while copying.
|
||||
// On return, written == n if and only if err == nil.
|
||||
//
|
||||
// If dst implements the ReaderFrom interface,
|
||||
// the copy is implemented using it.
|
||||
func CopyN(dst Writer, src Reader, n int64) (written int64, err error) {
|
||||
written, err = Copy(dst, LimitReader(src, n))
|
||||
if written == n {
|
||||
return n, nil
|
||||
}
|
||||
if written < n && err == nil {
|
||||
// src stopped early; must have been EOF.
|
||||
err = EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Copy copies from src to dst until either EOF is reached
|
||||
// on src or an error occurs. It returns the number of bytes
|
||||
// copied and the first error encountered while copying, if any.
|
||||
//
|
||||
// A successful Copy returns err == nil, not err == EOF.
|
||||
// Because Copy is defined to read from src until EOF, it does
|
||||
// not treat an EOF from Read as an error to be reported.
|
||||
//
|
||||
// If src implements the WriterTo interface,
|
||||
// the copy is implemented by calling src.WriteTo(dst).
|
||||
// Otherwise, if dst implements the ReaderFrom interface,
|
||||
// the copy is implemented by calling dst.ReadFrom(src).
|
||||
func Copy(dst Writer, src Reader) (written int64, err error) {
|
||||
return copyBuffer(dst, src, nil)
|
||||
}
|
||||
|
||||
// CopyBuffer is identical to Copy except that it stages through the
|
||||
// provided buffer (if one is required) rather than allocating a
|
||||
// temporary one. If buf is nil, one is allocated; otherwise if it has
|
||||
// zero length, CopyBuffer panics.
|
||||
//
|
||||
// If either src implements WriterTo or dst implements ReaderFrom,
|
||||
// buf will not be used to perform the copy.
|
||||
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
|
||||
if buf != nil && len(buf) == 0 {
|
||||
panic("empty buffer in CopyBuffer")
|
||||
}
|
||||
return copyBuffer(dst, src, buf)
|
||||
}
|
||||
|
||||
// copyBuffer is the actual implementation of Copy and CopyBuffer.
|
||||
// if buf is nil, one is allocated.
|
||||
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
|
||||
// If the reader has a WriteTo method, use it to do the copy.
|
||||
// Avoids an allocation and a copy.
|
||||
if wt, ok := src.(WriterTo); ok {
|
||||
return wt.WriteTo(dst)
|
||||
}
|
||||
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
|
||||
if rt, ok := dst.(ReaderFrom); ok {
|
||||
return rt.ReadFrom(src)
|
||||
}
|
||||
if buf == nil {
|
||||
size := 32 * 1024
|
||||
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
|
||||
if l.N < 1 {
|
||||
size = 1
|
||||
} else {
|
||||
size = int(l.N)
|
||||
}
|
||||
}
|
||||
buf = make([]byte, size)
|
||||
}
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
if nw < 0 || nr < nw {
|
||||
nw = 0
|
||||
if ew == nil {
|
||||
ew = errInvalidWrite
|
||||
}
|
||||
}
|
||||
written += int64(nw)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er != nil {
|
||||
if er != EOF {
|
||||
err = er
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
|
||||
// LimitReader returns a Reader that reads from r
|
||||
// but stops with EOF after n bytes.
|
||||
// The underlying implementation is a *LimitedReader.
|
||||
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
|
||||
|
||||
// A LimitedReader reads from R but limits the amount of
|
||||
// data returned to just N bytes. Each call to Read
|
||||
// updates N to reflect the new amount remaining.
|
||||
// Read returns EOF when N <= 0 or when the underlying R returns EOF.
|
||||
type LimitedReader struct {
|
||||
R Reader // underlying reader
|
||||
N int64 // max bytes remaining
|
||||
}
|
||||
|
||||
func (l *LimitedReader) Read(p []byte) (n int, err error) {
|
||||
if l.N <= 0 {
|
||||
return 0, EOF
|
||||
}
|
||||
if int64(len(p)) > l.N {
|
||||
p = p[0:l.N]
|
||||
}
|
||||
n, err = l.R.Read(p)
|
||||
l.N -= int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// NewSectionReader returns a SectionReader that reads from r
|
||||
// starting at offset off and stops with EOF after n bytes.
|
||||
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
|
||||
return &SectionReader{r, off, off, off + n}
|
||||
}
|
||||
|
||||
// SectionReader implements Read, Seek, and ReadAt on a section
|
||||
// of an underlying ReaderAt.
|
||||
type SectionReader struct {
|
||||
r ReaderAt
|
||||
base int64
|
||||
off int64
|
||||
limit int64
|
||||
}
|
||||
|
||||
func (s *SectionReader) Read(p []byte) (n int, err error) {
|
||||
if s.off >= s.limit {
|
||||
return 0, EOF
|
||||
}
|
||||
if max := s.limit - s.off; int64(len(p)) > max {
|
||||
p = p[0:max]
|
||||
}
|
||||
n, err = s.r.ReadAt(p, s.off)
|
||||
s.off += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
var errWhence = errors.New("Seek: invalid whence")
|
||||
var errOffset = errors.New("Seek: invalid offset")
|
||||
|
||||
func (s *SectionReader) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
default:
|
||||
return 0, errWhence
|
||||
case SeekStart:
|
||||
offset += s.base
|
||||
case SeekCurrent:
|
||||
offset += s.off
|
||||
case SeekEnd:
|
||||
offset += s.limit
|
||||
}
|
||||
if offset < s.base {
|
||||
return 0, errOffset
|
||||
}
|
||||
s.off = offset
|
||||
return offset - s.base, nil
|
||||
}
|
||||
|
||||
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
if off < 0 || off >= s.limit-s.base {
|
||||
return 0, EOF
|
||||
}
|
||||
off += s.base
|
||||
if max := s.limit - off; int64(len(p)) > max {
|
||||
p = p[0:max]
|
||||
n, err = s.r.ReadAt(p, off)
|
||||
if err == nil {
|
||||
err = EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
return s.r.ReadAt(p, off)
|
||||
}
|
||||
|
||||
// Size returns the size of the section in bytes.
|
||||
func (s *SectionReader) Size() int64 { return s.limit - s.base }
|
||||
|
||||
// TeeReader returns a Reader that writes to w what it reads from r.
|
||||
// All reads from r performed through it are matched with
|
||||
// corresponding writes to w. There is no internal buffering -
|
||||
// the write must complete before the read completes.
|
||||
// Any error encountered while writing is reported as a read error.
|
||||
func TeeReader(r Reader, w Writer) Reader {
|
||||
return &teeReader{r, w}
|
||||
}
|
||||
|
||||
type teeReader struct {
|
||||
r Reader
|
||||
w Writer
|
||||
}
|
||||
|
||||
func (t *teeReader) Read(p []byte) (n int, err error) {
|
||||
n, err = t.r.Read(p)
|
||||
if n > 0 {
|
||||
if n, err := t.w.Write(p[:n]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Discard is an Writer on which all Write calls succeed
|
||||
// without doing anything.
|
||||
var Discard Writer = discard{}
|
||||
|
||||
type discard struct{}
|
||||
|
||||
// discard implements ReaderFrom as an optimization so Copy to
|
||||
// io.Discard can avoid doing unnecessary work.
|
||||
var _ ReaderFrom = discard{}
|
||||
|
||||
func (discard) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (discard) WriteString(s string) (int, error) {
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
var blackHolePool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
b := make([]byte, 8192)
|
||||
return &b
|
||||
},
|
||||
}
|
||||
|
||||
func (discard) ReadFrom(r Reader) (n int64, err error) {
|
||||
bufp := blackHolePool.Get().(*[]byte)
|
||||
readSize := 0
|
||||
for {
|
||||
readSize, err = r.Read(*bufp)
|
||||
n += int64(readSize)
|
||||
if err != nil {
|
||||
blackHolePool.Put(bufp)
|
||||
if err == EOF {
|
||||
return n, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NopCloser returns a ReadCloser with a no-op Close method wrapping
|
||||
// the provided Reader r.
|
||||
func NopCloser(r Reader) ReadCloser {
|
||||
return nopCloser{r}
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
Reader
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
// ReadAll reads from r until an error or EOF and returns the data it read.
|
||||
// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
||||
// defined to read from src until EOF, it does not treat an EOF from Read
|
||||
// as an error to be reported.
|
||||
func ReadAll(r Reader) ([]byte, error) {
|
||||
b := make([]byte, 0, 512)
|
||||
for {
|
||||
if len(b) == cap(b) {
|
||||
// Add more capacity (let append pick how much).
|
||||
b = append(b, 0)[:len(b)]
|
||||
}
|
||||
n, err := r.Read(b[len(b):cap(b)])
|
||||
b = b[:len(b)+n]
|
||||
if err != nil {
|
||||
if err == EOF {
|
||||
err = nil
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2009 The Go 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 ioutil implements some I/O utility functions.
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ReadAll reads from r until an error or EOF and returns the data it read.
|
||||
// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
||||
// defined to read from src until EOF, it does not treat an EOF from Read
|
||||
// as an error to be reported.
|
||||
//
|
||||
// As of Go 1.16, this function simply calls io.ReadAll.
|
||||
func ReadAll(r io.Reader) ([]byte, error) {
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
|
||||
// ReadFile reads the file named by filename and returns the contents.
|
||||
// A successful call returns err == nil, not err == EOF. Because ReadFile
|
||||
// reads the whole file, it does not treat an EOF from Read as an error
|
||||
// to be reported.
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
// It's a good but not certain bet that FileInfo will tell us exactly how much to
|
||||
// read, so let's try it but be prepared for the answer to be wrong.
|
||||
const minRead = 512
|
||||
var n int64 = minRead
|
||||
|
||||
if fi, err := f.Stat(); err == nil {
|
||||
// As initial capacity for readAll, use Size + a little extra in case Size
|
||||
// is zero, and to avoid another allocation after Read has filled the
|
||||
// buffer. The readAll call will read into its allocated internal buffer
|
||||
// cheaply. If the size was wrong, we'll either waste some space off the end
|
||||
// or reallocate as needed, but in the overwhelmingly common case we'll get
|
||||
// it just right.
|
||||
if size := fi.Size() + minRead; size > n {
|
||||
n = size
|
||||
}
|
||||
}
|
||||
|
||||
if int64(int(n)) != n {
|
||||
n = minRead
|
||||
}
|
||||
|
||||
b := make([]byte, 0, n)
|
||||
for {
|
||||
if len(b) == cap(b) {
|
||||
// Add more capacity (let append pick how much).
|
||||
b = append(b, 0)[:len(b)]
|
||||
}
|
||||
n, err := f.Read(b[len(b):cap(b)])
|
||||
b = b[:len(b)+n]
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteFile writes data to a file named by filename.
|
||||
// If the file does not exist, WriteFile creates it with permissions perm
|
||||
// (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
|
||||
func WriteFile(filename string, data []byte, perm fs.FileMode) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write(data)
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of directory entries sorted by filename.
|
||||
func ReadDir(dirname string) ([]fs.FileInfo, error) {
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// NopCloser returns a ReadCloser with a no-op Close method wrapping
|
||||
// the provided Reader r.
|
||||
//
|
||||
// As of Go 1.16, this function simply calls io.NopCloser.
|
||||
func NopCloser(r io.Reader) io.ReadCloser {
|
||||
return io.NopCloser(r)
|
||||
}
|
||||
|
||||
// Discard is an io.Writer on which all Write calls succeed
|
||||
// without doing anything.
|
||||
//
|
||||
// As of Go 1.16, this value is simply io.Discard.
|
||||
var Discard io.Writer = io.Discard
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2010 The Go 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 ioutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Random number state.
|
||||
// We generate random temporary file names so that there's a good
|
||||
// chance the file doesn't exist yet - keeps the number of tries in
|
||||
// TempFile to a minimum.
|
||||
var rand uint32
|
||||
var randmu sync.Mutex
|
||||
|
||||
func reseed() uint32 {
|
||||
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||
}
|
||||
|
||||
func nextRandom() string {
|
||||
randmu.Lock()
|
||||
r := rand
|
||||
if r == 0 {
|
||||
r = reseed()
|
||||
}
|
||||
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
||||
rand = r
|
||||
randmu.Unlock()
|
||||
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
||||
}
|
||||
|
||||
// TempFile creates a new temporary file in the directory dir,
|
||||
// opens the file for reading and writing, and returns the resulting *os.File.
|
||||
// The filename is generated by taking pattern and adding a random
|
||||
// string to the end. If pattern includes a "*", the random string
|
||||
// replaces the last "*".
|
||||
// If dir is the empty string, TempFile uses the default directory
|
||||
// for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempFile simultaneously
|
||||
// will not choose the same file. The caller can use f.Name()
|
||||
// to find the pathname of the file. It is the caller's responsibility
|
||||
// to remove the file when no longer needed.
|
||||
func TempFile(dir, pattern string) (f *os.File, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
prefix, suffix, err := prefixAndSuffix(pattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
name := filepath.Join(dir, prefix+nextRandom()+suffix)
|
||||
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var errPatternHasSeparator = errors.New("pattern contains path separator")
|
||||
|
||||
// prefixAndSuffix splits pattern by the last wildcard "*", if applicable,
|
||||
// returning prefix as the part before "*" and suffix as the part after "*".
|
||||
func prefixAndSuffix(pattern string) (prefix, suffix string, err error) {
|
||||
if strings.ContainsRune(pattern, os.PathSeparator) {
|
||||
err = errPatternHasSeparator
|
||||
return
|
||||
}
|
||||
if pos := strings.LastIndex(pattern, "*"); pos != -1 {
|
||||
prefix, suffix = pattern[:pos], pattern[pos+1:]
|
||||
} else {
|
||||
prefix = pattern
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TempDir creates a new temporary directory in the directory dir.
|
||||
// The directory name is generated by taking pattern and applying a
|
||||
// random string to the end. If pattern includes a "*", the random string
|
||||
// replaces the last "*". TempDir returns the name of the new directory.
|
||||
// If dir is the empty string, TempDir uses the
|
||||
// default directory for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempDir simultaneously
|
||||
// will not choose the same directory. It is the caller's responsibility
|
||||
// to remove the directory when no longer needed.
|
||||
func TempDir(dir, pattern string) (name string, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
prefix, suffix, err := prefixAndSuffix(pattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
try := filepath.Join(dir, prefix+nextRandom()+suffix)
|
||||
err = os.Mkdir(try, 0700)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
name = try
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// Copyright 2010 The Go 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 io
|
||||
|
||||
type eofReader struct{}
|
||||
|
||||
func (eofReader) Read([]byte) (int, error) {
|
||||
return 0, EOF
|
||||
}
|
||||
|
||||
type multiReader struct {
|
||||
readers []Reader
|
||||
}
|
||||
|
||||
func (mr *multiReader) Read(p []byte) (n int, err error) {
|
||||
for len(mr.readers) > 0 {
|
||||
// Optimization to flatten nested multiReaders (Issue 13558).
|
||||
if len(mr.readers) == 1 {
|
||||
if r, ok := mr.readers[0].(*multiReader); ok {
|
||||
mr.readers = r.readers
|
||||
continue
|
||||
}
|
||||
}
|
||||
n, err = mr.readers[0].Read(p)
|
||||
if err == EOF {
|
||||
// Use eofReader instead of nil to avoid nil panic
|
||||
// after performing flatten (Issue 18232).
|
||||
mr.readers[0] = eofReader{} // permit earlier GC
|
||||
mr.readers = mr.readers[1:]
|
||||
}
|
||||
if n > 0 || err != EOF {
|
||||
if err == EOF && len(mr.readers) > 0 {
|
||||
// Don't return EOF yet. More readers remain.
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return 0, EOF
|
||||
}
|
||||
|
||||
// MultiReader returns a Reader that's the logical concatenation of
|
||||
// the provided input readers. They're read sequentially. Once all
|
||||
// inputs have returned EOF, Read will return EOF. If any of the readers
|
||||
// return a non-nil, non-EOF error, Read will return that error.
|
||||
func MultiReader(readers ...Reader) Reader {
|
||||
r := make([]Reader, len(readers))
|
||||
copy(r, readers)
|
||||
return &multiReader{r}
|
||||
}
|
||||
|
||||
type multiWriter struct {
|
||||
writers []Writer
|
||||
}
|
||||
|
||||
func (t *multiWriter) Write(p []byte) (n int, err error) {
|
||||
for _, w := range t.writers {
|
||||
n, err = w.Write(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != len(p) {
|
||||
err = ErrShortWrite
|
||||
return
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var _ StringWriter = (*multiWriter)(nil)
|
||||
|
||||
func (t *multiWriter) WriteString(s string) (n int, err error) {
|
||||
var p []byte // lazily initialized if/when needed
|
||||
for _, w := range t.writers {
|
||||
if sw, ok := w.(StringWriter); ok {
|
||||
n, err = sw.WriteString(s)
|
||||
} else {
|
||||
if p == nil {
|
||||
p = []byte(s)
|
||||
}
|
||||
n, err = w.Write(p)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != len(s) {
|
||||
err = ErrShortWrite
|
||||
return
|
||||
}
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
// MultiWriter creates a writer that duplicates its writes to all the
|
||||
// provided writers, similar to the Unix tee(1) command.
|
||||
//
|
||||
// Each write is written to each listed writer, one at a time.
|
||||
// If a listed writer returns an error, that overall write operation
|
||||
// stops and returns the error; it does not continue down the list.
|
||||
func MultiWriter(writers ...Writer) Writer {
|
||||
allWriters := make([]Writer, 0, len(writers))
|
||||
for _, w := range writers {
|
||||
if mw, ok := w.(*multiWriter); ok {
|
||||
allWriters = append(allWriters, mw.writers...)
|
||||
} else {
|
||||
allWriters = append(allWriters, w)
|
||||
}
|
||||
}
|
||||
return &multiWriter{allWriters}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Pipe adapter to connect code expecting an io.Reader
|
||||
// with code expecting an io.Writer.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// onceError is an object that will only store an error once.
|
||||
type onceError struct {
|
||||
sync.Mutex // guards following
|
||||
err error
|
||||
}
|
||||
|
||||
func (a *onceError) Store(err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
if a.err != nil {
|
||||
return
|
||||
}
|
||||
a.err = err
|
||||
}
|
||||
func (a *onceError) Load() error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
return a.err
|
||||
}
|
||||
|
||||
// ErrClosedPipe is the error used for read or write operations on a closed pipe.
|
||||
var ErrClosedPipe = errors.New("io: read/write on closed pipe")
|
||||
|
||||
// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
|
||||
type pipe struct {
|
||||
wrMu sync.Mutex // Serializes Write operations
|
||||
wrCh chan []byte
|
||||
rdCh chan int
|
||||
|
||||
once sync.Once // Protects closing done
|
||||
done chan struct{}
|
||||
rerr onceError
|
||||
werr onceError
|
||||
}
|
||||
|
||||
func (p *pipe) Read(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-p.done:
|
||||
return 0, p.readCloseError()
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case bw := <-p.wrCh:
|
||||
nr := copy(b, bw)
|
||||
p.rdCh <- nr
|
||||
return nr, nil
|
||||
case <-p.done:
|
||||
return 0, p.readCloseError()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) readCloseError() error {
|
||||
rerr := p.rerr.Load()
|
||||
if werr := p.werr.Load(); rerr == nil && werr != nil {
|
||||
return werr
|
||||
}
|
||||
return ErrClosedPipe
|
||||
}
|
||||
|
||||
func (p *pipe) CloseRead(err error) error {
|
||||
if err == nil {
|
||||
err = ErrClosedPipe
|
||||
}
|
||||
p.rerr.Store(err)
|
||||
p.once.Do(func() { close(p.done) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pipe) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-p.done:
|
||||
return 0, p.writeCloseError()
|
||||
default:
|
||||
p.wrMu.Lock()
|
||||
defer p.wrMu.Unlock()
|
||||
}
|
||||
|
||||
for once := true; once || len(b) > 0; once = false {
|
||||
select {
|
||||
case p.wrCh <- b:
|
||||
nw := <-p.rdCh
|
||||
b = b[nw:]
|
||||
n += nw
|
||||
case <-p.done:
|
||||
return n, p.writeCloseError()
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *pipe) writeCloseError() error {
|
||||
werr := p.werr.Load()
|
||||
if rerr := p.rerr.Load(); werr == nil && rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
return ErrClosedPipe
|
||||
}
|
||||
|
||||
func (p *pipe) CloseWrite(err error) error {
|
||||
if err == nil {
|
||||
err = EOF
|
||||
}
|
||||
p.werr.Store(err)
|
||||
p.once.Do(func() { close(p.done) })
|
||||
return nil
|
||||
}
|
||||
|
||||
// A PipeReader is the read half of a pipe.
|
||||
type PipeReader struct {
|
||||
p *pipe
|
||||
}
|
||||
|
||||
// Read implements the standard Read interface:
|
||||
// it reads data from the pipe, blocking until a writer
|
||||
// arrives or the write end is closed.
|
||||
// If the write end is closed with an error, that error is
|
||||
// returned as err; otherwise err is EOF.
|
||||
func (r *PipeReader) Read(data []byte) (n int, err error) {
|
||||
return r.p.Read(data)
|
||||
}
|
||||
|
||||
// Close closes the reader; subsequent writes to the
|
||||
// write half of the pipe will return the error ErrClosedPipe.
|
||||
func (r *PipeReader) Close() error {
|
||||
return r.CloseWithError(nil)
|
||||
}
|
||||
|
||||
// CloseWithError closes the reader; subsequent writes
|
||||
// to the write half of the pipe will return the error err.
|
||||
//
|
||||
// CloseWithError never overwrites the previous error if it exists
|
||||
// and always returns nil.
|
||||
func (r *PipeReader) CloseWithError(err error) error {
|
||||
return r.p.CloseRead(err)
|
||||
}
|
||||
|
||||
// A PipeWriter is the write half of a pipe.
|
||||
type PipeWriter struct {
|
||||
p *pipe
|
||||
}
|
||||
|
||||
// Write implements the standard Write interface:
|
||||
// it writes data to the pipe, blocking until one or more readers
|
||||
// have consumed all the data or the read end is closed.
|
||||
// If the read end is closed with an error, that err is
|
||||
// returned as err; otherwise err is ErrClosedPipe.
|
||||
func (w *PipeWriter) Write(data []byte) (n int, err error) {
|
||||
return w.p.Write(data)
|
||||
}
|
||||
|
||||
// Close closes the writer; subsequent reads from the
|
||||
// read half of the pipe will return no bytes and EOF.
|
||||
func (w *PipeWriter) Close() error {
|
||||
return w.CloseWithError(nil)
|
||||
}
|
||||
|
||||
// CloseWithError closes the writer; subsequent reads from the
|
||||
// read half of the pipe will return no bytes and the error err,
|
||||
// or EOF if err is nil.
|
||||
//
|
||||
// CloseWithError never overwrites the previous error if it exists
|
||||
// and always returns nil.
|
||||
func (w *PipeWriter) CloseWithError(err error) error {
|
||||
return w.p.CloseWrite(err)
|
||||
}
|
||||
|
||||
// Pipe creates a synchronous in-memory pipe.
|
||||
// It can be used to connect code expecting an io.Reader
|
||||
// with code expecting an io.Writer.
|
||||
//
|
||||
// Reads and Writes on the pipe are matched one to one
|
||||
// except when multiple Reads are needed to consume a single Write.
|
||||
// That is, each Write to the PipeWriter blocks until it has satisfied
|
||||
// one or more Reads from the PipeReader that fully consume
|
||||
// the written data.
|
||||
// The data is copied directly from the Write to the corresponding
|
||||
// Read (or Reads); there is no internal buffering.
|
||||
//
|
||||
// It is safe to call Read and Write in parallel with each other or with Close.
|
||||
// Parallel calls to Read and parallel calls to Write are also safe:
|
||||
// the individual calls will be gated sequentially.
|
||||
func Pipe() (*PipeReader, *PipeWriter) {
|
||||
p := &pipe{
|
||||
wrCh: make(chan []byte),
|
||||
rdCh: make(chan int),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
return &PipeReader{p}, &PipeWriter{p}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
// we copy the go src directly, then change every include to github.com/mandiant/GoReSym/<whatever>
|
||||
// this is required since we're using internal files. Our modifications are directly inside the copied source
|
||||
"github.com/mandiant/GoReSym/buildid"
|
||||
"github.com/mandiant/GoReSym/buildinfo"
|
||||
"github.com/mandiant/GoReSym/objfile"
|
||||
"github.com/mandiant/GoReSym/runtime/debug"
|
||||
)
|
||||
|
||||
func isStdPackage(pkg string) bool {
|
||||
// Empty name is common for reflect/type functions and some runtime symbols
|
||||
if len(strings.TrimSpace(pkg)) <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, v := range standardPackages {
|
||||
if v == pkg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// pclntab header info
|
||||
type PcLnTabMetadata struct {
|
||||
VA uint64
|
||||
Version string
|
||||
Endianess string
|
||||
CpuQuantum uint32
|
||||
CpuQuantumStr string
|
||||
PointerSize uint32
|
||||
}
|
||||
|
||||
type FuncMetadata struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
PackageName string
|
||||
FullName string
|
||||
}
|
||||
|
||||
type ExtractMetadata struct {
|
||||
Version string
|
||||
BuildId string
|
||||
Arch string
|
||||
OS string
|
||||
TabMeta PcLnTabMetadata
|
||||
ModuleMeta objfile.ModuleData
|
||||
Types []objfile.Type
|
||||
Interfaces []objfile.Type
|
||||
BuildInfo debug.BuildInfo
|
||||
Files []string
|
||||
UserFunctions []FuncMetadata
|
||||
StdFunctions []FuncMetadata
|
||||
}
|
||||
|
||||
func main_impl(fileName string, printStdPkgs bool, printFilePaths bool, printTypes bool, manualTypeAddress int, versionOverride string) (metadata ExtractMetadata, err error) {
|
||||
extractMetadata := ExtractMetadata{}
|
||||
|
||||
file, err := objfile.Open(fileName)
|
||||
if err != nil {
|
||||
return ExtractMetadata{}, fmt.Errorf("invalid file: %w", err)
|
||||
}
|
||||
|
||||
buildId, err := buildid.ReadFile(fileName)
|
||||
if err == nil {
|
||||
extractMetadata.BuildId = buildId
|
||||
} else {
|
||||
extractMetadata.BuildId = ""
|
||||
}
|
||||
|
||||
// try to get version the 'correct' way, also fill out buildSettings if parsing was ok
|
||||
bi, err := buildinfo.ReadFile(fileName)
|
||||
if err == nil {
|
||||
extractMetadata.Version = bi.GoVersion
|
||||
|
||||
for _, setting := range bi.Settings {
|
||||
if setting.Key == "GOOS" {
|
||||
extractMetadata.OS = setting.Value
|
||||
} else if setting.Key == "GOARCH" {
|
||||
extractMetadata.Arch = setting.Value
|
||||
}
|
||||
}
|
||||
|
||||
extractMetadata.BuildInfo = *bi
|
||||
}
|
||||
|
||||
// Optional bruteforce any one of these, but only if they weren't previous found in the buildinfo
|
||||
if extractMetadata.OS == "" || extractMetadata.Arch == "" || extractMetadata.Version == "" {
|
||||
// GOARCH
|
||||
if extractMetadata.Arch == "" {
|
||||
extractMetadata.Arch = file.GOARCH()
|
||||
}
|
||||
|
||||
fileData, fileDataErr := ioutil.ReadFile(fileName)
|
||||
if fileDataErr == nil {
|
||||
|
||||
// GOVERSION
|
||||
if extractMetadata.Version == "" {
|
||||
// go1.<varies><garbage data>
|
||||
idx := bytes.Index(fileData, []byte{0x67, 0x6F, 0x31, 0x2E})
|
||||
if idx != -1 && len(fileData[idx:]) > 10 {
|
||||
extractMetadata.Version = "go1."
|
||||
ver := fileData[idx+4 : idx+10]
|
||||
for i, c := range ver {
|
||||
// the string is _not_ null terminated, nor length delimited. So, filter till first non-numeric ascii
|
||||
nextIsNumeric := (i+1) < len(ver) && ver[i+1] >= 0x30 && ver[i+1] <= 0x39
|
||||
|
||||
// careful not to end with a . at the end
|
||||
if (c >= 0x30 && c <= 0x39 && c != ' ') || (c == '.' && nextIsNumeric) {
|
||||
extractMetadata.Version += string([]byte{c})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GOOS
|
||||
if extractMetadata.OS == "" {
|
||||
// try to find the OS by locating the source file name from https://github.com/golang/go/tree/master/src/runtime/os_<os name>.go or the asm file name rt0_<os name>_<arch>.s
|
||||
// if this is bad, we can end up signaturing the asm file manually (todo)
|
||||
// /src/runtime/os_
|
||||
needleSrcFile := []byte{0x2F, 0x73, 0x72, 0x63, 0x2F, 0x72, 0x75, 0x6E, 0x74, 0x69, 0x6D, 0x65, 0x2F, 0x6F, 0x73, 0x5F}
|
||||
needleSrcFileLen := len(needleSrcFile)
|
||||
idx := bytes.Index(fileData, needleSrcFile)
|
||||
if idx != -1 && len(fileData[idx:]) > needleSrcFileLen+20 {
|
||||
os_str := fileData[idx+needleSrcFileLen : idx+needleSrcFileLen+20]
|
||||
for _, c := range os_str {
|
||||
// end our search at the first '.', which should be the .go soure file extension, or a space as fallback
|
||||
if (c >= 0x30 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) && c != '.' && c != ' ' {
|
||||
extractMetadata.OS += string([]byte{c})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// /src/runtime/rt0_
|
||||
needleAsmFile := []byte{0x2F, 0x73, 0x72, 0x63, 0x2F, 0x72, 0x75, 0x6E, 0x74, 0x69, 0x6D, 0x65, 0x2F, 0x72, 0x74, 0x30, 0x5F}
|
||||
needleAsmFileLen := len(needleAsmFile)
|
||||
idx := bytes.Index(fileData, needleAsmFile)
|
||||
if idx != -1 && len(fileData[idx:]) > needleAsmFileLen+20 {
|
||||
os_str := fileData[idx+needleAsmFileLen : idx+needleAsmFileLen+20]
|
||||
for _, c := range os_str {
|
||||
// end our search at the first '_', which should be the _arch, space as fallback
|
||||
if (c >= 0x30 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) && c != '_' && c != '.' && c != ' ' {
|
||||
extractMetadata.OS += string([]byte{c})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(versionOverride) > 0 {
|
||||
extractMetadata.Version = versionOverride
|
||||
}
|
||||
|
||||
// numeric only, go1.17 -> 1.17
|
||||
goVersionIdx := strings.Index(extractMetadata.Version, "go")
|
||||
if goVersionIdx != -1 {
|
||||
// "devel go1.18-2d1d548 Tue Dec 21 03:55:43 2021 +0000"
|
||||
extractMetadata.Version = strings.Split(extractMetadata.Version[goVersionIdx+2:]+" ", " ")[0]
|
||||
|
||||
// go1.18-2d1d548
|
||||
extractMetadata.Version = strings.Split(extractMetadata.Version+"-", "-")[0]
|
||||
}
|
||||
|
||||
tab, tabva, err := file.PCLineTable()
|
||||
if err != nil {
|
||||
return ExtractMetadata{}, fmt.Errorf("failed to read pclntab: %w", err)
|
||||
}
|
||||
|
||||
if tab.Go12line == nil {
|
||||
log.Fatalf("pclntab read, but is nil")
|
||||
return ExtractMetadata{}, fmt.Errorf("read pclntab, but parsing failed. The file may not be fully unpacked or corrupted: %w", err)
|
||||
}
|
||||
|
||||
extractMetadata.TabMeta.CpuQuantum = tab.Go12line.Quantum
|
||||
|
||||
// quantum is the minimal unit for a program counter (1 on x86, 4 on most other systems).
|
||||
// 386: 1, amd64: 1, arm: 4, arm64: 4, mips: 4, mips/64/64le/64be: 4, ppc64/64le: 4, riscv64: 4, s390x: 2, wasm: 1
|
||||
extractMetadata.TabMeta.CpuQuantumStr = "x86/x64/wasm"
|
||||
if extractMetadata.TabMeta.CpuQuantum == 2 {
|
||||
extractMetadata.TabMeta.CpuQuantumStr = "s390x"
|
||||
} else if extractMetadata.TabMeta.CpuQuantum == 4 {
|
||||
extractMetadata.TabMeta.CpuQuantumStr = "arm/mips/ppc/riscv"
|
||||
}
|
||||
|
||||
extractMetadata.TabMeta.VA = tabva
|
||||
extractMetadata.TabMeta.Version = tab.Go12line.Version.String()
|
||||
extractMetadata.TabMeta.Endianess = tab.Go12line.Binary.String()
|
||||
extractMetadata.TabMeta.PointerSize = tab.Go12line.Ptrsize
|
||||
|
||||
// this can be a little tricky to locate and parse properly across all go versions
|
||||
_, moduleData, err := file.ModuleDataTable(tabva, extractMetadata.Version, extractMetadata.TabMeta.Version, extractMetadata.TabMeta.PointerSize == 8, extractMetadata.TabMeta.Endianess == "LittleEndian")
|
||||
if err == nil {
|
||||
extractMetadata.ModuleMeta = *moduleData
|
||||
if printTypes && manualTypeAddress == 0 {
|
||||
types, err := file.ParseTypeLinks(extractMetadata.Version, moduleData, extractMetadata.TabMeta.PointerSize == 8, extractMetadata.TabMeta.Endianess == "LittleEndian")
|
||||
if err == nil {
|
||||
extractMetadata.Types = types
|
||||
}
|
||||
|
||||
interfaces, err := file.ParseITabLinks(extractMetadata.Version, moduleData, extractMetadata.TabMeta.PointerSize == 8, extractMetadata.TabMeta.Endianess == "LittleEndian")
|
||||
if err == nil {
|
||||
extractMetadata.Interfaces = interfaces
|
||||
}
|
||||
} else if manualTypeAddress != 0 {
|
||||
types, err := file.ParseType(extractMetadata.Version, moduleData, uint64(manualTypeAddress), extractMetadata.TabMeta.PointerSize == 8, extractMetadata.TabMeta.Endianess == "LittleEndian")
|
||||
if err == nil {
|
||||
extractMetadata.Types = types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if printFilePaths {
|
||||
for k := range tab.Files {
|
||||
extractMetadata.Files = append(extractMetadata.Files, k)
|
||||
}
|
||||
}
|
||||
|
||||
for _, elem := range tab.Funcs {
|
||||
if isStdPackage(elem.PackageName()) {
|
||||
if printStdPkgs {
|
||||
extractMetadata.StdFunctions = append(extractMetadata.StdFunctions, FuncMetadata{
|
||||
Start: elem.Entry,
|
||||
End: elem.End,
|
||||
PackageName: elem.PackageName(),
|
||||
FullName: elem.Name,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
extractMetadata.UserFunctions = append(extractMetadata.UserFunctions, FuncMetadata{
|
||||
Start: elem.Entry,
|
||||
End: elem.End,
|
||||
PackageName: elem.PackageName(),
|
||||
FullName: elem.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return extractMetadata, nil
|
||||
}
|
||||
|
||||
func printForHuman(metadata ExtractMetadata) {
|
||||
fmt.Println("----GoReSym----")
|
||||
fmt.Println("Some information is ommitted, for a full listing do not use human view")
|
||||
fmt.Printf("%-20s %s\n", "Version:", metadata.Version)
|
||||
fmt.Printf("%-20s %s\n", "Arch:", metadata.Arch)
|
||||
fmt.Printf("%-20s %s\n", "OS:", metadata.OS)
|
||||
fmt.Println("\n-BUILD INFO-")
|
||||
fmt.Printf("%-20s %s\n", "GoVersion", metadata.BuildInfo.GoVersion)
|
||||
fmt.Printf("%-20s %s\n", "Path", metadata.BuildInfo.Path)
|
||||
fmt.Printf("%-20s %s\n", "Main.Path", metadata.BuildInfo.Main.Path)
|
||||
fmt.Printf("%-20s %s\n", "Main.Version", metadata.BuildInfo.Main.Version)
|
||||
fmt.Printf("%-20s %s\n", "Main.Sum", metadata.BuildInfo.Main.Sum)
|
||||
fmt.Printf("%-20s %s\n", "Main.Path", metadata.BuildInfo.Main.Path)
|
||||
for i, dep := range metadata.BuildInfo.Deps {
|
||||
depPrefix := fmt.Sprintf("Dep%d.", i)
|
||||
fmt.Printf("%-20s %s\n", depPrefix+"Path", dep.Path)
|
||||
fmt.Printf("%-20s %s\n", depPrefix+"Version", dep.Version)
|
||||
fmt.Printf("%-20s %s\n", depPrefix+"Sum", dep.Sum)
|
||||
}
|
||||
|
||||
fmt.Println("\n -BUILD SETTINGS-")
|
||||
if len(metadata.BuildInfo.Settings) > 0 {
|
||||
for _, setting := range metadata.BuildInfo.Settings {
|
||||
fmt.Printf(" %-20s %s\n", "Setting."+setting.Key, setting.Value)
|
||||
}
|
||||
} else {
|
||||
fmt.Println(" <NO SETTINGS PRESENT>")
|
||||
}
|
||||
|
||||
fmt.Println("\n-TYPE STRUCTURES-")
|
||||
printedStruct := false
|
||||
for _, typ := range metadata.Types {
|
||||
if len(typ.Reconstructed) > 0 {
|
||||
fmt.Printf("VA: 0x%x\n", typ.VA)
|
||||
fmt.Printf("%s\n\n", typ.Reconstructed)
|
||||
printedStruct = true
|
||||
}
|
||||
}
|
||||
if !printedStruct {
|
||||
fmt.Println("<NO TYPE STRUCTURES EXTRACTED>")
|
||||
}
|
||||
|
||||
fmt.Println("\n-INTERFACES-")
|
||||
printedInterface := false
|
||||
for _, typ := range metadata.Interfaces {
|
||||
if len(typ.Reconstructed) > 0 {
|
||||
fmt.Printf("%-20s 0x%x\n", "VA:", typ.VA)
|
||||
fmt.Printf("%s\n\n", typ.Reconstructed)
|
||||
printedInterface = true
|
||||
}
|
||||
}
|
||||
if !printedInterface {
|
||||
fmt.Println("<NO INTERFACES EXTRACTED>")
|
||||
}
|
||||
|
||||
fmt.Println("\n-Files-")
|
||||
if len(metadata.Files) > 0 {
|
||||
for _, file := range metadata.Files {
|
||||
fmt.Println(file)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("<NO FILES EXTRACTED>")
|
||||
}
|
||||
|
||||
fmt.Println("\n-User Functions-")
|
||||
if len(metadata.UserFunctions) > 0 {
|
||||
for i, fn := range metadata.UserFunctions {
|
||||
fnPrefix := fmt.Sprintf("UserFunc%d.", i)
|
||||
fmt.Printf("%-20s 0x%x\n", fnPrefix+"StartVA:", fn.Start)
|
||||
fmt.Printf("%-20s 0x%x\n", fnPrefix+"EndVA:", fn.End)
|
||||
fmt.Printf("%-20s %s\n", fnPrefix+"Package:", fn.PackageName)
|
||||
fmt.Printf("%-20s %s\n", fnPrefix+"Name:", strings.TrimLeft(strings.TrimLeft(fn.FullName, fn.PackageName), "."))
|
||||
}
|
||||
} else {
|
||||
fmt.Println("<NO USER FUNCTIONS EXTRACTED>")
|
||||
}
|
||||
|
||||
fmt.Println("\n-Standard Functions-")
|
||||
if len(metadata.StdFunctions) > 0 {
|
||||
for i, fn := range metadata.StdFunctions {
|
||||
fnPrefix := fmt.Sprintf("StdFunc%d.", i)
|
||||
fmt.Printf("%-20s 0x%x\n", fnPrefix+"StartVA:", fn.Start)
|
||||
fmt.Printf("%-20s 0x%x\n", fnPrefix+"EndVA:", fn.End)
|
||||
fmt.Printf("%-20s %s\n", fnPrefix+"Name:", fn.FullName)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("<NO STANDARD FUNCTIONS EXTRACTED>")
|
||||
}
|
||||
}
|
||||
|
||||
func DataToJson(data interface{}) string {
|
||||
jsonBytes, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return "{\"error\": \"failed to format output\"}"
|
||||
}
|
||||
return string(jsonBytes)
|
||||
}
|
||||
|
||||
func TextToJson(key string, text string) string {
|
||||
return fmt.Sprintf("{\"%s\": \"%s\"}", key, text)
|
||||
}
|
||||
|
||||
func main() {
|
||||
stdout := bufio.NewWriter(os.Stdout)
|
||||
defer stdout.Flush()
|
||||
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("GoReSym: ")
|
||||
|
||||
printStdPkgs := flag.Bool("d", false, "Print Default Packages")
|
||||
printFilePaths := flag.Bool("p", false, "Print File Paths")
|
||||
printTypes := flag.Bool("t", false, "Print types automatically, enumerate typelinks and itablinks")
|
||||
typeAddress := flag.Int("m", 0, "Manually parse the RTYPE at the provided virtual address, disables automated enumeration of moduledata typelinks itablinks")
|
||||
versionOverride := flag.String("v", "", "Override the automated version detection, ex: 1.17. If this is wrong, parsing may fail or produce nonsense")
|
||||
humanView := flag.Bool("human", false, "Human view, print information flat rather than json, some information is ommited for clarity")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Println(TextToJson("error", "filepath must be provided as first argument"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
metadata, err := main_impl(flag.Arg(0), *printStdPkgs, *printFilePaths, *printTypes, *typeAddress, *versionOverride)
|
||||
if err != nil {
|
||||
fmt.Println(TextToJson("error", fmt.Sprintf("Failed to parse file: %s", err)))
|
||||
os.Exit(1)
|
||||
} else {
|
||||
if *humanView {
|
||||
printForHuman(metadata)
|
||||
} else {
|
||||
fmt.Println(DataToJson((metadata)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<<<<<<< HEAD
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
=======
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
>>>>>>> 4feec1a (Add licensing in headers)
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var versions = []string{"117", "116", "115", "114", "113", "112", "111", "110", "19", "18", "17", "16", "15"}
|
||||
var fileNames = []string{"testproject_lin", "testproject_lin_32", "testproject_lin_stripped", "testproject_lin_stripped_32", "testproject_mac", "testproject_mac_stripped", "testproject_win_32.exe", "testproject_win_stripped_32.exe", "testproject_win_stripped.exe", "testproject_win.exe"}
|
||||
|
||||
func TestAllVersions(t *testing.T) {
|
||||
workingDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get working directory")
|
||||
}
|
||||
|
||||
fmt.Println(workingDirectory)
|
||||
|
||||
for _, v := range versions {
|
||||
for _, file := range fileNames {
|
||||
versionPath := fmt.Sprintf("%s/%s", v, file)
|
||||
filePath := fmt.Sprintf("%s/test/build/%s", workingDirectory, versionPath)
|
||||
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||
fmt.Printf("Test file %s doesn't exist\n", filePath)
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run(versionPath, func(t *testing.T) {
|
||||
data, err := main_impl(filePath, true, true, true, 0, "")
|
||||
if err != nil {
|
||||
t.Errorf("Go %s failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if data.TabMeta.VA == 0 {
|
||||
t.Errorf("Go %s pclntab location failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if data.ModuleMeta.VA == 0 {
|
||||
t.Errorf("Go %s moduledata location failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if len(data.Types) == 0 {
|
||||
t.Errorf("Go %s type parsing failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
// unsupported
|
||||
if v != "15" && v != "16" {
|
||||
if len(data.Interfaces) == 0 {
|
||||
t.Errorf("Go %s interface parsing failed on %s: %s", v, file, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.StdFunctions) == 0 {
|
||||
t.Errorf("Go %s std functions failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if len(data.UserFunctions) == 0 {
|
||||
t.Errorf("Go %s user functions failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if len(data.Files) == 0 {
|
||||
t.Errorf("Go %s files failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if data.Version == "" {
|
||||
t.Errorf("Go %s version failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if data.OS == "" {
|
||||
t.Errorf("Go %s OS failed on %s: %s", v, file, err)
|
||||
}
|
||||
|
||||
if data.Arch == "" {
|
||||
t.Errorf("Go %s Arch failed on %s: %s", v, file, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Derived from Inferno utils/6l/l.h and related files.
|
||||
// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6l/l.h
|
||||
//
|
||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package objabi
|
||||
|
||||
// Auto.name
|
||||
const (
|
||||
A_AUTO = 1 + iota
|
||||
A_PARAM
|
||||
A_DELETED_AUTO
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2013 The Go 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 objabi
|
||||
|
||||
// This file defines the IDs for PCDATA and FUNCDATA instructions
|
||||
// in Go binaries.
|
||||
//
|
||||
// These must agree with ../../../runtime/funcdata.h and
|
||||
// ../../../runtime/symtab.go.
|
||||
|
||||
const (
|
||||
PCDATA_UnsafePoint = 0
|
||||
PCDATA_StackMapIndex = 1
|
||||
PCDATA_InlTreeIndex = 2
|
||||
|
||||
FUNCDATA_ArgsPointerMaps = 0
|
||||
FUNCDATA_LocalsPointerMaps = 1
|
||||
FUNCDATA_StackObjects = 2
|
||||
FUNCDATA_InlTree = 3
|
||||
FUNCDATA_OpenCodedDeferInfo = 4
|
||||
|
||||
// ArgsSizeUnknown is set in Func.argsize to mark all functions
|
||||
// whose argument size is unknown (C vararg functions, and
|
||||
// assembly code without an explicit specification).
|
||||
// This value is generated by the compiler, assembler, or linker.
|
||||
ArgsSizeUnknown = -0x80000000
|
||||
)
|
||||
|
||||
// Special PCDATA values.
|
||||
const (
|
||||
// PCDATA_UnsafePoint values.
|
||||
PCDATA_UnsafePointSafe = -1 // Safe for async preemption
|
||||
PCDATA_UnsafePointUnsafe = -2 // Unsafe for async preemption
|
||||
|
||||
// PCDATA_Restart1(2) apply on a sequence of instructions, within
|
||||
// which if an async preemption happens, we should back off the PC
|
||||
// to the start of the sequence when resuming.
|
||||
// We need two so we can distinguish the start/end of the sequence
|
||||
// in case that two sequences are next to each other.
|
||||
PCDATA_Restart1 = -3
|
||||
PCDATA_Restart2 = -4
|
||||
|
||||
// Like PCDATA_Restart1, but back to function entry if async preempted.
|
||||
PCDATA_RestartAtEntry = -5
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2018 The Go 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 objabi
|
||||
|
||||
// A FuncID identifies particular functions that need to be treated
|
||||
// specially by the runtime.
|
||||
// Note that in some situations involving plugins, there may be multiple
|
||||
// copies of a particular special runtime function.
|
||||
// Note: this list must match the list in runtime/symtab.go.
|
||||
type FuncID uint8
|
||||
|
||||
const (
|
||||
FuncID_normal FuncID = iota // not a special function
|
||||
FuncID_runtime_main
|
||||
FuncID_goexit
|
||||
FuncID_jmpdefer
|
||||
FuncID_mcall
|
||||
FuncID_morestack
|
||||
FuncID_mstart
|
||||
FuncID_rt0_go
|
||||
FuncID_asmcgocall
|
||||
FuncID_sigpanic
|
||||
FuncID_runfinq
|
||||
FuncID_gcBgMarkWorker
|
||||
FuncID_systemstack_switch
|
||||
FuncID_systemstack
|
||||
FuncID_cgocallback
|
||||
FuncID_gogo
|
||||
FuncID_externalthreadhandler
|
||||
FuncID_debugCallV1
|
||||
FuncID_gopanic
|
||||
FuncID_panicwrap
|
||||
FuncID_handleAsyncEvent
|
||||
FuncID_asyncPreempt
|
||||
FuncID_wrapper // any autogenerated code (hash/eq algorithms, method wrappers, etc.)
|
||||
)
|
||||
|
||||
// Get the function ID for the named function in the named file.
|
||||
// The function should be package-qualified.
|
||||
func GetFuncID(name string, isWrapper bool) FuncID {
|
||||
if isWrapper {
|
||||
return FuncID_wrapper
|
||||
}
|
||||
switch name {
|
||||
case "runtime.main":
|
||||
return FuncID_runtime_main
|
||||
case "runtime.goexit":
|
||||
return FuncID_goexit
|
||||
case "runtime.jmpdefer":
|
||||
return FuncID_jmpdefer
|
||||
case "runtime.mcall":
|
||||
return FuncID_mcall
|
||||
case "runtime.morestack":
|
||||
return FuncID_morestack
|
||||
case "runtime.mstart":
|
||||
return FuncID_mstart
|
||||
case "runtime.rt0_go":
|
||||
return FuncID_rt0_go
|
||||
case "runtime.asmcgocall":
|
||||
return FuncID_asmcgocall
|
||||
case "runtime.sigpanic":
|
||||
return FuncID_sigpanic
|
||||
case "runtime.runfinq":
|
||||
return FuncID_runfinq
|
||||
case "runtime.gcBgMarkWorker":
|
||||
return FuncID_gcBgMarkWorker
|
||||
case "runtime.systemstack_switch":
|
||||
return FuncID_systemstack_switch
|
||||
case "runtime.systemstack":
|
||||
return FuncID_systemstack
|
||||
case "runtime.cgocallback":
|
||||
return FuncID_cgocallback
|
||||
case "runtime.gogo":
|
||||
return FuncID_gogo
|
||||
case "runtime.externalthreadhandler":
|
||||
return FuncID_externalthreadhandler
|
||||
case "runtime.debugCallV1":
|
||||
return FuncID_debugCallV1
|
||||
case "runtime.gopanic":
|
||||
return FuncID_gopanic
|
||||
case "runtime.panicwrap":
|
||||
return FuncID_panicwrap
|
||||
case "runtime.handleAsyncEvent":
|
||||
return FuncID_handleAsyncEvent
|
||||
case "runtime.asyncPreempt":
|
||||
return FuncID_asyncPreempt
|
||||
case "runtime.deferreturn":
|
||||
// Don't show in the call stack (used when invoking defer functions)
|
||||
return FuncID_wrapper
|
||||
case "runtime.runOpenDeferFrame":
|
||||
// Don't show in the call stack (used when invoking defer functions)
|
||||
return FuncID_wrapper
|
||||
case "runtime.reflectcallSave":
|
||||
// Don't show in the call stack (used when invoking defer functions)
|
||||
return FuncID_wrapper
|
||||
}
|
||||
return FuncID_normal
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Derived from Inferno utils/6l/l.h and related files.
|
||||
// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6l/l.h
|
||||
//
|
||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package objabi
|
||||
|
||||
import "fmt"
|
||||
|
||||
// HeadType is the executable header type.
|
||||
type HeadType uint8
|
||||
|
||||
const (
|
||||
Hunknown HeadType = iota
|
||||
Hdarwin
|
||||
Hdragonfly
|
||||
Hfreebsd
|
||||
Hjs
|
||||
Hlinux
|
||||
Hnetbsd
|
||||
Hopenbsd
|
||||
Hplan9
|
||||
Hsolaris
|
||||
Hwindows
|
||||
Haix
|
||||
)
|
||||
|
||||
func (h *HeadType) Set(s string) error {
|
||||
switch s {
|
||||
case "aix":
|
||||
*h = Haix
|
||||
case "darwin", "ios":
|
||||
*h = Hdarwin
|
||||
case "dragonfly":
|
||||
*h = Hdragonfly
|
||||
case "freebsd":
|
||||
*h = Hfreebsd
|
||||
case "js":
|
||||
*h = Hjs
|
||||
case "linux", "android":
|
||||
*h = Hlinux
|
||||
case "netbsd":
|
||||
*h = Hnetbsd
|
||||
case "openbsd":
|
||||
*h = Hopenbsd
|
||||
case "plan9":
|
||||
*h = Hplan9
|
||||
case "illumos", "solaris":
|
||||
*h = Hsolaris
|
||||
case "windows":
|
||||
*h = Hwindows
|
||||
default:
|
||||
return fmt.Errorf("invalid headtype: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HeadType) String() string {
|
||||
switch *h {
|
||||
case Haix:
|
||||
return "aix"
|
||||
case Hdarwin:
|
||||
return "darwin"
|
||||
case Hdragonfly:
|
||||
return "dragonfly"
|
||||
case Hfreebsd:
|
||||
return "freebsd"
|
||||
case Hjs:
|
||||
return "js"
|
||||
case Hlinux:
|
||||
return "linux"
|
||||
case Hnetbsd:
|
||||
return "netbsd"
|
||||
case Hopenbsd:
|
||||
return "openbsd"
|
||||
case Hplan9:
|
||||
return "plan9"
|
||||
case Hsolaris:
|
||||
return "solaris"
|
||||
case Hwindows:
|
||||
return "windows"
|
||||
}
|
||||
return fmt.Sprintf("HeadType(%d)", *h)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2017 The Go 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 objabi
|
||||
|
||||
import "strings"
|
||||
|
||||
// PathToPrefix converts raw string to the prefix that will be used in the
|
||||
// symbol table. All control characters, space, '%' and '"', as well as
|
||||
// non-7-bit clean bytes turn into %xx. The period needs escaping only in the
|
||||
// last segment of the path, and it makes for happier users if we escape that as
|
||||
// little as possible.
|
||||
func PathToPrefix(s string) string {
|
||||
slash := strings.LastIndex(s, "/")
|
||||
// check for chars that need escaping
|
||||
n := 0
|
||||
for r := 0; r < len(s); r++ {
|
||||
if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
// quick exit
|
||||
if n == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
// escape
|
||||
const hex = "0123456789abcdef"
|
||||
p := make([]byte, 0, len(s)+2*n)
|
||||
for r := 0; r < len(s); r++ {
|
||||
if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
|
||||
p = append(p, '%', hex[c>>4], hex[c&0xF])
|
||||
} else {
|
||||
p = append(p, c)
|
||||
}
|
||||
}
|
||||
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// IsRuntimePackagePath examines 'pkgpath' and returns TRUE if it
|
||||
// belongs to the collection of "runtime-related" packages, including
|
||||
// "runtime" itself, "reflect", "syscall", and the
|
||||
// "runtime/internal/*" packages. The compiler and/or assembler in
|
||||
// some cases need to be aware of when they are building such a
|
||||
// package, for example to enable features such as ABI selectors in
|
||||
// assembly sources.
|
||||
func IsRuntimePackagePath(pkgpath string) bool {
|
||||
rval := false
|
||||
switch pkgpath {
|
||||
case "runtime":
|
||||
rval = true
|
||||
case "reflect":
|
||||
rval = true
|
||||
case "syscall":
|
||||
rval = true
|
||||
default:
|
||||
rval = strings.HasPrefix(pkgpath, "runtime/internal")
|
||||
}
|
||||
return rval
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 The Go 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 objabi
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPathToPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path string
|
||||
Expected string
|
||||
}{{"foo/bar/v1", "foo/bar/v1"},
|
||||
{"foo/bar/v.1", "foo/bar/v%2e1"},
|
||||
{"f.o.o/b.a.r/v1", "f.o.o/b.a.r/v1"},
|
||||
{"f.o.o/b.a.r/v.1", "f.o.o/b.a.r/v%2e1"},
|
||||
{"f.o.o/b.a.r/v..1", "f.o.o/b.a.r/v%2e%2e1"},
|
||||
{"f.o.o/b.a.r/v..1.", "f.o.o/b.a.r/v%2e%2e1%2e"},
|
||||
{"f.o.o/b.a.r/v%1", "f.o.o/b.a.r/v%251"},
|
||||
{"runtime", "runtime"},
|
||||
{"sync/atomic", "sync/atomic"},
|
||||
{"golang.org/x/tools/godoc", "golang.org/x/tools/godoc"},
|
||||
{"foo.bar/baz.quux", "foo.bar/baz%2equux"},
|
||||
{"", ""},
|
||||
{"%foo%bar", "%25foo%25bar"},
|
||||
{"\x01\x00\x7F☺", "%01%00%7f%e2%98%ba"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
if got := PathToPrefix(tc.Path); got != tc.Expected {
|
||||
t.Errorf("expected PathToPrefix(%s) = %s, got %s", tc.Path, tc.Expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// Derived from Inferno utils/6l/l.h and related files.
|
||||
// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6l/l.h
|
||||
//
|
||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package objabi
|
||||
|
||||
type RelocType int16
|
||||
|
||||
//go:generate stringer -type=RelocType
|
||||
const (
|
||||
R_ADDR RelocType = 1 + iota
|
||||
// R_ADDRPOWER relocates a pair of "D-form" instructions (instructions with 16-bit
|
||||
// immediates in the low half of the instruction word), usually addis followed by
|
||||
// another add or a load, inserting the "high adjusted" 16 bits of the address of
|
||||
// the referenced symbol into the immediate field of the first instruction and the
|
||||
// low 16 bits into that of the second instruction.
|
||||
R_ADDRPOWER
|
||||
// R_ADDRARM64 relocates an adrp, add pair to compute the address of the
|
||||
// referenced symbol.
|
||||
R_ADDRARM64
|
||||
// R_ADDRMIPS (only used on mips/mips64) resolves to the low 16 bits of an external
|
||||
// address, by encoding it into the instruction.
|
||||
R_ADDRMIPS
|
||||
// R_ADDROFF resolves to a 32-bit offset from the beginning of the section
|
||||
// holding the data being relocated to the referenced symbol.
|
||||
R_ADDROFF
|
||||
// R_WEAKADDROFF resolves just like R_ADDROFF but is a weak relocation.
|
||||
// A weak relocation does not make the symbol it refers to reachable,
|
||||
// and is only honored by the linker if the symbol is in some other way
|
||||
// reachable.
|
||||
R_WEAKADDROFF
|
||||
R_SIZE
|
||||
R_CALL
|
||||
R_CALLARM
|
||||
R_CALLARM64
|
||||
R_CALLIND
|
||||
R_CALLPOWER
|
||||
// R_CALLMIPS (only used on mips64) resolves to non-PC-relative target address
|
||||
// of a CALL (JAL) instruction, by encoding the address into the instruction.
|
||||
R_CALLMIPS
|
||||
// R_CALLRISCV marks RISC-V CALLs for stack checking.
|
||||
R_CALLRISCV
|
||||
R_CONST
|
||||
R_PCREL
|
||||
// R_TLS_LE, used on 386, amd64, and ARM, resolves to the offset of the
|
||||
// thread-local symbol from the thread local base and is used to implement the
|
||||
// "local exec" model for tls access (r.Sym is not set on intel platforms but is
|
||||
// set to a TLS symbol -- runtime.tlsg -- in the linker when externally linking).
|
||||
R_TLS_LE
|
||||
// R_TLS_IE, used 386, amd64, and ARM resolves to the PC-relative offset to a GOT
|
||||
// slot containing the offset from the thread-local symbol from the thread local
|
||||
// base and is used to implemented the "initial exec" model for tls access (r.Sym
|
||||
// is not set on intel platforms but is set to a TLS symbol -- runtime.tlsg -- in
|
||||
// the linker when externally linking).
|
||||
R_TLS_IE
|
||||
R_GOTOFF
|
||||
R_PLT0
|
||||
R_PLT1
|
||||
R_PLT2
|
||||
R_USEFIELD
|
||||
// R_USETYPE resolves to an *rtype, but no relocation is created. The
|
||||
// linker uses this as a signal that the pointed-to type information
|
||||
// should be linked into the final binary, even if there are no other
|
||||
// direct references. (This is used for types reachable by reflection.)
|
||||
R_USETYPE
|
||||
// R_USEIFACE marks a type is converted to an interface in the function this
|
||||
// relocation is applied to. The target is a type descriptor.
|
||||
// This is a marker relocation (0-sized), for the linker's reachabililty
|
||||
// analysis.
|
||||
R_USEIFACE
|
||||
// R_USEIFACEMETHOD marks an interface method that is used in the function
|
||||
// this relocation is applied to. The target is an interface type descriptor.
|
||||
// The addend is the offset of the method in the type descriptor.
|
||||
// This is a marker relocation (0-sized), for the linker's reachabililty
|
||||
// analysis.
|
||||
R_USEIFACEMETHOD
|
||||
// R_METHODOFF resolves to a 32-bit offset from the beginning of the section
|
||||
// holding the data being relocated to the referenced symbol.
|
||||
// It is a variant of R_ADDROFF used when linking from the uncommonType of a
|
||||
// *rtype, and may be set to zero by the linker if it determines the method
|
||||
// text is unreachable by the linked program.
|
||||
R_METHODOFF
|
||||
R_POWER_TOC
|
||||
R_GOTPCREL
|
||||
// R_JMPMIPS (only used on mips64) resolves to non-PC-relative target address
|
||||
// of a JMP instruction, by encoding the address into the instruction.
|
||||
// The stack nosplit check ignores this since it is not a function call.
|
||||
R_JMPMIPS
|
||||
|
||||
// R_DWARFSECREF resolves to the offset of the symbol from its section.
|
||||
// Target of relocation must be size 4 (in current implementation).
|
||||
R_DWARFSECREF
|
||||
|
||||
// R_DWARFFILEREF resolves to an index into the DWARF .debug_line
|
||||
// file table for the specified file symbol. Must be applied to an
|
||||
// attribute of form DW_FORM_data4.
|
||||
R_DWARFFILEREF
|
||||
|
||||
// Platform dependent relocations. Architectures with fixed width instructions
|
||||
// have the inherent issue that a 32-bit (or 64-bit!) displacement cannot be
|
||||
// stuffed into a 32-bit instruction, so an address needs to be spread across
|
||||
// several instructions, and in turn this requires a sequence of relocations, each
|
||||
// updating a part of an instruction. This leads to relocation codes that are
|
||||
// inherently processor specific.
|
||||
|
||||
// Arm64.
|
||||
|
||||
// Set a MOV[NZ] immediate field to bits [15:0] of the offset from the thread
|
||||
// local base to the thread local variable defined by the referenced (thread
|
||||
// local) symbol. Error if the offset does not fit into 16 bits.
|
||||
R_ARM64_TLS_LE
|
||||
|
||||
// Relocates an ADRP; LD64 instruction sequence to load the offset between
|
||||
// the thread local base and the thread local variable defined by the
|
||||
// referenced (thread local) symbol from the GOT.
|
||||
R_ARM64_TLS_IE
|
||||
|
||||
// R_ARM64_GOTPCREL relocates an adrp, ld64 pair to compute the address of the GOT
|
||||
// slot of the referenced symbol.
|
||||
R_ARM64_GOTPCREL
|
||||
|
||||
// R_ARM64_GOT resolves a GOT-relative instruction sequence, usually an adrp
|
||||
// followed by another ld instruction.
|
||||
R_ARM64_GOT
|
||||
|
||||
// R_ARM64_PCREL resolves a PC-relative addresses instruction sequence, usually an
|
||||
// adrp followed by another add instruction.
|
||||
R_ARM64_PCREL
|
||||
|
||||
// R_ARM64_LDST8 sets a LD/ST immediate value to bits [11:0] of a local address.
|
||||
R_ARM64_LDST8
|
||||
|
||||
// R_ARM64_LDST16 sets a LD/ST immediate value to bits [11:1] of a local address.
|
||||
R_ARM64_LDST16
|
||||
|
||||
// R_ARM64_LDST32 sets a LD/ST immediate value to bits [11:2] of a local address.
|
||||
R_ARM64_LDST32
|
||||
|
||||
// R_ARM64_LDST64 sets a LD/ST immediate value to bits [11:3] of a local address.
|
||||
R_ARM64_LDST64
|
||||
|
||||
// R_ARM64_LDST128 sets a LD/ST immediate value to bits [11:4] of a local address.
|
||||
R_ARM64_LDST128
|
||||
|
||||
// PPC64.
|
||||
|
||||
// R_POWER_TLS_LE is used to implement the "local exec" model for tls
|
||||
// access. It resolves to the offset of the thread-local symbol from the
|
||||
// thread pointer (R13) and inserts this value into the low 16 bits of an
|
||||
// instruction word.
|
||||
R_POWER_TLS_LE
|
||||
|
||||
// R_POWER_TLS_IE is used to implement the "initial exec" model for tls access. It
|
||||
// relocates a D-form, DS-form instruction sequence like R_ADDRPOWER_DS. It
|
||||
// inserts to the offset of GOT slot for the thread-local symbol from the TOC (the
|
||||
// GOT slot is filled by the dynamic linker with the offset of the thread-local
|
||||
// symbol from the thread pointer (R13)).
|
||||
R_POWER_TLS_IE
|
||||
|
||||
// R_POWER_TLS marks an X-form instruction such as "MOVD 0(R13)(R31*1), g" as
|
||||
// accessing a particular thread-local symbol. It does not affect code generation
|
||||
// but is used by the system linker when relaxing "initial exec" model code to
|
||||
// "local exec" model code.
|
||||
R_POWER_TLS
|
||||
|
||||
// R_ADDRPOWER_DS is similar to R_ADDRPOWER above, but assumes the second
|
||||
// instruction is a "DS-form" instruction, which has an immediate field occupying
|
||||
// bits [15:2] of the instruction word. Bits [15:2] of the address of the
|
||||
// relocated symbol are inserted into this field; it is an error if the last two
|
||||
// bits of the address are not 0.
|
||||
R_ADDRPOWER_DS
|
||||
|
||||
// R_ADDRPOWER_PCREL relocates a D-form, DS-form instruction sequence like
|
||||
// R_ADDRPOWER_DS but inserts the offset of the GOT slot for the referenced symbol
|
||||
// from the TOC rather than the symbol's address.
|
||||
R_ADDRPOWER_GOT
|
||||
|
||||
// R_ADDRPOWER_PCREL relocates two D-form instructions like R_ADDRPOWER, but
|
||||
// inserts the displacement from the place being relocated to the address of the
|
||||
// relocated symbol instead of just its address.
|
||||
R_ADDRPOWER_PCREL
|
||||
|
||||
// R_ADDRPOWER_TOCREL relocates two D-form instructions like R_ADDRPOWER, but
|
||||
// inserts the offset from the TOC to the address of the relocated symbol
|
||||
// rather than the symbol's address.
|
||||
R_ADDRPOWER_TOCREL
|
||||
|
||||
// R_ADDRPOWER_TOCREL relocates a D-form, DS-form instruction sequence like
|
||||
// R_ADDRPOWER_DS but inserts the offset from the TOC to the address of the
|
||||
// relocated symbol rather than the symbol's address.
|
||||
R_ADDRPOWER_TOCREL_DS
|
||||
|
||||
// RISC-V.
|
||||
|
||||
// R_RISCV_PCREL_ITYPE resolves a 32-bit PC-relative address using an
|
||||
// AUIPC + I-type instruction pair.
|
||||
R_RISCV_PCREL_ITYPE
|
||||
|
||||
// R_RISCV_PCREL_STYPE resolves a 32-bit PC-relative address using an
|
||||
// AUIPC + S-type instruction pair.
|
||||
R_RISCV_PCREL_STYPE
|
||||
|
||||
// R_RISCV_TLS_IE_ITYPE resolves a 32-bit TLS initial-exec TOC offset
|
||||
// address using an AUIPC + I-type instruction pair.
|
||||
R_RISCV_TLS_IE_ITYPE
|
||||
|
||||
// R_RISCV_TLS_IE_STYPE resolves a 32-bit TLS initial-exec TOC offset
|
||||
// address using an AUIPC + S-type instruction pair.
|
||||
R_RISCV_TLS_IE_STYPE
|
||||
|
||||
// R_PCRELDBL relocates s390x 2-byte aligned PC-relative addresses.
|
||||
// TODO(mundaym): remove once variants can be serialized - see issue 14218.
|
||||
R_PCRELDBL
|
||||
|
||||
// R_ADDRMIPSU (only used on mips/mips64) resolves to the sign-adjusted "upper" 16
|
||||
// bits (bit 16-31) of an external address, by encoding it into the instruction.
|
||||
R_ADDRMIPSU
|
||||
// R_ADDRMIPSTLS (only used on mips64) resolves to the low 16 bits of a TLS
|
||||
// address (offset from thread pointer), by encoding it into the instruction.
|
||||
R_ADDRMIPSTLS
|
||||
|
||||
// R_ADDRCUOFF resolves to a pointer-sized offset from the start of the
|
||||
// symbol's DWARF compile unit.
|
||||
R_ADDRCUOFF
|
||||
|
||||
// R_WASMIMPORT resolves to the index of the WebAssembly function import.
|
||||
R_WASMIMPORT
|
||||
|
||||
// R_XCOFFREF (only used on aix/ppc64) prevents garbage collection by ld
|
||||
// of a symbol. This isn't a real relocation, it can be placed in anywhere
|
||||
// in a symbol and target any symbols.
|
||||
R_XCOFFREF
|
||||
)
|
||||
|
||||
// IsDirectCall reports whether r is a relocation for a direct call.
|
||||
// A direct call is a CALL instruction that takes the target address
|
||||
// as an immediate. The address is embedded into the instruction, possibly
|
||||
// with limited width. An indirect call is a CALL instruction that takes
|
||||
// the target address in register or memory.
|
||||
func (r RelocType) IsDirectCall() bool {
|
||||
switch r {
|
||||
case R_CALL, R_CALLARM, R_CALLARM64, R_CALLMIPS, R_CALLPOWER, R_CALLRISCV:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDirectJump reports whether r is a relocation for a direct jump.
|
||||
// A direct jump is a JMP instruction that takes the target address
|
||||
// as an immediate. The address is embedded into the instruction, possibly
|
||||
// with limited width. An indirect jump is a JMP instruction that takes
|
||||
// the target address in register or memory.
|
||||
func (r RelocType) IsDirectJump() bool {
|
||||
switch r {
|
||||
case R_JMPMIPS:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDirectCallOrJump reports whether r is a relocation for a direct
|
||||
// call or a direct jump.
|
||||
func (r RelocType) IsDirectCallOrJump() bool {
|
||||
return r.IsDirectCall() || r.IsDirectJump()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Code generated by "stringer -type=RelocType"; DO NOT EDIT.
|
||||
|
||||
package objabi
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[R_ADDR-1]
|
||||
_ = x[R_ADDRPOWER-2]
|
||||
_ = x[R_ADDRARM64-3]
|
||||
_ = x[R_ADDRMIPS-4]
|
||||
_ = x[R_ADDROFF-5]
|
||||
_ = x[R_WEAKADDROFF-6]
|
||||
_ = x[R_SIZE-7]
|
||||
_ = x[R_CALL-8]
|
||||
_ = x[R_CALLARM-9]
|
||||
_ = x[R_CALLARM64-10]
|
||||
_ = x[R_CALLIND-11]
|
||||
_ = x[R_CALLPOWER-12]
|
||||
_ = x[R_CALLMIPS-13]
|
||||
_ = x[R_CALLRISCV-14]
|
||||
_ = x[R_CONST-15]
|
||||
_ = x[R_PCREL-16]
|
||||
_ = x[R_TLS_LE-17]
|
||||
_ = x[R_TLS_IE-18]
|
||||
_ = x[R_GOTOFF-19]
|
||||
_ = x[R_PLT0-20]
|
||||
_ = x[R_PLT1-21]
|
||||
_ = x[R_PLT2-22]
|
||||
_ = x[R_USEFIELD-23]
|
||||
_ = x[R_USETYPE-24]
|
||||
_ = x[R_USEIFACE-25]
|
||||
_ = x[R_USEIFACEMETHOD-26]
|
||||
_ = x[R_METHODOFF-27]
|
||||
_ = x[R_POWER_TOC-28]
|
||||
_ = x[R_GOTPCREL-29]
|
||||
_ = x[R_JMPMIPS-30]
|
||||
_ = x[R_DWARFSECREF-31]
|
||||
_ = x[R_DWARFFILEREF-32]
|
||||
_ = x[R_ARM64_TLS_LE-33]
|
||||
_ = x[R_ARM64_TLS_IE-34]
|
||||
_ = x[R_ARM64_GOTPCREL-35]
|
||||
_ = x[R_ARM64_GOT-36]
|
||||
_ = x[R_ARM64_PCREL-37]
|
||||
_ = x[R_ARM64_LDST8-38]
|
||||
_ = x[R_ARM64_LDST16-39]
|
||||
_ = x[R_ARM64_LDST32-40]
|
||||
_ = x[R_ARM64_LDST64-41]
|
||||
_ = x[R_ARM64_LDST128-42]
|
||||
_ = x[R_POWER_TLS_LE-43]
|
||||
_ = x[R_POWER_TLS_IE-44]
|
||||
_ = x[R_POWER_TLS-45]
|
||||
_ = x[R_ADDRPOWER_DS-46]
|
||||
_ = x[R_ADDRPOWER_GOT-47]
|
||||
_ = x[R_ADDRPOWER_PCREL-48]
|
||||
_ = x[R_ADDRPOWER_TOCREL-49]
|
||||
_ = x[R_ADDRPOWER_TOCREL_DS-50]
|
||||
_ = x[R_RISCV_PCREL_ITYPE-51]
|
||||
_ = x[R_RISCV_PCREL_STYPE-52]
|
||||
_ = x[R_RISCV_TLS_IE_ITYPE-53]
|
||||
_ = x[R_RISCV_TLS_IE_STYPE-54]
|
||||
_ = x[R_PCRELDBL-55]
|
||||
_ = x[R_ADDRMIPSU-56]
|
||||
_ = x[R_ADDRMIPSTLS-57]
|
||||
_ = x[R_ADDRCUOFF-58]
|
||||
_ = x[R_WASMIMPORT-59]
|
||||
_ = x[R_XCOFFREF-60]
|
||||
}
|
||||
|
||||
const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_WEAKADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_USEIFACER_USEIFACEMETHODR_METHODOFFR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST16R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_RISCV_TLS_IE_ITYPER_RISCV_TLS_IE_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF"
|
||||
|
||||
var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 60, 66, 72, 81, 92, 101, 112, 122, 133, 140, 147, 155, 163, 171, 177, 183, 189, 199, 208, 218, 234, 245, 256, 266, 275, 288, 302, 316, 330, 346, 357, 370, 383, 397, 411, 425, 440, 454, 468, 479, 493, 508, 525, 543, 564, 583, 602, 622, 642, 652, 663, 676, 687, 699, 709}
|
||||
|
||||
func (i RelocType) String() string {
|
||||
i -= 1
|
||||
if i < 0 || i >= RelocType(len(_RelocType_index)-1) {
|
||||
return "RelocType(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
}
|
||||
return _RelocType_name[_RelocType_index[i]:_RelocType_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Derived from Inferno utils/6l/l.h and related files.
|
||||
// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6l/l.h
|
||||
//
|
||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package objabi
|
||||
|
||||
// A SymKind describes the kind of memory represented by a symbol.
|
||||
type SymKind uint8
|
||||
|
||||
// Defined SymKind values.
|
||||
// These are used to index into cmd/link/internal/sym/AbiSymKindToSymKind
|
||||
//
|
||||
// TODO(rsc): Give idiomatic Go names.
|
||||
//go:generate stringer -type=SymKind
|
||||
const (
|
||||
// An otherwise invalid zero value for the type
|
||||
Sxxx SymKind = iota
|
||||
// Executable instructions
|
||||
STEXT
|
||||
// Read only static data
|
||||
SRODATA
|
||||
// Static data that does not contain any pointers
|
||||
SNOPTRDATA
|
||||
// Static data
|
||||
SDATA
|
||||
// Statically data that is initially all 0s
|
||||
SBSS
|
||||
// Statically data that is initially all 0s and does not contain pointers
|
||||
SNOPTRBSS
|
||||
// Thread-local data that is initially all 0s
|
||||
STLSBSS
|
||||
// Debugging data
|
||||
SDWARFCUINFO
|
||||
SDWARFCONST
|
||||
SDWARFFCN
|
||||
SDWARFABSFCN
|
||||
SDWARFTYPE
|
||||
SDWARFVAR
|
||||
SDWARFRANGE
|
||||
SDWARFLOC
|
||||
SDWARFLINES
|
||||
// ABI alias. An ABI alias symbol is an empty symbol with a
|
||||
// single relocation with 0 size that references the native
|
||||
// function implementation symbol.
|
||||
//
|
||||
// TODO(austin): Remove this and all uses once the compiler
|
||||
// generates real ABI wrappers rather than symbol aliases.
|
||||
SABIALIAS
|
||||
// Coverage instrumentation counter for libfuzzer.
|
||||
SLIBFUZZER_EXTRA_COUNTER
|
||||
// Update cmd/link/internal/sym/AbiSymKindToSymKind for new SymKind values.
|
||||
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
// Code generated by "stringer -type=SymKind"; DO NOT EDIT.
|
||||
|
||||
package objabi
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Sxxx-0]
|
||||
_ = x[STEXT-1]
|
||||
_ = x[SRODATA-2]
|
||||
_ = x[SNOPTRDATA-3]
|
||||
_ = x[SDATA-4]
|
||||
_ = x[SBSS-5]
|
||||
_ = x[SNOPTRBSS-6]
|
||||
_ = x[STLSBSS-7]
|
||||
_ = x[SDWARFCUINFO-8]
|
||||
_ = x[SDWARFCONST-9]
|
||||
_ = x[SDWARFFCN-10]
|
||||
_ = x[SDWARFABSFCN-11]
|
||||
_ = x[SDWARFTYPE-12]
|
||||
_ = x[SDWARFVAR-13]
|
||||
_ = x[SDWARFRANGE-14]
|
||||
_ = x[SDWARFLOC-15]
|
||||
_ = x[SDWARFLINES-16]
|
||||
_ = x[SABIALIAS-17]
|
||||
_ = x[SLIBFUZZER_EXTRA_COUNTER-18]
|
||||
}
|
||||
|
||||
const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSABIALIASSLIBFUZZER_EXTRA_COUNTER"
|
||||
|
||||
var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 63, 74, 83, 95, 105, 114, 125, 134, 145, 154, 178}
|
||||
|
||||
func (i SymKind) String() string {
|
||||
if i >= SymKind(len(_SymKind_index)-1) {
|
||||
return "SymKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _SymKind_name[_SymKind_index[i]:_SymKind_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2012 The Go 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 objabi
|
||||
|
||||
// Must match runtime and reflect.
|
||||
// Included by cmd/gc.
|
||||
|
||||
const (
|
||||
KindBool = 1 + iota
|
||||
KindInt
|
||||
KindInt8
|
||||
KindInt16
|
||||
KindInt32
|
||||
KindInt64
|
||||
KindUint
|
||||
KindUint8
|
||||
KindUint16
|
||||
KindUint32
|
||||
KindUint64
|
||||
KindUintptr
|
||||
KindFloat32
|
||||
KindFloat64
|
||||
KindComplex64
|
||||
KindComplex128
|
||||
KindArray
|
||||
KindChan
|
||||
KindFunc
|
||||
KindInterface
|
||||
KindMap
|
||||
KindPtr
|
||||
KindSlice
|
||||
KindString
|
||||
KindStruct
|
||||
KindUnsafePointer
|
||||
KindDirectIface = 1 << 5
|
||||
KindGCProg = 1 << 6
|
||||
KindMask = (1 << 5) - 1
|
||||
)
|
||||
@@ -0,0 +1,412 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
package objfile
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/mandiant/GoReSym/cmd/src"
|
||||
"github.com/mandiant/GoReSym/debug/gosym"
|
||||
|
||||
"golang.org/x/arch/arm/armasm"
|
||||
"golang.org/x/arch/arm64/arm64asm"
|
||||
"golang.org/x/arch/ppc64/ppc64asm"
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
)
|
||||
|
||||
// Disasm is a disassembler for a given File.
|
||||
type Disasm struct {
|
||||
syms []Sym //symbols in file, sorted by address
|
||||
pcln Liner // pcln table
|
||||
text []byte // bytes of text segment (actual instructions)
|
||||
textStart uint64 // start PC of text
|
||||
textEnd uint64 // end PC of text
|
||||
goarch string // GOARCH string
|
||||
disasm disasmFunc // disassembler function for goarch
|
||||
byteOrder binary.ByteOrder // byte order for goarch
|
||||
}
|
||||
|
||||
// Disasm returns a disassembler for the file f.
|
||||
func (e *Entry) Disasm() (*Disasm, error) {
|
||||
syms, err := e.Symbols()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pcln, _, err := e.PCLineTable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
textStart, textBytes, err := e.Text()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goarch := e.GOARCH()
|
||||
disasm := disasms[goarch]
|
||||
byteOrder := byteOrders[goarch]
|
||||
if disasm == nil || byteOrder == nil {
|
||||
return nil, fmt.Errorf("unsupported architecture")
|
||||
}
|
||||
|
||||
// Filter out section symbols, overwriting syms in place.
|
||||
keep := syms[:0]
|
||||
for _, sym := range syms {
|
||||
switch sym.Name {
|
||||
case "runtime.text", "text", "_text", "runtime.etext", "etext", "_etext":
|
||||
// drop
|
||||
default:
|
||||
keep = append(keep, sym)
|
||||
}
|
||||
}
|
||||
syms = keep
|
||||
d := &Disasm{
|
||||
syms: syms,
|
||||
pcln: pcln,
|
||||
text: textBytes,
|
||||
textStart: textStart,
|
||||
textEnd: textStart + uint64(len(textBytes)),
|
||||
goarch: goarch,
|
||||
disasm: disasm,
|
||||
byteOrder: byteOrder,
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// lookup finds the symbol name containing addr.
|
||||
func (d *Disasm) lookup(addr uint64) (name string, base uint64) {
|
||||
i := sort.Search(len(d.syms), func(i int) bool { return addr < d.syms[i].Addr })
|
||||
if i > 0 {
|
||||
s := d.syms[i-1]
|
||||
if s.Addr != 0 && s.Addr <= addr && addr < s.Addr+uint64(s.Size) {
|
||||
return s.Name, s.Addr
|
||||
}
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// base returns the final element in the path.
|
||||
// It works on both Windows and Unix paths,
|
||||
// regardless of host operating system.
|
||||
func base(path string) string {
|
||||
path = path[strings.LastIndex(path, "/")+1:]
|
||||
path = path[strings.LastIndex(path, `\`)+1:]
|
||||
return path
|
||||
}
|
||||
|
||||
// CachedFile contains the content of a file split into lines.
|
||||
type CachedFile struct {
|
||||
FileName string
|
||||
Lines [][]byte
|
||||
}
|
||||
|
||||
// FileCache is a simple LRU cache of file contents.
|
||||
type FileCache struct {
|
||||
files *list.List
|
||||
maxLen int
|
||||
}
|
||||
|
||||
// NewFileCache returns a FileCache which can contain up to maxLen cached file contents.
|
||||
func NewFileCache(maxLen int) *FileCache {
|
||||
return &FileCache{
|
||||
files: list.New(),
|
||||
maxLen: maxLen,
|
||||
}
|
||||
}
|
||||
|
||||
// Line returns the source code line for the given file and line number.
|
||||
// If the file is not already cached, reads it, inserts it into the cache,
|
||||
// and removes the least recently used file if necessary.
|
||||
// If the file is in cache, it is moved to the front of the list.
|
||||
func (fc *FileCache) Line(filename string, line int) ([]byte, error) {
|
||||
if filepath.Ext(filename) != ".go" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Clean filenames returned by src.Pos.SymFilename()
|
||||
// or src.PosBase.SymFilename() removing
|
||||
// the leading src.FileSymPrefix.
|
||||
filename = strings.TrimPrefix(filename, src.FileSymPrefix)
|
||||
|
||||
// Expand literal "$GOROOT" rewritten by obj.AbsFile()
|
||||
filename = filepath.Clean(os.ExpandEnv(filename))
|
||||
|
||||
var cf *CachedFile
|
||||
var e *list.Element
|
||||
|
||||
for e = fc.files.Front(); e != nil; e = e.Next() {
|
||||
cf = e.Value.(*CachedFile)
|
||||
if cf.FileName == filename {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if e == nil {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cf = &CachedFile{
|
||||
FileName: filename,
|
||||
Lines: bytes.Split(content, []byte{'\n'}),
|
||||
}
|
||||
fc.files.PushFront(cf)
|
||||
|
||||
if fc.files.Len() >= fc.maxLen {
|
||||
fc.files.Remove(fc.files.Back())
|
||||
}
|
||||
} else {
|
||||
fc.files.MoveToFront(e)
|
||||
}
|
||||
|
||||
// because //line directives can be out-of-range. (#36683)
|
||||
if line-1 >= len(cf.Lines) || line-1 < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return cf.Lines[line-1], nil
|
||||
}
|
||||
|
||||
// Print prints a disassembly of the file to w.
|
||||
// If filter is non-nil, the disassembly only includes functions with names matching filter.
|
||||
// If printCode is true, the disassembly includs corresponding source lines.
|
||||
// The disassembly only includes functions that overlap the range [start, end).
|
||||
func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64, printCode bool, gnuAsm bool) {
|
||||
if start < d.textStart {
|
||||
start = d.textStart
|
||||
}
|
||||
if end > d.textEnd {
|
||||
end = d.textEnd
|
||||
}
|
||||
printed := false
|
||||
bw := bufio.NewWriter(w)
|
||||
|
||||
var fc *FileCache
|
||||
if printCode {
|
||||
fc = NewFileCache(8)
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(bw, 18, 8, 1, '\t', tabwriter.StripEscape)
|
||||
for _, sym := range d.syms {
|
||||
symStart := sym.Addr
|
||||
symEnd := sym.Addr + uint64(sym.Size)
|
||||
relocs := sym.Relocs
|
||||
if sym.Code != 'T' && sym.Code != 't' ||
|
||||
symStart < d.textStart ||
|
||||
symEnd <= start || end <= symStart ||
|
||||
filter != nil && !filter.MatchString(sym.Name) {
|
||||
continue
|
||||
}
|
||||
if printed {
|
||||
fmt.Fprintf(bw, "\n")
|
||||
}
|
||||
printed = true
|
||||
|
||||
file, _, _ := d.pcln.PCToLine(sym.Addr)
|
||||
fmt.Fprintf(bw, "TEXT %s(SB) %s\n", sym.Name, file)
|
||||
|
||||
if symEnd > end {
|
||||
symEnd = end
|
||||
}
|
||||
code := d.text[:end-d.textStart]
|
||||
|
||||
var lastFile string
|
||||
var lastLine int
|
||||
|
||||
d.Decode(symStart, symEnd, relocs, gnuAsm, func(pc, size uint64, file string, line int, text string) {
|
||||
i := pc - d.textStart
|
||||
|
||||
if printCode {
|
||||
if file != lastFile || line != lastLine {
|
||||
if srcLine, err := fc.Line(file, line); err == nil {
|
||||
fmt.Fprintf(tw, "%s%s%s\n", []byte{tabwriter.Escape}, srcLine, []byte{tabwriter.Escape})
|
||||
}
|
||||
|
||||
lastFile, lastLine = file, line
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, " %#x\t", pc)
|
||||
} else {
|
||||
fmt.Fprintf(tw, " %s:%d\t%#x\t", base(file), line, pc)
|
||||
}
|
||||
|
||||
if size%4 != 0 || d.goarch == "386" || d.goarch == "amd64" {
|
||||
// Print instruction as bytes.
|
||||
fmt.Fprintf(tw, "%x", code[i:i+size])
|
||||
} else {
|
||||
// Print instruction as 32-bit words.
|
||||
for j := uint64(0); j < size; j += 4 {
|
||||
if j > 0 {
|
||||
fmt.Fprintf(tw, " ")
|
||||
}
|
||||
fmt.Fprintf(tw, "%08x", d.byteOrder.Uint32(code[i+j:]))
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(tw, "\t%s\t\n", text)
|
||||
})
|
||||
tw.Flush()
|
||||
}
|
||||
bw.Flush()
|
||||
}
|
||||
|
||||
// Decode disassembles the text segment range [start, end), calling f for each instruction.
|
||||
func (d *Disasm) Decode(start, end uint64, relocs []Reloc, gnuAsm bool, f func(pc, size uint64, file string, line int, text string)) {
|
||||
if start < d.textStart {
|
||||
start = d.textStart
|
||||
}
|
||||
if end > d.textEnd {
|
||||
end = d.textEnd
|
||||
}
|
||||
code := d.text[:end-d.textStart]
|
||||
lookup := d.lookup
|
||||
for pc := start; pc < end; {
|
||||
i := pc - d.textStart
|
||||
text, size := d.disasm(code[i:], pc, lookup, d.byteOrder, gnuAsm)
|
||||
file, line, _ := d.pcln.PCToLine(pc)
|
||||
sep := "\t"
|
||||
for len(relocs) > 0 && relocs[0].Addr < i+uint64(size) {
|
||||
text += sep + relocs[0].Stringer.String(pc-start)
|
||||
sep = " "
|
||||
relocs = relocs[1:]
|
||||
}
|
||||
f(pc, uint64(size), file, line, text)
|
||||
pc += uint64(size)
|
||||
}
|
||||
}
|
||||
|
||||
type lookupFunc = func(addr uint64) (sym string, base uint64)
|
||||
type disasmFunc func(code []byte, pc uint64, lookup lookupFunc, ord binary.ByteOrder, _ bool) (text string, size int)
|
||||
|
||||
func disasm_386(code []byte, pc uint64, lookup lookupFunc, _ binary.ByteOrder, gnuAsm bool) (string, int) {
|
||||
return disasm_x86(code, pc, lookup, 32, gnuAsm)
|
||||
}
|
||||
|
||||
func disasm_amd64(code []byte, pc uint64, lookup lookupFunc, _ binary.ByteOrder, gnuAsm bool) (string, int) {
|
||||
return disasm_x86(code, pc, lookup, 64, gnuAsm)
|
||||
}
|
||||
|
||||
func disasm_x86(code []byte, pc uint64, lookup lookupFunc, arch int, gnuAsm bool) (string, int) {
|
||||
inst, err := x86asm.Decode(code, arch)
|
||||
var text string
|
||||
size := inst.Len
|
||||
if err != nil || size == 0 || inst.Op == 0 {
|
||||
size = 1
|
||||
text = "?"
|
||||
} else {
|
||||
if gnuAsm {
|
||||
text = fmt.Sprintf("%-36s // %s", x86asm.GoSyntax(inst, pc, lookup), x86asm.GNUSyntax(inst, pc, nil))
|
||||
} else {
|
||||
text = x86asm.GoSyntax(inst, pc, lookup)
|
||||
}
|
||||
}
|
||||
return text, size
|
||||
}
|
||||
|
||||
type textReader struct {
|
||||
code []byte
|
||||
pc uint64
|
||||
}
|
||||
|
||||
func (r textReader) ReadAt(data []byte, off int64) (n int, err error) {
|
||||
if off < 0 || uint64(off) < r.pc {
|
||||
return 0, io.EOF
|
||||
}
|
||||
d := uint64(off) - r.pc
|
||||
if d >= uint64(len(r.code)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(data, r.code[d:])
|
||||
if n < len(data) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func disasm_arm(code []byte, pc uint64, lookup lookupFunc, _ binary.ByteOrder, gnuAsm bool) (string, int) {
|
||||
inst, err := armasm.Decode(code, armasm.ModeARM)
|
||||
var text string
|
||||
size := inst.Len
|
||||
if err != nil || size == 0 || inst.Op == 0 {
|
||||
size = 4
|
||||
text = "?"
|
||||
} else if gnuAsm {
|
||||
text = fmt.Sprintf("%-36s // %s", armasm.GoSyntax(inst, pc, lookup, textReader{code, pc}), armasm.GNUSyntax(inst))
|
||||
} else {
|
||||
text = armasm.GoSyntax(inst, pc, lookup, textReader{code, pc})
|
||||
}
|
||||
return text, size
|
||||
}
|
||||
|
||||
func disasm_arm64(code []byte, pc uint64, lookup lookupFunc, byteOrder binary.ByteOrder, gnuAsm bool) (string, int) {
|
||||
inst, err := arm64asm.Decode(code)
|
||||
var text string
|
||||
if err != nil || inst.Op == 0 {
|
||||
text = "?"
|
||||
} else if gnuAsm {
|
||||
text = fmt.Sprintf("%-36s // %s", arm64asm.GoSyntax(inst, pc, lookup, textReader{code, pc}), arm64asm.GNUSyntax(inst))
|
||||
} else {
|
||||
text = arm64asm.GoSyntax(inst, pc, lookup, textReader{code, pc})
|
||||
}
|
||||
return text, 4
|
||||
}
|
||||
|
||||
func disasm_ppc64(code []byte, pc uint64, lookup lookupFunc, byteOrder binary.ByteOrder, gnuAsm bool) (string, int) {
|
||||
inst, err := ppc64asm.Decode(code, byteOrder)
|
||||
var text string
|
||||
size := inst.Len
|
||||
if err != nil || size == 0 {
|
||||
size = 4
|
||||
text = "?"
|
||||
} else {
|
||||
if gnuAsm {
|
||||
text = fmt.Sprintf("%-36s // %s", ppc64asm.GoSyntax(inst, pc, lookup), ppc64asm.GNUSyntax(inst, pc))
|
||||
} else {
|
||||
text = ppc64asm.GoSyntax(inst, pc, lookup)
|
||||
}
|
||||
}
|
||||
return text, size
|
||||
}
|
||||
|
||||
var disasms = map[string]disasmFunc{
|
||||
"386": disasm_386,
|
||||
"amd64": disasm_amd64,
|
||||
"arm": disasm_arm,
|
||||
"arm64": disasm_arm64,
|
||||
"ppc64": disasm_ppc64,
|
||||
"ppc64le": disasm_ppc64,
|
||||
}
|
||||
|
||||
var byteOrders = map[string]binary.ByteOrder{
|
||||
"386": binary.LittleEndian,
|
||||
"amd64": binary.LittleEndian,
|
||||
"arm": binary.LittleEndian,
|
||||
"arm64": binary.LittleEndian,
|
||||
"ppc64": binary.BigEndian,
|
||||
"ppc64le": binary.LittleEndian,
|
||||
"s390x": binary.BigEndian,
|
||||
}
|
||||
|
||||
type Liner interface {
|
||||
// Given a pc, returns the corresponding file, line, and function data.
|
||||
// If unknown, returns "",0,nil.
|
||||
PCToLine(uint64) (string, int, *gosym.Func)
|
||||
}
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Parsing of ELF executables (Linux, FreeBSD, and so on).
|
||||
|
||||
package objfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mandiant/GoReSym/debug/dwarf"
|
||||
"github.com/mandiant/GoReSym/debug/elf"
|
||||
)
|
||||
|
||||
type elfFile struct {
|
||||
elf *elf.File
|
||||
}
|
||||
|
||||
func openElf(r io.ReaderAt) (rawFile, error) {
|
||||
f, err := elf.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &elfFile{f}, nil
|
||||
}
|
||||
|
||||
func (f *elfFile) read_memory(VA uint64, size uint64) (data []byte, err error) {
|
||||
for _, prog := range f.elf.Progs {
|
||||
if prog.Vaddr <= VA && VA <= prog.Vaddr+prog.Filesz-1 {
|
||||
n := prog.Vaddr + prog.Filesz - VA
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := prog.ReadAt(data, int64(VA-prog.Vaddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Failed to read memory")
|
||||
}
|
||||
|
||||
func (f *elfFile) symbols() ([]Sym, error) {
|
||||
elfSyms, err := f.elf.Symbols()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var syms []Sym
|
||||
for _, s := range elfSyms {
|
||||
sym := Sym{Addr: s.Value, Name: s.Name, Size: int64(s.Size), Code: '?'}
|
||||
switch s.Section {
|
||||
case elf.SHN_UNDEF:
|
||||
sym.Code = 'U'
|
||||
case elf.SHN_COMMON:
|
||||
sym.Code = 'B'
|
||||
default:
|
||||
i := int(s.Section)
|
||||
if i < 0 || i >= len(f.elf.Sections) {
|
||||
break
|
||||
}
|
||||
sect := f.elf.Sections[i]
|
||||
switch sect.Flags & (elf.SHF_WRITE | elf.SHF_ALLOC | elf.SHF_EXECINSTR) {
|
||||
case elf.SHF_ALLOC | elf.SHF_EXECINSTR:
|
||||
sym.Code = 'T'
|
||||
case elf.SHF_ALLOC:
|
||||
sym.Code = 'R'
|
||||
case elf.SHF_ALLOC | elf.SHF_WRITE:
|
||||
sym.Code = 'D'
|
||||
}
|
||||
}
|
||||
if elf.ST_BIND(s.Info) == elf.STB_LOCAL {
|
||||
sym.Code += 'a' - 'A'
|
||||
}
|
||||
syms = append(syms, sym)
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
func (f *elfFile) pcln_scan() (candidates []PclntabCandidate, err error) {
|
||||
// 1) Locate pclntab via symbols (standard way)
|
||||
foundpcln := false
|
||||
var pclntab []byte
|
||||
if sect := f.elf.Section(".gopclntab"); sect != nil {
|
||||
if pclntab, err = sect.Data(); err == nil {
|
||||
foundpcln = true
|
||||
}
|
||||
}
|
||||
|
||||
// 2) if not found, byte scan for it
|
||||
ExitScan:
|
||||
for _, sec := range f.elf.Sections {
|
||||
// first section is all zeros, skip
|
||||
if sec.Type == elf.SHT_NULL {
|
||||
continue
|
||||
}
|
||||
|
||||
// malware can split the pclntab across multiple sections, re-merge
|
||||
data := f.elf.DataAfterSection(sec.Name)
|
||||
if !foundpcln {
|
||||
// https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
|
||||
pclntab_sigs := [][]byte{[]byte("\xFB\xFF\xFF\xFF\x00\x00"), []byte("\xFA\xFF\xFF\xFF\x00\x00"), []byte("\xF0\xFF\xFF\xFF\x00\x00"),
|
||||
[]byte("\xFF\xFF\xFF\xFB\x00\x00"), []byte("\xFF\xFF\xFF\xFA\x00\x00"), []byte("\xFF\xFF\xFF\xF0\x00\x00")}
|
||||
matches := findAllOccurrences(data, pclntab_sigs)
|
||||
for _, pclntab_idx := range matches {
|
||||
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
|
||||
pclntab = data[pclntab_idx:]
|
||||
|
||||
var candidate PclntabCandidate
|
||||
candidate.pclntab = pclntab
|
||||
candidate.secStart = uint64(sec.Addr)
|
||||
candidate.pclntabVA = candidate.secStart + uint64(pclntab_idx)
|
||||
|
||||
candidates = append(candidates, candidate)
|
||||
// we must scan all signature for all sections. DO NOT BREAK
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
|
||||
pclntab_idx := bytes.Index(data, pclntab)
|
||||
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
|
||||
var candidate PclntabCandidate
|
||||
candidate.pclntab = pclntab
|
||||
candidate.secStart = uint64(sec.Addr)
|
||||
candidate.pclntabVA = candidate.secStart + uint64(pclntab_idx)
|
||||
|
||||
candidates = append(candidates, candidate)
|
||||
break ExitScan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (f *elfFile) pcln() (candidates []PclntabCandidate, err error) {
|
||||
candidates, err = f.pcln_scan()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4) symtab is completely optional, but try to find it
|
||||
var symtab []byte
|
||||
if sect := f.elf.Section(".gosymtab"); sect != nil {
|
||||
symtab, err = sect.Data()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for _, c := range candidates {
|
||||
c.symtab = symtab
|
||||
}
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (f *elfFile) moduledata_scan(pclntabVA uint64, is64bit bool, littleendian bool, ignorelist []uint64) (secStart uint64, moduledataVA uint64, moduledata []byte, err error) {
|
||||
foundsym := false
|
||||
foundsec := false
|
||||
foundmodule := false
|
||||
|
||||
syms, err := f.symbols()
|
||||
if err == nil {
|
||||
foundsym = false
|
||||
for _, sym := range syms {
|
||||
// TODO: handle legacy symbols ??
|
||||
if sym.Name == "runtime.firstmoduledata" {
|
||||
moduledataVA = sym.Addr
|
||||
foundsym = true // annoyingly the elf symbols dont give section #, so we delay getting data to later, unlike in pe
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan:
|
||||
for _, sec := range f.elf.Sections {
|
||||
// first section is all zeros, skip
|
||||
if sec.Type == elf.SHT_NULL {
|
||||
continue
|
||||
}
|
||||
|
||||
// malware can split the pclntab across multiple sections, re-merge
|
||||
data := f.elf.DataAfterSection(sec.Name)
|
||||
if !foundsym {
|
||||
// fall back to scanning for structure using address of pclntab, which is first value in struc
|
||||
var pclntabVA_bytes []byte
|
||||
if is64bit {
|
||||
pclntabVA_bytes = make([]byte, 8)
|
||||
if littleendian {
|
||||
binary.LittleEndian.PutUint64(pclntabVA_bytes, pclntabVA)
|
||||
} else {
|
||||
binary.BigEndian.PutUint64(pclntabVA_bytes, pclntabVA)
|
||||
}
|
||||
} else {
|
||||
pclntabVA_bytes = make([]byte, 4)
|
||||
if littleendian {
|
||||
binary.LittleEndian.PutUint32(pclntabVA_bytes, uint32(pclntabVA))
|
||||
} else {
|
||||
binary.BigEndian.PutUint32(pclntabVA_bytes, uint32(pclntabVA))
|
||||
}
|
||||
}
|
||||
|
||||
moduledata_idx := bytes.Index(data, pclntabVA_bytes)
|
||||
if moduledata_idx != -1 && moduledata_idx < int(sec.Size) {
|
||||
moduledata = data[moduledata_idx:]
|
||||
moduledataVA = sec.Addr + uint64(moduledata_idx)
|
||||
secStart = sec.Addr
|
||||
|
||||
// optionally consult ignore list, to skip past previous (bad) scan results
|
||||
if ignorelist != nil {
|
||||
for _, ignore := range ignorelist {
|
||||
if ignore == secStart+uint64(moduledata_idx) {
|
||||
continue scan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundsec = true
|
||||
foundmodule = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if moduledataVA > sec.Addr && moduledataVA < sec.Addr+sec.Size {
|
||||
sectionoffset := moduledataVA - sec.Addr
|
||||
moduledata = data[sectionoffset:]
|
||||
secStart = sec.Addr
|
||||
foundsec = true
|
||||
foundmodule = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundmodule {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata could not be located")
|
||||
}
|
||||
|
||||
if !foundsec {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata containing section could not be located")
|
||||
}
|
||||
|
||||
return secStart, moduledataVA, moduledata, nil
|
||||
}
|
||||
|
||||
func (f *elfFile) text() (textStart uint64, text []byte, err error) {
|
||||
sect := f.elf.Section(".text")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf("text section not found")
|
||||
}
|
||||
textStart = sect.Addr
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *elfFile) rdata() (textStart uint64, text []byte, err error) {
|
||||
sect := f.elf.Section(".rodata")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf("rdata section not found")
|
||||
}
|
||||
textStart = sect.Addr
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *elfFile) rel_rdata() (textStart uint64, text []byte, err error) {
|
||||
sect := f.elf.Section(".data.rel.ro")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf(".data.rel.ro section not found")
|
||||
}
|
||||
textStart = sect.Addr
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *elfFile) goarch() string {
|
||||
switch f.elf.Machine {
|
||||
case elf.EM_386:
|
||||
return "386"
|
||||
case elf.EM_X86_64:
|
||||
return "amd64"
|
||||
case elf.EM_ARM:
|
||||
return "arm"
|
||||
case elf.EM_AARCH64:
|
||||
return "arm64"
|
||||
case elf.EM_PPC64:
|
||||
if f.elf.ByteOrder == binary.LittleEndian {
|
||||
return "ppc64le"
|
||||
}
|
||||
return "ppc64"
|
||||
case elf.EM_S390:
|
||||
return "s390x"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *elfFile) loadAddress() (uint64, error) {
|
||||
for _, p := range f.elf.Progs {
|
||||
if p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 {
|
||||
return p.Vaddr, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("unknown load address")
|
||||
}
|
||||
|
||||
func (f *elfFile) dwarf() (*dwarf.Data, error) {
|
||||
return f.elf.DWARF()
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Parsing of Go intermediate object files and archives.
|
||||
|
||||
package objfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mandiant/GoReSym/archive"
|
||||
"github.com/mandiant/GoReSym/debug/dwarf"
|
||||
"github.com/mandiant/GoReSym/debug/gosym"
|
||||
"github.com/mandiant/GoReSym/goobj"
|
||||
"github.com/mandiant/GoReSym/objabi"
|
||||
"github.com/mandiant/GoReSym/sys"
|
||||
)
|
||||
|
||||
type goobjFile struct {
|
||||
goobj *archive.GoObj
|
||||
r *goobj.Reader
|
||||
f *os.File
|
||||
arch *sys.Arch
|
||||
}
|
||||
|
||||
func openGoFile(f *os.File) (*File, error) {
|
||||
a, err := archive.Parse(f, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries := make([]*Entry, 0, len(a.Entries))
|
||||
L:
|
||||
for _, e := range a.Entries {
|
||||
switch e.Type {
|
||||
case archive.EntryPkgDef:
|
||||
continue
|
||||
case archive.EntryGoObj:
|
||||
o := e.Obj
|
||||
b := make([]byte, o.Size)
|
||||
_, err := f.ReadAt(b, o.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := goobj.NewReaderFromBytes(b, false)
|
||||
var arch *sys.Arch
|
||||
for _, a := range sys.Archs {
|
||||
if a.Name == e.Obj.Arch {
|
||||
arch = a
|
||||
break
|
||||
}
|
||||
}
|
||||
entries = append(entries, &Entry{
|
||||
name: e.Name,
|
||||
raw: &goobjFile{e.Obj, r, f, arch},
|
||||
})
|
||||
continue
|
||||
case archive.EntryNativeObj:
|
||||
nr := io.NewSectionReader(f, e.Offset, e.Size)
|
||||
for _, try := range openers {
|
||||
if raw, err := try(nr); err == nil {
|
||||
entries = append(entries, &Entry{
|
||||
name: e.Name,
|
||||
raw: raw,
|
||||
})
|
||||
continue L
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("open %s: unrecognized archive member %s", f.Name(), e.Name)
|
||||
}
|
||||
return &File{f, entries}, nil
|
||||
}
|
||||
|
||||
func goobjName(name string, ver int) string {
|
||||
if ver == 0 {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf("%s<%d>", name, ver)
|
||||
}
|
||||
|
||||
type goobjReloc struct {
|
||||
Off int32
|
||||
Size uint8
|
||||
Type objabi.RelocType
|
||||
Add int64
|
||||
Sym string
|
||||
}
|
||||
|
||||
func (r goobjReloc) String(insnOffset uint64) string {
|
||||
delta := int64(r.Off) - int64(insnOffset)
|
||||
s := fmt.Sprintf("[%d:%d]%s", delta, delta+int64(r.Size), r.Type)
|
||||
if r.Sym != "" {
|
||||
if r.Add != 0 {
|
||||
return fmt.Sprintf("%s:%s+%d", s, r.Sym, r.Add)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", s, r.Sym)
|
||||
}
|
||||
if r.Add != 0 {
|
||||
return fmt.Sprintf("%s:%d", s, r.Add)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *goobjFile) read_memory(VA uint64, size uint64) (data []byte, err error) {
|
||||
return nil, errors.New("not implemented for go object file")
|
||||
}
|
||||
|
||||
func (f *goobjFile) symbols() ([]Sym, error) {
|
||||
r := f.r
|
||||
var syms []Sym
|
||||
|
||||
// Name of referenced indexed symbols.
|
||||
nrefName := r.NRefName()
|
||||
refNames := make(map[goobj.SymRef]string, nrefName)
|
||||
for i := 0; i < nrefName; i++ {
|
||||
rn := r.RefName(i)
|
||||
refNames[rn.Sym()] = rn.Name(r)
|
||||
}
|
||||
|
||||
abiToVer := func(abi uint16) int {
|
||||
var ver int
|
||||
if abi == goobj.SymABIstatic {
|
||||
// Static symbol
|
||||
ver = 1
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
resolveSymRef := func(s goobj.SymRef) string {
|
||||
var i uint32
|
||||
switch p := s.PkgIdx; p {
|
||||
case goobj.PkgIdxInvalid:
|
||||
if s.SymIdx != 0 {
|
||||
panic("bad sym ref")
|
||||
}
|
||||
return ""
|
||||
case goobj.PkgIdxHashed64:
|
||||
i = s.SymIdx + uint32(r.NSym())
|
||||
case goobj.PkgIdxHashed:
|
||||
i = s.SymIdx + uint32(r.NSym()+r.NHashed64def())
|
||||
case goobj.PkgIdxNone:
|
||||
i = s.SymIdx + uint32(r.NSym()+r.NHashed64def()+r.NHasheddef())
|
||||
case goobj.PkgIdxBuiltin:
|
||||
name, abi := goobj.BuiltinName(int(s.SymIdx))
|
||||
return goobjName(name, abi)
|
||||
case goobj.PkgIdxSelf:
|
||||
i = s.SymIdx
|
||||
default:
|
||||
return refNames[s]
|
||||
}
|
||||
sym := r.Sym(i)
|
||||
return goobjName(sym.Name(r), abiToVer(sym.ABI()))
|
||||
}
|
||||
|
||||
// Defined symbols
|
||||
ndef := uint32(r.NSym() + r.NHashed64def() + r.NHasheddef() + r.NNonpkgdef())
|
||||
for i := uint32(0); i < ndef; i++ {
|
||||
osym := r.Sym(i)
|
||||
if osym.Name(r) == "" {
|
||||
continue // not a real symbol
|
||||
}
|
||||
name := osym.Name(r)
|
||||
ver := osym.ABI()
|
||||
name = goobjName(name, abiToVer(ver))
|
||||
typ := objabi.SymKind(osym.Type())
|
||||
var code rune = '?'
|
||||
switch typ {
|
||||
case objabi.STEXT:
|
||||
code = 'T'
|
||||
case objabi.SRODATA:
|
||||
code = 'R'
|
||||
case objabi.SDATA:
|
||||
code = 'D'
|
||||
case objabi.SBSS, objabi.SNOPTRBSS, objabi.STLSBSS:
|
||||
code = 'B'
|
||||
}
|
||||
if ver >= goobj.SymABIstatic {
|
||||
code += 'a' - 'A'
|
||||
}
|
||||
|
||||
sym := Sym{
|
||||
Name: name,
|
||||
Addr: uint64(r.DataOff(i)),
|
||||
Size: int64(osym.Siz()),
|
||||
Code: code,
|
||||
}
|
||||
|
||||
relocs := r.Relocs(i)
|
||||
sym.Relocs = make([]Reloc, len(relocs))
|
||||
for j := range relocs {
|
||||
rel := &relocs[j]
|
||||
sym.Relocs[j] = Reloc{
|
||||
Addr: uint64(r.DataOff(i)) + uint64(rel.Off()),
|
||||
Size: uint64(rel.Siz()),
|
||||
Stringer: goobjReloc{
|
||||
Off: rel.Off(),
|
||||
Size: rel.Siz(),
|
||||
Type: objabi.RelocType(rel.Type()),
|
||||
Add: rel.Add(),
|
||||
Sym: resolveSymRef(rel.Sym()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
syms = append(syms, sym)
|
||||
}
|
||||
|
||||
// Referenced symbols
|
||||
n := ndef + uint32(r.NNonpkgref())
|
||||
for i := ndef; i < n; i++ {
|
||||
osym := r.Sym(i)
|
||||
sym := Sym{Name: osym.Name(r), Code: 'U'}
|
||||
syms = append(syms, sym)
|
||||
}
|
||||
for i := 0; i < nrefName; i++ {
|
||||
rn := r.RefName(i)
|
||||
sym := Sym{Name: rn.Name(r), Code: 'U'}
|
||||
syms = append(syms, sym)
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
func (f *goobjFile) pcln_scan() (candidates []PclntabCandidate, err error) {
|
||||
return nil, fmt.Errorf("pcln not available in go object file")
|
||||
}
|
||||
|
||||
func (f *goobjFile) pcln() (candidates []PclntabCandidate, err error) {
|
||||
// Should never be called. We implement Liner below, callers
|
||||
// should use that instead.
|
||||
return nil, fmt.Errorf("pcln not available in go object file")
|
||||
}
|
||||
|
||||
func (f *goobjFile) moduledata_scan(pclntabVA uint64, is64bit bool, littleendian bool, ignorelist []uint64) (secStart uint64, moduledataRVA uint64, moduledata []byte, err error) {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata not implemented at this time for go object file")
|
||||
}
|
||||
|
||||
// Find returns the file name, line, and function data for the given pc.
|
||||
// Returns "",0,nil if unknown.
|
||||
// This function implements the Liner interface in preference to pcln() above.
|
||||
func (f *goobjFile) PCToLine(pc uint64) (string, int, *gosym.Func) {
|
||||
r := f.r
|
||||
if f.arch == nil {
|
||||
return "", 0, nil
|
||||
}
|
||||
getSymData := func(s goobj.SymRef) []byte {
|
||||
if s.PkgIdx != goobj.PkgIdxHashed {
|
||||
// We don't need the data for non-hashed symbols, yet.
|
||||
panic("not supported")
|
||||
}
|
||||
i := uint32(s.SymIdx + uint32(r.NSym()+r.NHashed64def()))
|
||||
return r.BytesAt(r.DataOff(i), r.DataSize(i))
|
||||
}
|
||||
|
||||
ndef := uint32(r.NSym() + r.NHashed64def() + r.NHasheddef() + r.NNonpkgdef())
|
||||
for i := uint32(0); i < ndef; i++ {
|
||||
osym := r.Sym(i)
|
||||
addr := uint64(r.DataOff(i))
|
||||
if pc < addr || pc >= addr+uint64(osym.Siz()) {
|
||||
continue
|
||||
}
|
||||
isym := ^uint32(0)
|
||||
auxs := r.Auxs(i)
|
||||
for j := range auxs {
|
||||
a := &auxs[j]
|
||||
if a.Type() != goobj.AuxFuncInfo {
|
||||
continue
|
||||
}
|
||||
if a.Sym().PkgIdx != goobj.PkgIdxSelf {
|
||||
panic("funcinfo symbol not defined in current package")
|
||||
}
|
||||
isym = a.Sym().SymIdx
|
||||
}
|
||||
if isym == ^uint32(0) {
|
||||
continue
|
||||
}
|
||||
b := r.BytesAt(r.DataOff(isym), r.DataSize(isym))
|
||||
var info *goobj.FuncInfo
|
||||
pcline := getSymData(info.ReadPcline(b))
|
||||
line := int(pcValue(pcline, pc-addr, f.arch))
|
||||
pcfile := getSymData(info.ReadPcfile(b))
|
||||
fileID := pcValue(pcfile, pc-addr, f.arch)
|
||||
fileName := r.File(int(fileID))
|
||||
// Note: we provide only the name in the Func structure.
|
||||
// We could provide more if needed.
|
||||
return fileName, line, &gosym.Func{Sym: &gosym.Sym{Name: osym.Name(r)}}
|
||||
}
|
||||
return "", 0, nil
|
||||
}
|
||||
|
||||
// pcValue looks up the given PC in a pc value table. target is the
|
||||
// offset of the pc from the entry point.
|
||||
func pcValue(tab []byte, target uint64, arch *sys.Arch) int32 {
|
||||
val := int32(-1)
|
||||
var pc uint64
|
||||
for step(&tab, &pc, &val, pc == 0, arch) {
|
||||
if target < pc {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// step advances to the next pc, value pair in the encoded table.
|
||||
func step(p *[]byte, pc *uint64, val *int32, first bool, arch *sys.Arch) bool {
|
||||
uvdelta := readvarint(p)
|
||||
if uvdelta == 0 && !first {
|
||||
return false
|
||||
}
|
||||
if uvdelta&1 != 0 {
|
||||
uvdelta = ^(uvdelta >> 1)
|
||||
} else {
|
||||
uvdelta >>= 1
|
||||
}
|
||||
vdelta := int32(uvdelta)
|
||||
pcdelta := readvarint(p) * uint32(arch.MinLC)
|
||||
*pc += uint64(pcdelta)
|
||||
*val += vdelta
|
||||
return true
|
||||
}
|
||||
|
||||
// readvarint reads, removes, and returns a varint from *p.
|
||||
func readvarint(p *[]byte) uint32 {
|
||||
var v, shift uint32
|
||||
s := *p
|
||||
for shift = 0; ; shift += 7 {
|
||||
b := s[0]
|
||||
s = s[1:]
|
||||
v |= (uint32(b) & 0x7F) << shift
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
*p = s
|
||||
return v
|
||||
}
|
||||
|
||||
// We treat the whole object file as the text section.
|
||||
func (f *goobjFile) text() (textStart uint64, text []byte, err error) {
|
||||
text = make([]byte, f.goobj.Size)
|
||||
_, err = f.f.ReadAt(text, int64(f.goobj.Offset))
|
||||
return
|
||||
}
|
||||
|
||||
// We treat the whole object file as the data section.
|
||||
func (f *goobjFile) rdata() (textStart uint64, text []byte, err error) {
|
||||
text = make([]byte, f.goobj.Size)
|
||||
_, err = f.f.ReadAt(text, int64(f.goobj.Offset))
|
||||
return
|
||||
}
|
||||
|
||||
func (f *goobjFile) goarch() string {
|
||||
return f.goobj.Arch
|
||||
}
|
||||
|
||||
func (f *goobjFile) loadAddress() (uint64, error) {
|
||||
return 0, fmt.Errorf("unknown load address")
|
||||
}
|
||||
|
||||
func (f *goobjFile) dwarf() (*dwarf.Data, error) {
|
||||
return nil, errors.New("no DWARF data in go object file")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,316 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Parsing of Mach-O executables (OS X).
|
||||
|
||||
package objfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/mandiant/GoReSym/debug/dwarf"
|
||||
"github.com/mandiant/GoReSym/debug/macho"
|
||||
)
|
||||
|
||||
const stabTypeMask = 0xe0
|
||||
|
||||
type machoFile struct {
|
||||
macho *macho.File
|
||||
}
|
||||
|
||||
func openMacho(r io.ReaderAt) (rawFile, error) {
|
||||
f, err := macho.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &machoFile{f}, nil
|
||||
}
|
||||
|
||||
func (f *machoFile) read_memory(VA uint64, size uint64) (data []byte, err error) {
|
||||
for _, load := range f.macho.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if seg.Addr <= VA && VA <= seg.Addr+seg.Filesz-1 {
|
||||
if seg.Name == "__PAGEZERO" {
|
||||
continue
|
||||
}
|
||||
n := seg.Addr + seg.Filesz - VA
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := seg.ReadAt(data, int64(VA-seg.Addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Failed to read memory")
|
||||
}
|
||||
|
||||
func (f *machoFile) symbols() ([]Sym, error) {
|
||||
if f.macho.Symtab == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build sorted list of addresses of all symbols.
|
||||
// We infer the size of a symbol by looking at where the next symbol begins.
|
||||
var addrs []uint64
|
||||
for _, s := range f.macho.Symtab.Syms {
|
||||
// Skip stab debug info.
|
||||
if s.Type&stabTypeMask == 0 {
|
||||
addrs = append(addrs, s.Value)
|
||||
}
|
||||
}
|
||||
sort.Sort(uint64s(addrs))
|
||||
|
||||
var syms []Sym
|
||||
for _, s := range f.macho.Symtab.Syms {
|
||||
if s.Type&stabTypeMask != 0 {
|
||||
// Skip stab debug info.
|
||||
continue
|
||||
}
|
||||
sym := Sym{Name: s.Name, Addr: s.Value, Code: '?'}
|
||||
i := sort.Search(len(addrs), func(x int) bool { return addrs[x] > s.Value })
|
||||
if i < len(addrs) {
|
||||
sym.Size = int64(addrs[i] - s.Value)
|
||||
}
|
||||
if s.Sect == 0 {
|
||||
sym.Code = 'U'
|
||||
} else if int(s.Sect) <= len(f.macho.Sections) {
|
||||
sect := f.macho.Sections[s.Sect-1]
|
||||
switch sect.Seg {
|
||||
case "__TEXT", "__DATA_CONST":
|
||||
sym.Code = 'R'
|
||||
case "__DATA":
|
||||
sym.Code = 'D'
|
||||
}
|
||||
switch sect.Seg + " " + sect.Name {
|
||||
case "__TEXT __text":
|
||||
sym.Code = 'T'
|
||||
case "__DATA __bss", "__DATA __noptrbss":
|
||||
sym.Code = 'B'
|
||||
}
|
||||
}
|
||||
syms = append(syms, sym)
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
func (f *machoFile) pcln_scan() (candidates []PclntabCandidate, err error) {
|
||||
// 1) Locate pclntab via symbols (standard way)
|
||||
foundpcln := false
|
||||
var pclntab []byte
|
||||
if sect := f.macho.Section("__gopclntab"); sect != nil {
|
||||
if pclntab, err = sect.Data(); err == nil {
|
||||
foundpcln = true
|
||||
}
|
||||
}
|
||||
|
||||
// 2) if not found, byte scan for it
|
||||
ExitScan:
|
||||
for _, sec := range f.macho.Sections {
|
||||
// malware can split the pclntab across multiple sections, re-merge
|
||||
data := f.macho.DataAfterSection(sec.Name)
|
||||
|
||||
if !foundpcln {
|
||||
// https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
|
||||
pclntab_sigs := [][]byte{[]byte("\xFB\xFF\xFF\xFF\x00\x00"), []byte("\xFA\xFF\xFF\xFF\x00\x00"), []byte("\xF0\xFF\xFF\xFF\x00\x00"),
|
||||
[]byte("\xFF\xFF\xFF\xFB\x00\x00"), []byte("\xFF\xFF\xFF\xFA\x00\x00"), []byte("\xFF\xFF\xFF\xF0\x00\x00")}
|
||||
matches := findAllOccurrences(data, pclntab_sigs)
|
||||
for _, pclntab_idx := range matches {
|
||||
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
|
||||
pclntab = data[pclntab_idx:]
|
||||
|
||||
var candidate PclntabCandidate
|
||||
candidate.pclntab = pclntab
|
||||
candidate.secStart = uint64(sec.Addr)
|
||||
candidate.pclntabVA = candidate.secStart + uint64(pclntab_idx)
|
||||
|
||||
candidates = append(candidates, candidate)
|
||||
// we must scan all signature for all sections. DO NOT BREAK
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
|
||||
pclntab_idx := bytes.Index(data, pclntab)
|
||||
if pclntab_idx != -1 {
|
||||
var candidate PclntabCandidate
|
||||
candidate.pclntab = pclntab
|
||||
candidate.secStart = uint64(sec.Addr)
|
||||
candidate.pclntabVA = candidate.secStart + uint64(pclntab_idx)
|
||||
|
||||
candidates = append(candidates, candidate)
|
||||
|
||||
break ExitScan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (f *machoFile) pcln() (candidates []PclntabCandidate, err error) {
|
||||
candidates, err = f.pcln_scan()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4) symtab is completely optional, but try to find it
|
||||
var symtab []byte
|
||||
if sect := f.macho.Section("__gosymtab"); sect != nil {
|
||||
symtab, err = sect.Data()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for _, c := range candidates {
|
||||
c.symtab = symtab
|
||||
}
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (f *machoFile) moduledata_scan(pclntabVA uint64, is64bit bool, littleendian bool, ignorelist []uint64) (secStart uint64, moduledataVA uint64, moduledata []byte, err error) {
|
||||
foundsym := false
|
||||
foundsec := false
|
||||
foundmodule := false
|
||||
|
||||
syms, err := f.symbols()
|
||||
if err == nil {
|
||||
foundsym = false
|
||||
for _, sym := range syms {
|
||||
// TODO: handle legacy symbols ??
|
||||
if sym.Name == "runtime.firstmoduledata" {
|
||||
moduledataVA = sym.Addr
|
||||
foundsym = true // annoyingly the elf symbols dont give section #, so we delay getting data to later, unlike in pe
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan:
|
||||
for _, sec := range f.macho.Sections {
|
||||
// malware can split the pclntab across multiple sections, re-merge
|
||||
data := f.macho.DataAfterSection(sec.Name)
|
||||
if !foundsym {
|
||||
// fall back to scanning for structure using address of pclntab, which is first value in struc
|
||||
var pclntabVA_bytes []byte
|
||||
if is64bit {
|
||||
pclntabVA_bytes = make([]byte, 8)
|
||||
if littleendian {
|
||||
binary.LittleEndian.PutUint64(pclntabVA_bytes, pclntabVA)
|
||||
} else {
|
||||
binary.BigEndian.PutUint64(pclntabVA_bytes, pclntabVA)
|
||||
}
|
||||
} else {
|
||||
pclntabVA_bytes = make([]byte, 4)
|
||||
if littleendian {
|
||||
binary.LittleEndian.PutUint32(pclntabVA_bytes, uint32(pclntabVA))
|
||||
} else {
|
||||
binary.BigEndian.PutUint32(pclntabVA_bytes, uint32(pclntabVA))
|
||||
}
|
||||
}
|
||||
|
||||
moduledata_idx := bytes.Index(data, pclntabVA_bytes)
|
||||
if moduledata_idx != -1 && moduledata_idx < int(sec.Size) {
|
||||
moduledata = data[moduledata_idx:]
|
||||
moduledataVA = sec.Addr + uint64(moduledata_idx)
|
||||
secStart = sec.Addr
|
||||
|
||||
// optionally consult ignore list, to skip past previous (bad) scan results
|
||||
if ignorelist != nil {
|
||||
for _, ignore := range ignorelist {
|
||||
if ignore == moduledataVA {
|
||||
continue scan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundsec = true
|
||||
foundmodule = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if moduledataVA > sec.Addr && moduledataVA < sec.Addr+sec.Size {
|
||||
sectionoffset := moduledataVA - sec.Addr
|
||||
moduledata = data[sectionoffset:]
|
||||
secStart = sec.Addr
|
||||
foundsec = true
|
||||
foundmodule = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundmodule {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata could not be located")
|
||||
}
|
||||
|
||||
if !foundsec {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata containing section could not be located")
|
||||
}
|
||||
|
||||
return secStart, moduledataVA, moduledata, nil
|
||||
}
|
||||
|
||||
func (f *machoFile) text() (textStart uint64, text []byte, err error) {
|
||||
sect := f.macho.Section("__text")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf("text section not found")
|
||||
}
|
||||
textStart = sect.Addr
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *machoFile) rdata() (textStart uint64, text []byte, err error) {
|
||||
sect := f.macho.Section("__DATA")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf("data section not found")
|
||||
}
|
||||
textStart = sect.Addr
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *machoFile) goarch() string {
|
||||
switch f.macho.Cpu {
|
||||
case macho.Cpu386:
|
||||
return "386"
|
||||
case macho.CpuAmd64:
|
||||
return "amd64"
|
||||
case macho.CpuArm:
|
||||
return "arm"
|
||||
case macho.CpuArm64:
|
||||
return "arm64"
|
||||
case macho.CpuPpc64:
|
||||
return "ppc64"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type uint64s []uint64
|
||||
|
||||
func (x uint64s) Len() int { return len(x) }
|
||||
func (x uint64s) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x uint64s) Less(i, j int) bool { return x[i] < x[j] }
|
||||
|
||||
func (f *machoFile) loadAddress() (uint64, error) {
|
||||
return 0, fmt.Errorf("unknown load address")
|
||||
}
|
||||
|
||||
func (f *machoFile) dwarf() (*dwarf.Data, error) {
|
||||
return f.macho.DWARF()
|
||||
}
|
||||
+1537
File diff suppressed because it is too large
Load Diff
+415
@@ -0,0 +1,415 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
||||
|
||||
// Parsing of PE executables (Microsoft Windows).
|
||||
|
||||
package objfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/mandiant/GoReSym/debug/dwarf"
|
||||
"github.com/mandiant/GoReSym/debug/pe"
|
||||
)
|
||||
|
||||
type peFile struct {
|
||||
pe *pe.File
|
||||
}
|
||||
|
||||
func openPE(r io.ReaderAt) (rawFile, error) {
|
||||
f, err := pe.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &peFile{f}, nil
|
||||
}
|
||||
|
||||
func (f *peFile) read_memory(VA uint64, size uint64) (data []byte, err error) {
|
||||
var imageBase uint64
|
||||
switch oh := f.pe.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
imageBase = uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
imageBase = oh.ImageBase
|
||||
}
|
||||
|
||||
VA -= imageBase
|
||||
for _, sect := range f.pe.Sections {
|
||||
if uint64(sect.VirtualAddress) <= VA && VA <= uint64(sect.VirtualAddress+sect.Size-1) {
|
||||
n := uint64(sect.VirtualAddress+sect.Size) - VA
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := sect.ReadAt(data, int64(VA-uint64(sect.VirtualAddress)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading section data failed")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Failed to read memory")
|
||||
}
|
||||
|
||||
func (f *peFile) symbols() ([]Sym, error) {
|
||||
// Build sorted list of addresses of all symbols.
|
||||
// We infer the size of a symbol by looking at where the next symbol begins.
|
||||
var addrs []uint64
|
||||
|
||||
var imageBase uint64
|
||||
switch oh := f.pe.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
imageBase = uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
imageBase = oh.ImageBase
|
||||
}
|
||||
|
||||
var syms []Sym
|
||||
for _, s := range f.pe.Symbols {
|
||||
const (
|
||||
N_UNDEF = 0 // An undefined (extern) symbol
|
||||
N_ABS = -1 // An absolute symbol (e_value is a constant, not an address)
|
||||
N_DEBUG = -2 // A debugging symbol
|
||||
)
|
||||
sym := Sym{Name: s.Name, Addr: uint64(s.Value), Code: '?'}
|
||||
switch s.SectionNumber {
|
||||
case N_UNDEF:
|
||||
sym.Code = 'U'
|
||||
case N_ABS:
|
||||
sym.Code = 'C'
|
||||
case N_DEBUG:
|
||||
sym.Code = '?'
|
||||
default:
|
||||
if s.SectionNumber < 0 || len(f.pe.Sections) < int(s.SectionNumber) {
|
||||
return nil, fmt.Errorf("invalid section number in symbol table")
|
||||
}
|
||||
sect := f.pe.Sections[s.SectionNumber-1]
|
||||
const (
|
||||
text = 0x20
|
||||
data = 0x40
|
||||
bss = 0x80
|
||||
permW = 0x80000000
|
||||
)
|
||||
ch := sect.Characteristics
|
||||
switch {
|
||||
case ch&text != 0:
|
||||
sym.Code = 'T'
|
||||
case ch&data != 0:
|
||||
if ch&permW == 0 {
|
||||
sym.Code = 'R'
|
||||
} else {
|
||||
sym.Code = 'D'
|
||||
}
|
||||
case ch&bss != 0:
|
||||
sym.Code = 'B'
|
||||
}
|
||||
sym.Addr += imageBase + uint64(sect.VirtualAddress)
|
||||
}
|
||||
syms = append(syms, sym)
|
||||
addrs = append(addrs, sym.Addr)
|
||||
}
|
||||
|
||||
sort.Sort(uint64s(addrs))
|
||||
for i := range syms {
|
||||
j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr })
|
||||
if j < len(addrs) {
|
||||
syms[i].Size = int64(addrs[j] - syms[i].Addr)
|
||||
}
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
func (f *peFile) pcln_scan() (candidates []PclntabCandidate, err error) {
|
||||
var imageBase uint64
|
||||
switch oh := f.pe.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
imageBase = uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
imageBase = oh.ImageBase
|
||||
default:
|
||||
return nil, fmt.Errorf("pe file format not recognized")
|
||||
}
|
||||
|
||||
// 1) Locate pclntab via symbols (standard way)
|
||||
foundpcln := false
|
||||
var pclntab []byte
|
||||
|
||||
if pclntab, err = loadPETable(f.pe, "runtime.pclntab", "runtime.epclntab"); err == nil {
|
||||
foundpcln = true
|
||||
} else {
|
||||
// We didn't find the symbols, so look for the names used in 1.3 and earlier.
|
||||
// TODO: Remove code looking for the old symbols when we no longer care about 1.3.
|
||||
var err2 error
|
||||
if pclntab, err2 = loadPETable(f.pe, "pclntab", "epclntab"); err2 == nil {
|
||||
foundpcln = true
|
||||
}
|
||||
}
|
||||
|
||||
// 2) if not found, byte scan for it
|
||||
ExitScan:
|
||||
for _, sec := range f.pe.Sections {
|
||||
// malware can split the pclntab across multiple sections, re-merge
|
||||
data := f.pe.DataAfterSection(sec.Name)
|
||||
|
||||
if !foundpcln {
|
||||
// https://github.com/golang/go/blob/2cb9042dc2d5fdf6013305a077d013dbbfbaac06/src/debug/gosym/pclntab.go#L172
|
||||
pclntab_sigs := [][]byte{[]byte("\xFB\xFF\xFF\xFF\x00\x00"), []byte("\xFA\xFF\xFF\xFF\x00\x00"), []byte("\xF0\xFF\xFF\xFF\x00\x00"),
|
||||
[]byte("\xFF\xFF\xFF\xFB\x00\x00"), []byte("\xFF\xFF\xFF\xFA\x00\x00"), []byte("\xFF\xFF\xFF\xF0\x00\x00")}
|
||||
matches := findAllOccurrences(data, pclntab_sigs)
|
||||
for _, pclntab_idx := range matches {
|
||||
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
|
||||
pclntab = data[pclntab_idx:]
|
||||
|
||||
var candidate PclntabCandidate
|
||||
candidate.pclntab = pclntab
|
||||
candidate.secStart = imageBase + uint64(sec.VirtualAddress)
|
||||
candidate.pclntabVA = candidate.secStart + uint64(pclntab_idx)
|
||||
|
||||
candidates = append(candidates, candidate)
|
||||
// we must scan all signature for all sections. DO NOT BREAK
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 3) if we found it earlier, figure out which section base to return (might be wrong for packed things)
|
||||
pclntab_idx := bytes.Index(data, pclntab)
|
||||
if pclntab_idx != -1 && pclntab_idx < int(sec.Size) {
|
||||
var candidate PclntabCandidate
|
||||
candidate.pclntab = pclntab
|
||||
candidate.secStart = imageBase + uint64(sec.VirtualAddress)
|
||||
candidate.pclntabVA = candidate.secStart + uint64(pclntab_idx)
|
||||
|
||||
candidates = append(candidates, candidate)
|
||||
break ExitScan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (f *peFile) pcln() (candidates []PclntabCandidate, err error) {
|
||||
candidates, err = f.pcln_scan()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4) symtab is completely optional, but try to find it
|
||||
var symtab []byte
|
||||
if symtab, err = loadPETable(f.pe, "runtime.symtab", "runtime.esymtab"); err != nil {
|
||||
symtab, err = loadPETable(f.pe, "symtab", "esymtab")
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for _, c := range candidates {
|
||||
c.symtab = symtab
|
||||
}
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (f *peFile) moduledata_scan(pclntabVA uint64, is64bit bool, littleendian bool, ignorelist []uint64) (secStart uint64, moduledataRVA uint64, moduledata []byte, err error) {
|
||||
var imageBase uint64
|
||||
switch oh := f.pe.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
imageBase = uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
imageBase = oh.ImageBase
|
||||
default:
|
||||
return 0, 0, nil, fmt.Errorf("pe file format not recognized")
|
||||
}
|
||||
|
||||
foundmodule := false
|
||||
foundsec := false
|
||||
var moduledata_idx int = 0
|
||||
|
||||
// first type to find via symbols as per normal
|
||||
if sym, err := findPESymbol(f.pe, "runtime.firstmoduledata"); err == nil {
|
||||
if uint32(sym.SectionNumber) <= uint32(len(f.pe.Sections)) {
|
||||
sect := f.pe.Sections[sym.SectionNumber-1]
|
||||
data, err := sect.Data()
|
||||
if err == nil && sym.Value < uint32(len(data)) {
|
||||
moduledata = data[sym.Value:]
|
||||
foundmodule = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: do we want to handle legacy symbols??
|
||||
}
|
||||
|
||||
scan:
|
||||
for _, sec := range f.pe.Sections {
|
||||
// malware can split the pclntab across multiple sections, re-merge
|
||||
data := f.pe.DataAfterSection(sec.Name)
|
||||
|
||||
if !foundmodule {
|
||||
// fall back to scanning for structure using address of pclntab, which is first value in struc
|
||||
var pclntabVA_bytes []byte
|
||||
if is64bit {
|
||||
pclntabVA_bytes = make([]byte, 8)
|
||||
if littleendian {
|
||||
binary.LittleEndian.PutUint64(pclntabVA_bytes, pclntabVA)
|
||||
} else {
|
||||
binary.BigEndian.PutUint64(pclntabVA_bytes, pclntabVA)
|
||||
}
|
||||
} else {
|
||||
pclntabVA_bytes = make([]byte, 4)
|
||||
if littleendian {
|
||||
binary.LittleEndian.PutUint32(pclntabVA_bytes, uint32(pclntabVA))
|
||||
} else {
|
||||
binary.BigEndian.PutUint32(pclntabVA_bytes, uint32(pclntabVA))
|
||||
}
|
||||
}
|
||||
|
||||
moduledata_idx = bytes.Index(data, pclntabVA_bytes)
|
||||
if moduledata_idx != -1 && moduledata_idx < int(sec.Size) {
|
||||
moduledata = data[moduledata_idx:]
|
||||
secStart = imageBase + uint64(sec.VirtualAddress)
|
||||
|
||||
// optionally consult ignore list, to skip past previous (bad) scan results
|
||||
if ignorelist != nil {
|
||||
for _, ignore := range ignorelist {
|
||||
if ignore == secStart+uint64(moduledata_idx) {
|
||||
continue scan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundsec = true
|
||||
foundmodule = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// locate the VA of the already found data
|
||||
moduledata_idx = bytes.Index(data, moduledata)
|
||||
if moduledata_idx != -1 && moduledata_idx < int(sec.Size) {
|
||||
secStart = imageBase + uint64(sec.VirtualAddress)
|
||||
foundsec = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundmodule {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata could not be located")
|
||||
}
|
||||
|
||||
if !foundsec {
|
||||
return 0, 0, nil, fmt.Errorf("moduledata containing section could not be located")
|
||||
}
|
||||
|
||||
return secStart, secStart + uint64(moduledata_idx), moduledata, nil
|
||||
}
|
||||
|
||||
func (f *peFile) text() (textStart uint64, text []byte, err error) {
|
||||
var imageBase uint64
|
||||
switch oh := f.pe.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
imageBase = uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
imageBase = oh.ImageBase
|
||||
default:
|
||||
return 0, nil, fmt.Errorf("pe file format not recognized")
|
||||
}
|
||||
sect := f.pe.Section(".text")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf("text section not found")
|
||||
}
|
||||
textStart = imageBase + uint64(sect.VirtualAddress)
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *peFile) rdata() (textStart uint64, text []byte, err error) {
|
||||
var imageBase uint64
|
||||
switch oh := f.pe.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
imageBase = uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
imageBase = oh.ImageBase
|
||||
default:
|
||||
return 0, nil, fmt.Errorf("pe file format not recognized")
|
||||
}
|
||||
sect := f.pe.Section(".rdata")
|
||||
if sect == nil {
|
||||
return 0, nil, fmt.Errorf("rdata section not found")
|
||||
}
|
||||
textStart = imageBase + uint64(sect.VirtualAddress)
|
||||
text, err = sect.Data()
|
||||
return
|
||||
}
|
||||
|
||||
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
|
||||
for _, s := range f.Symbols {
|
||||
if s.Name != name {
|
||||
continue
|
||||
}
|
||||
if s.SectionNumber <= 0 {
|
||||
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
|
||||
}
|
||||
if len(f.Sections) < int(s.SectionNumber) {
|
||||
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no %s symbol found", name)
|
||||
}
|
||||
|
||||
func loadPETable(f *pe.File, sname, ename string) ([]byte, error) {
|
||||
ssym, err := findPESymbol(f, sname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
esym, err := findPESymbol(f, ename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ssym.SectionNumber != esym.SectionNumber {
|
||||
return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename)
|
||||
}
|
||||
|
||||
if uint32(ssym.SectionNumber) > uint32(len(f.Sections)) {
|
||||
return nil, fmt.Errorf("pclntab symbol section index out of range")
|
||||
}
|
||||
|
||||
sect := f.Sections[ssym.SectionNumber-1]
|
||||
data, err := sect.Data()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ssym.Value > esym.Value || esym.Value > uint32(len(data)) {
|
||||
return nil, fmt.Errorf("pclntab symbols are malformed")
|
||||
}
|
||||
|
||||
return data[ssym.Value:esym.Value], nil
|
||||
}
|
||||
|
||||
func (f *peFile) goarch() string {
|
||||
switch f.pe.Machine {
|
||||
case pe.IMAGE_FILE_MACHINE_I386:
|
||||
return "386"
|
||||
case pe.IMAGE_FILE_MACHINE_AMD64:
|
||||
return "amd64"
|
||||
case pe.IMAGE_FILE_MACHINE_ARMNT:
|
||||
return "arm"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (f *peFile) loadAddress() (uint64, error) {
|
||||
return 0, fmt.Errorf("unknown load address")
|
||||
}
|
||||
|
||||
func (f *peFile) dwarf() (*dwarf.Data, error) {
|
||||
return f.pe.DWARF()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user