diff --git a/debug/gosym/pclntab.go b/debug/gosym/pclntab.go index ce36b07..f0890f0 100644 --- a/debug/gosym/pclntab.go +++ b/debug/gosym/pclntab.go @@ -66,6 +66,7 @@ func (v version) String() string { type LineTable struct { Data []byte PC uint64 + GofuncVA uint64 Line int // This mutex is used to keep parsing of pclntab synchronous. @@ -172,8 +173,8 @@ func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 { // 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)} +func NewLineTable(data []byte, text uint64, gofunc uint64) *LineTable { + return &LineTable{Data: data, PC: text, GofuncVA: gofunc, Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)} } // Go 1.2 symbol table format. @@ -366,6 +367,7 @@ func (t *LineTable) go12Funcs() []Func { info := t.funcData(uint32(i)) f.LineTable = t f.FrameSize = int(info.deferreturn()) + f.FuncData = info syms[i] = Sym{ Value: f.Entry, Type: 'T', @@ -515,13 +517,15 @@ 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) Num_pcdata() uint32 { return f.field(7) } func (f funcData) cuOffset() uint32 { return f.field(8) } +func (f funcData) Num_funcdata() uint32 { return f.field(10) } // 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 { + if n == 0 || n > 10 { panic("bad funcdata field") } // In Go 1.18, the first field of _func changed @@ -531,8 +535,14 @@ func (f funcData) field(n uint32) uint32 { sz0 = 4 } off := sz0 + (n-1)*4 // subsequent fields are 4 bytes each - data := f.data[off:] - return f.t.Binary.Uint32(data) + + if n == 10 { // except for the last 4 fields which are 1 byte each + off = off + 3 // we want the last byte + return uint32(f.data[off]) + } else { + data := f.data[off:] + return f.t.Binary.Uint32(data) + } } // step advances to the next pc, value pair in the encoded table. diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index 7014a06..60bf0a5 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -128,16 +128,233 @@ func (s *Sym) BaseName() string { return s.Name } +// go v1.16-v1.18 +type inlinedCall_v116 struct { + parent int16 + funcId uint8 + _pad uint8 + file int32 + line int32 + func_ int32 + parentPc int32 +} + +// go v.1.20+ +type inlinedCall_v120 struct { + funcId uint8 + _pad [3]uint8 + nameOff int32 + parentPc int32 + startLine int32 +} + +const ( + MAX_TREE_SIZE = 4096 + size_inlinedCall_v116 = 20 + size_inlinedCall_v120 = 16 + FUNCID_MAX = 22 // funcID maximum value +) + +// An InlinedCall collects information about a function that has been inlined as well as its parent +type InlinedCall struct { + Funcname string + ParentName string + CallingPc uint64 + ParentEntry uint64 + Data []byte +} + // 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 + 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 + FuncData funcData + InlinedList []InlinedCall + Obj *Obj +} + +const ( + PCDATA_InlTreeIndex = 2 + FUNCDATA_InlTree = 3 +) + +func (f *Func) HasInline() uint32 { + npcdata := int(f.FuncData.Num_pcdata()) + nfuncdata := int(f.FuncData.Num_funcdata()) + + // check the relevant index exists + if nfuncdata <= FUNCDATA_InlTree { + return 0xffff + } + + // get the size of runtime_func actual fields + sz0 := f.LineTable.Ptrsize + if f.LineTable.Version >= ver118 { + sz0 = 4 + } + + // calculate where funcdata array begins + func_hdr_size := int(sz0) + (4 * 10) // sz of first elt + size of remaining elts + pcdata_size := 4 * npcdata // elts in pcdata[npcdata] are 4 bytes each + funcdata_offset := func_hdr_size + pcdata_size + + // isolate the funcdata array + funcdata_size := 4 * nfuncdata + funcdata_raw := f.FuncData.data[funcdata_offset:(funcdata_offset + funcdata_size)] + if len(funcdata_raw) != funcdata_size { + fmt.Printf("wanted %d bytes for uint32_t funcdata[nfuncdata], got %d\n", funcdata_size, len(funcdata_raw)) + return 0xffff + } + + // get the actual inline data value + funcdata_InlTree := f.LineTable.Binary.Uint32(funcdata_raw[4*FUNCDATA_InlTree:]) + + // check if the value is valid + if funcdata_InlTree == ^uint32(0) { + return 0xffff + } + + return funcdata_InlTree +} + +func isValidFuncID(data []byte) bool { + + // TODO -- currently only accepts "FuncIDNormal" + // We may want to include other types. + if data[0] != 0 { + return false + } + + i := 1 + for i < 4 { + if data[i] != 0 { + return false + } + i += 1 + } + + return true +} + +// validate that calling PC falls within calling function +func isValidPC(data []byte, f *Func) (bool, int32) { + var pc int32 + var pc_address uint64 + + // convert bytes to int32 + // TODO -- see isValidFuncName() + err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &pc) + if err != nil { + fmt.Println(err) + return false, -1 + } + pc_address = uint64(pc) + f.Entry + if (pc_address <= f.End) && (pc_address >= f.Entry) { + return true, pc + } + + return false, -1 +} + +// TODO -- pull out binary converter to its own func for reuse +// TODO -- check for little vs big endian +func isValidFuncName(data []byte, f *Func) (bool, string) { + var nameOff int32 + + err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &nameOff) + if err != nil { + fmt.Println(err) + return false, "" + } + + // check that name offset falls within func name table boundaries + funcNameTable := f.LineTable.funcnametab + if nameOff < int32(len(funcNameTable)) { + i := nameOff + for i < int32(len(funcNameTable)) { + // get str len by iterating until we hit a null byte + if funcNameTable[i] == '\000' { + break + } + i += 1 + } + + name := string(funcNameTable[nameOff:i]) + return true, name + } + return false, "" +} + +func (f *Func) iterateInline_v116(tree []byte) []InlinedCall { + var inlineList []InlinedCall + fmt.Println("\tinside version116. BAD.") + return inlineList +} + +func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { + var inlineList []InlinedCall + + // check there are enough bytes for an inlinedCall struct + off := 0 + // iterate until we hit invalid data + // that indicates we've read this function's entire inline tree + for len(tree)-off >= size_inlinedCall_v120 { + // get elt bytes + elt_raw := tree[off : off+size_inlinedCall_v120] + + // verify funcId and padding look normal + if !isValidFuncID(elt_raw[:4]) { + break + } + // verify calling PC exists within parent func bounds + is_valid_pc, pc := isValidPC(elt_raw[8:12], f) + if !is_valid_pc { + break + } + // resolve name + is_valid_fname, fname := isValidFuncName(elt_raw[4:8], f) + if !is_valid_fname { + break + } + // create InlinedCall object + inlineList = append(inlineList, InlinedCall{ + Funcname: fname, + ParentName: f.Name, + CallingPc: uint64(pc), + ParentEntry: f.Entry, + Data: elt_raw, + }) + // add obj to InlineList + off = off + size_inlinedCall_v120 + } + return inlineList +} + +// return array of inlined functions inside f or nil +func (f *Func) GetInlinedCalls(data []byte) { + var inlList []InlinedCall + + // get size of inlined struct based on version + if f.LineTable.Version >= ver118 { + inlList = f.iterateInline_v120(data) + } else { + inlList = f.iterateInline_v116(data) + } + + for _, elt := range inlList { + f.InlinedList = append(f.InlinedList, InlinedCall{ + Funcname: elt.Funcname, + ParentName: elt.ParentName, + CallingPc: elt.CallingPc, + ParentEntry: elt.ParentEntry, + Data: elt.Data, + }) + } } // An Obj represents a collection of functions in a symbol table. diff --git a/doc/inlinedFunctions.md b/doc/inlinedFunctions.md new file mode 100644 index 0000000..9a65746 --- /dev/null +++ b/doc/inlinedFunctions.md @@ -0,0 +1,94 @@ +# Inlined Function Identification + +This file describes the process of identifying functions that have been inlined by the Go compiler. +It also details how this process is implemented in GoReSym and lists known TODOs. + +## Finding []runtime__inlinedCall + +These steps calculate where to find the inline tree for a function `f` . +The inline tree holds information about any and all functions that were inlined into `f` by the Go compiler. +The documentation implies that each function that contains inlined functions will have its own distinct tree. + +1. Choose a function `f` +2. Get `funcData` for `f`. +3. Check whether `funcData.funcdata[FUNCDATA_InlTree]` exists (want `funcData.nfuncdata` >= `FUNCDATA_InlTree`) +4. Check whether `funcData.funcdata[FUNCDATA_InlTree]` is valid (want `funcData.funcdata[FUNCDATA_InlTree]` != `^uint32(0)`) +5. Save `funcData.funcdata[FUNCDATA_InlTree]` -- this is the inline tree offset for `f` +6. Get `go:func.*` via `moduledata`. (there are other ways but this is the least complicated) +7. Adjust `go:func.*` from absolute address to file offset by subtracting the preferred base address (in file header). `go:func.* -= baseAddress` +9. Go to inline tree. `InlineTreeAddress` = `go:func.*` + `funcData.funcdata[FUNCDATA_InlTree]`. This is an offset relative to the start of the binary file because we adjusted `go:func.*` in step 7 above. + + *NOTE: the inline tree and `go:func.*` addresses may be earlier in the binary than `pclntab`* + Therefore whatever component resolves inline functions MUST have access to the full file. + +## Validating inline tree entries + +We iterate over the file bytes from the `InlineTreeAddress` (N.B. file bytes in toto, not bytes in `pclntab`). For each iteration, we grab enough bytes to fill a single `runtime__inlinedCall` instance. Validate its fields. If any validation check fails or there are not enough bytes to fill the struct, assume that we have reached the end of the tree. Break. Return results. + +``` +Start at InlineTreeAddress. +While there are at least sizeof(runtime__inlinedCall) bytes not yet checked: + Get sizeof(runtime__inlinedCall) bytes as potentialCall + Check potentialCall.funcID + - funcID must be 0 (i.e. "normal") + - the subsequent padding bytes must also be 0 (number of pad bytes depends on Go version) + - if these fail, break + Check potentialCall.parentPc + - get parentFunction.Entry (aka start offset) (we have this data in the funcData used to locate the inline tree) + - get parentFunction.End (aka end offset) + - potentialCall.parentPc + parentFunction.Entry must be less than parentFunction.End + - if parentPc falls beyond the end of parentFunction, break + Check potentialCall.name (the field name varies between Go versions) + - get pcHeader.funcNameOffset + - get size in bytes of function name table + - pcHeader.funcNameOffset + potentialCall.name must fall within bounds of the function name table + - [NOT IMPLEMENTED] first char must be ASCII, previous char should be 0 (null-terminator) + - if name offset is invalid, break + Save data from the runtime__inlinedCall struct into a version-agnostic InlinedCall object + Add the new InlinedCall object to array of found InlinedCall objects + Move forward by sizeof(runtime_inlinedCall) + +Return array of InlinedCall objects + +``` + +## Known issues + +### Not implemented yet for Go v1.11-1.18 + +### Only tested on ELF format + +### Saving inlined function names + +Currently we manually calculate the size of the string to creat the slice. There's gotta be a better way to save the string and probably some extra validation we could to make sure that the func name offset points to the start of a string. + +### Only processing inlined calls where funcID==0 (normal function) + +Haven't found a great description of the funcID types. We may want to include more than just normal functions. We might also find that each inline tree ends with inline info for a type. If we can find a description of the inline tree or trees section as a whole, then we might be able to use this as a pattern to separate where inline trees begin/end. + +### How to calculate size of inline tree for a given `f`. + +Right now we start at a function's inline tree base and +process inline call data until we hit invalid data. If two inline trees for functions `f` and `j` are next to each other +with no buffer then `j`'s tree will be mistakenly included in `f`'s tree. The functions that were inlined into `j` +will be listed twice as inlined inside `f` as well as `j`. + +Another heuristic to help could be checking the tree bases for all functions with inline data and stopping the inline struct iteration when we reach another function's tree base. + +Haven't found any overview of an "inlined data section". Either finding one or walking through the compiler steps to build one would be useful. + +### Using pcdata + +We don't currently use `funcData.pcdata[PCDATA_InlineTreeIndex]`. +Use `funcData.pcdata[PCDATA_InlineTreeIndex]` + `pcHeader.pctabOffset` to go to relevant offset in `pcdata`. +(N.B. Some docs call `pcdata` the `pctab`. These are distinct from `pclntab`.) +The inline tree index could be used to check whether any given PC in a function kicks off inlined function instructions. +Since we want every inlined function and are not iterating over every PC in every function, we're not currently using this. HOWEVER. +This info might be helpful in determining how many functions were inlined into `f`. We would then be able to separate inline trees. + + +## References + +* [pclntab structs reference](https://github.com/elastic/otel-profiling-agent/blob/main/docs/gopclntab.md) +* [adding inline functions for golang debugger](https://developers.redhat.com/articles/2024/04/03/how-add-debug-support-go-stripped-binaries) +* [how and why inlining with source examples](https://dave.cheney.net/2020/04/25/inlining-optimisations-in-go) diff --git a/main.go b/main.go index b241c24..f0634a4 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/mandiant/GoReSym/buildinfo" "github.com/mandiant/GoReSym/objfile" "github.com/mandiant/GoReSym/runtime/debug" + "github.com/mandiant/GoReSym/debug/gosym" ) func isStdPackage(pkg string) bool { @@ -49,6 +50,7 @@ type FuncMetadata struct { End uint64 PackageName string FullName string + InlinedList []gosym.InlinedCall } type ExtractMetadata struct { @@ -187,8 +189,9 @@ func main_impl(fileName string, printStdPkgs bool, printFilePaths bool, printTyp var knownPclntabVA = uint64(0) var knownGoTextBase = uint64(0) + var knownGofuncVA = uint64(0) restartParseWithRealTextBase: - tabs, err := file.PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase) + tabs, err := file.PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase, knownGofuncVA) if err != nil { return ExtractMetadata{}, fmt.Errorf("failed to read pclntab: %w", err) } @@ -246,6 +249,7 @@ restartParseWithRealTextBase: // assign real base and restart pclntab parsing with correct VAs! knownGoTextBase = tmpModData.TextVA knownPclntabVA = tab.PclntabVA + knownGofuncVA = tmpModData.Gofunc goto restartParseWithRealTextBase } @@ -302,10 +306,10 @@ restartParseWithRealTextBase: End: elem.End, PackageName: elem.PackageName(), FullName: elem.Name, + InlinedList: elem.InlinedList, }) } } - return extractMetadata, nil } diff --git a/objfile/disasm.go b/objfile/disasm.go index 7641ac4..547030e 100644 --- a/objfile/disasm.go +++ b/objfile/disasm.go @@ -48,7 +48,7 @@ func (e *Entry) Disasm() (*Disasm, error) { return nil, err } - pclns, err := e.PCLineTable("", 0, 0) + pclns, err := e.PCLineTable("", 0, 0, 0) if err != nil { return nil, err } diff --git a/objfile/internals.go b/objfile/internals.go index a65692f..99d2bde 100644 --- a/objfile/internals.go +++ b/objfile/internals.go @@ -152,6 +152,7 @@ type ModuleData12_r15_r16_64 struct { End pvoid64 Gcdata pvoid64 Gcbss pvoid64 + Gofunc pvoid64 Typelinks GoSlice64 @@ -197,6 +198,7 @@ type ModuleData12_r15_r16_32 struct { End pvoid32 Gcdata pvoid32 Gcbss pvoid32 + Gofunc pvoid64 Typelinks GoSlice32 @@ -242,6 +244,7 @@ type ModuleData12_r17_64 struct { End pvoid64 Gcdata pvoid64 Gcbss pvoid64 + Gofunc pvoid64 Types pvoid64 Etypes pvoid64 @@ -292,6 +295,7 @@ type ModuleData12_r17_32 struct { End pvoid32 Gcdata pvoid32 Gcbss pvoid32 + Gofunc pvoid64 Types pvoid32 Etypes pvoid32 @@ -341,6 +345,7 @@ type ModuleData12_64 struct { End pvoid64 Gcdata pvoid64 Gcbss pvoid64 + Gofunc pvoid64 Types pvoid64 Etypes pvoid64 Textsectmap GoSlice64 @@ -392,6 +397,7 @@ type ModuleData12_32 struct { End pvoid32 Gcdata pvoid32 Gcbss pvoid32 + Gofunc pvoid64 Types pvoid32 Etypes pvoid32 Textsectmap GoSlice32 @@ -447,6 +453,7 @@ type ModuleData116_64 struct { End pvoid64 Gcdata pvoid64 Gcbss pvoid64 + Gofunc pvoid64 Types pvoid64 Etypes pvoid64 Textsectmap GoSlice64 @@ -502,6 +509,7 @@ type ModuleData116_32 struct { End pvoid32 Gcdata pvoid32 Gcbss pvoid32 + Gofunc pvoid64 Types pvoid32 Etypes pvoid32 Textsectmap GoSlice32 @@ -1301,6 +1309,7 @@ type ModuleData struct { ETypes uint64 // points to end of type information Typelinks GoSlice64 // points to metadata about offsets into types for structures and other types ITablinks GoSlice64 // points to metadata about offsets into types for interfaces + Gofunc uint64 // offset used for calculating inline trees // Some versions of go with 1.2 moduledata use a slice instead of the types + offset typelinks list LegacyTypes GoSlice64 diff --git a/objfile/objfile.go b/objfile/objfile.go index dfb5ba1..83d31c8 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -24,6 +24,10 @@ import ( "github.com/mandiant/GoReSym/debug/gosym" ) +const ( + MAX_TREE_SIZE = 4096 +) + type StompMagicCandidate struct { PclntabVa uint64 SuspectedModuleDataVa uint64 @@ -33,6 +37,7 @@ type StompMagicCandidate struct { type PclntabCandidate struct { SecStart uint64 PclntabVA uint64 + GofuncVA uint64 StompMagicCandidateMeta *StompMagicCandidate // some search modes might optimistically try to find moduledata or guess endianess, these hints must match the found moduleData VA later to be considered good candidate Pclntab []byte Symtab []byte // optional @@ -129,8 +134,8 @@ func (f *File) Symbols() ([]Sym, error) { } // previously : func (f *File) PCLineTable() (Liner, error) { -func (f *File) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) ([]PclntabCandidate, error) { - return f.entries[0].PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase) +func (f *File) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64, knownGofuncVA uint64) ([]PclntabCandidate, error) { + return f.entries[0].PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase, knownGofuncVA) } func (f *File) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version string, is64bit bool, littleendian bool) (secStart uint64, moduleData *ModuleData, err error) { @@ -211,7 +216,7 @@ func findAllOccurrences(data []byte, searches [][]byte) []int { } // previously: func (e *Entry) PCLineTable() (Liner, error) -func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) ([]PclntabCandidate, error) { +func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64, knownGofuncVA uint64) ([]PclntabCandidate, error) { // If the raw file implements Liner directly, use that. // Currently, only Go intermediate objects and archives (goobj) use this path. @@ -251,13 +256,41 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known continue } - parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart), versionOverride) + if knownGofuncVA != 0 { + candidate.GofuncVA = knownGofuncVA + } + + parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart, candidate.GofuncVA), versionOverride) if err != nil || parsedTable.Go12line == nil { continue } // the first good one happens to be correct more often than the last candidate.ParsedPclntab = parsedTable + + // add in inline func resolution + // we do it here for access to the Entry object + for i, fn := range candidate.ParsedPclntab.Funcs { + // calc inline data VAs + fnd_InlTree := fn.HasInline() + if fnd_InlTree == 0xffff { + // no inline tree data for this function + continue + } + + // calc where tree starts + treeBaseVA := candidate.GofuncVA + uint64(fnd_InlTree) + + // extract relevant bytes + treeBytes, err := e.raw.read_memory(treeBaseVA, MAX_TREE_SIZE) + if err != nil { + fmt.Errorf("failed to get tree bytes for inline function inside %s\n", fn.Name) + continue + } + + // save results for each function + candidate.ParsedPclntab.Funcs[i].GetInlinedCalls(treeBytes) + } finalCandidates = append(finalCandidates, candidate) atLeastOneGood = true } @@ -351,6 +384,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ETypes = uint64(module.Etypes) moduleData.Typelinks = module.Typelinks moduleData.ITablinks = module.Itablinks + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } else { var module ModuleData121_32 @@ -410,6 +446,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ITablinks.Data = pvoid64(module.Itablinks.Data) moduleData.ITablinks.Len = uint64(module.Itablinks.Len) moduleData.ITablinks.Capacity = uint64(module.Itablinks.Capacity) + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } case "1.20": @@ -466,6 +505,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ETypes = uint64(module.Etypes) moduleData.Typelinks = module.Typelinks moduleData.ITablinks = module.Itablinks + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } else { var module ModuleData120_32 @@ -525,6 +567,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ITablinks.Data = pvoid64(module.Itablinks.Data) moduleData.ITablinks.Len = uint64(module.Itablinks.Len) moduleData.ITablinks.Capacity = uint64(module.Itablinks.Capacity) + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } case "1.18": @@ -581,6 +626,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ETypes = uint64(module.Etypes) moduleData.Typelinks = module.Typelinks moduleData.ITablinks = module.Itablinks + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } else { var module ModuleData118_32 @@ -640,6 +688,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ITablinks.Data = pvoid64(module.Itablinks.Data) moduleData.ITablinks.Len = uint64(module.Itablinks.Len) moduleData.ITablinks.Capacity = uint64(module.Itablinks.Capacity) + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } case "1.16": @@ -674,6 +725,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ETypes = uint64(module.Etypes) moduleData.Typelinks = module.Typelinks moduleData.ITablinks = module.Itablinks + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } else { var module ModuleData116_32 @@ -711,6 +765,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ITablinks.Data = pvoid64(module.Itablinks.Data) moduleData.ITablinks.Len = uint64(module.Itablinks.Len) moduleData.ITablinks.Capacity = uint64(module.Itablinks.Capacity) + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } @@ -817,6 +874,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ETypes = uint64(module.Etypes) moduleData.Typelinks = module.Typelinks moduleData.ITablinks = module.Itablinks + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } else { var module ModuleData12_r17_32 @@ -854,6 +914,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ITablinks.Data = pvoid64(module.Itablinks.Data) moduleData.ITablinks.Len = uint64(module.Itablinks.Len) moduleData.ITablinks.Capacity = uint64(module.Itablinks.Capacity) + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } case "1.8": @@ -902,6 +965,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ETypes = uint64(module.Etypes) moduleData.Typelinks = module.Typelinks moduleData.ITablinks = module.Itablinks + + moduleData.Gofunc = uint64(module.Gofunc) + return secStart, moduleData, err } else { var module ModuleData12_32 @@ -939,6 +1005,8 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version moduleData.ITablinks.Data = pvoid64(module.Itablinks.Data) moduleData.ITablinks.Len = uint64(module.Itablinks.Len) moduleData.ITablinks.Capacity = uint64(module.Itablinks.Capacity) + + moduleData.Gofunc = uint64(module.Gofunc) return secStart, moduleData, err } }