From 1a342bc53222090a0b28640072223aebfaf3c1d8 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 5 Jun 2024 17:33:52 +0000 Subject: [PATCH 01/30] Update main.go with testing adjustments for inline func ID --- main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index b241c24..b8aac7f 100644 --- a/main.go +++ b/main.go @@ -286,6 +286,8 @@ restartParseWithRealTextBase: } } + fileData, _ := os.ReadFile(fileName) + // TODO -- use error or remove for _, elem := range finalTab.ParsedPclntab.Funcs { if isStdPackage(elem.PackageName()) { if printStdPkgs { @@ -297,6 +299,7 @@ restartParseWithRealTextBase: }) } } else { + elem.CheckInline(moduleData.Gofunc, fileData) extractMetadata.UserFunctions = append(extractMetadata.UserFunctions, FuncMetadata{ Start: elem.Entry, End: elem.End, @@ -305,7 +308,8 @@ restartParseWithRealTextBase: }) } } - + // TODO -- remove! + os.Exit(0) return extractMetadata, nil } From b298ea42ba5d717e5c10b3b10db83f5f59cf4011 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 5 Jun 2024 17:35:15 +0000 Subject: [PATCH 02/30] initial commit for inline func id changes --- debug/gosym/pclntab.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/debug/gosym/pclntab.go b/debug/gosym/pclntab.go index ce36b07..6966888 100644 --- a/debug/gosym/pclntab.go +++ b/debug/gosym/pclntab.go @@ -366,6 +366,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 +516,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 +534,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. From 682a59f9f8c3d5939e090acbc16c71743cdf78f7 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 5 Jun 2024 17:36:06 +0000 Subject: [PATCH 03/30] initial commit for inline func changes --- debug/gosym/symtab.go | 216 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index 7014a06..12189ee 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -137,9 +137,225 @@ type Func struct { Locals []*Sym // nil for Go 1.3 and later binaries FrameSize int LineTable *LineTable + FuncData funcData Obj *Obj } +const ( + PCDATA_InlTreeIndex = 2 + FUNCDATA_InlTree = 3 +) + +func (f *Func) hasInline() (uint32, uint32) { + npcdata := int(f.FuncData.Num_pcdata()) + nfuncdata := int(f.FuncData.Num_funcdata()) + + // check the relevant indices exist + if (npcdata <= PCDATA_InlTreeIndex) || (nfuncdata <= FUNCDATA_InlTree) { + return 0, 0 + } + + // get the size of runtime_func actual fields + sz0 := f.LineTable.Ptrsize + if f.LineTable.Version >= ver118 { + sz0 = 4 + } + + func_hdr_size := sz0 + (4*10) // sz of first elt + size of remaining elts + pcdata_size := 4 * npcdata // elts in pcdata[npcdata] are 4 bytes each + funcdata_size := 4 * nfuncdata // ^ditto + + func_pseudofields := f.FuncData.data[func_hdr_size:] // chop off actual fields + pcdata_raw := func_pseudofields[:pcdata_size] // isolate just the pcdata table bytes + funcdata_raw := func_pseudofields[pcdata_size:] // ^ditto for funcdata table bytes + if len(funcdata_raw) != funcdata_size { + fmt.Errorf("wanted %d bytes for uint32_t funcdata[nfuncdata], got %d\n", funcdata_size, len(funcdata_raw)) + return 0,0 + } + + // get the actual inline offsets + pcdata_InlIndex := f.LineTable.Binary.Uint32(pcdata_raw[4*PCDATA_InlTreeIndex:]) + funcdata_InlTree := f.LineTable.Binary.Uint32(funcdata_raw[4*FUNCDATA_InlTree:]) + + // check if these indices are ^uint32(0) + if pcdata_InlIndex == ^uint32(0) || funcdata_InlTree == ^uint32(0) { + return 0, 0 + } + + return pcdata_InlIndex, funcdata_InlTree +} + +// 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 ( + size_inlinedCall_v116 = 24 + size_inlinedCall_v120 = 20 + FUNCID_MAX = 22 // funcID maximum value +) + +type InlinedCall struct { + func_name string + parent_name string + parentPc int32 + parentEntry int32 + data []byte +} + +func isValidFuncID(data []byte) bool { + + if data[0] > FUNCID_MAX { + 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 + // TODO -- add check that we're not running off the end of the table + } + + name := string(funcNameTable[nameOff : i ]) + return true, name + } + return false, "" +} + +func (f *Func) iterateInline_v116(Gofunc uint64, tree []byte) []InlinedCall { + var inlineList []InlinedCall + return inlineList +} + +func (f *Func) iterateInline_v120(Gofunc uint64, tree []byte) []InlinedCall { + var inlineList []InlinedCall + + // check there are enough bytes for an inlinedCall struct + off := 0 + for off < (len(tree) - 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]) { + // verify calling PC exists within parent func bounds + is_valid_pc, pc := isValidPC(elt_raw[4:8], f) + if is_valid_pc { + // resolve name + is_valid_fname, fname := isValidFuncName(elt_raw[4:8], f) + if is_valid_fname { + // create InlinedCall object + // add obj to InlineList + fmt.Printf("got valid fname, %s, pc %x\n", fname, pc) + } + } + } + // (define runtime_inlinedCall struct type) + // for each elt: + // get funcnametab + offset + // verify the result falls within funcnametab + // if verify + // check which inlineCall struct to use based on version + // save funcname + // call PC + // line var + // create object and add to array + off = off + size_inlinedCall_v120 + } + return inlineList +} + +// TODO -- ret value isn't meaningful. remove or use. +func (f *Func) CheckInline(Gofunc uint64, filedata []byte) bool { + + Gofunc = Gofunc - 0x400000 + pcdata_InlIndex, funcdata_InlTree := f.hasInline() + if pcdata_InlIndex == 0 && funcdata_InlTree == 0 { + return false + } + fmt.Printf("Gofunc 0x%x, tree 0x%x\n", Gofunc, funcdata_InlTree) + + + treeBase := Gofunc + uint64(funcdata_InlTree) + fmt.Printf("Total len: 0x%x, treebase: 0x%x\n", len(filedata), treeBase) + // TODO -- should be filedata[treeBase : treeEnd] + // .... not sure how to calc end of table? + tree := filedata[treeBase:] + + // get size of inlined struct based on version + if f.LineTable.Version > ver118 { + f.iterateInline_v120(Gofunc, tree) + } else { + f.iterateInline_v116(Gofunc, tree) + } + return true +} + // 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 From b4fc18feec94531f83f8881268f300a99ffb9016 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 5 Jun 2024 19:05:58 -0500 Subject: [PATCH 04/30] Update main.go -- fixed parsing v1.20+ inlining --- main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index b8aac7f..548cab9 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 { @@ -286,6 +288,7 @@ restartParseWithRealTextBase: } } + // TODO -- move var above so we don't have to re-read the whole file fileData, _ := os.ReadFile(fileName) // TODO -- use error or remove for _, elem := range finalTab.ParsedPclntab.Funcs { @@ -299,17 +302,15 @@ restartParseWithRealTextBase: }) } } else { - elem.CheckInline(moduleData.Gofunc, fileData) extractMetadata.UserFunctions = append(extractMetadata.UserFunctions, FuncMetadata{ Start: elem.Entry, End: elem.End, PackageName: elem.PackageName(), FullName: elem.Name, + InlinedList: elem.CheckInline(moduleData.Gofunc, fileData), }) } } - // TODO -- remove! - os.Exit(0) return extractMetadata, nil } From 105e95baf60246eb90ebb62836af709521c85f6e Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 5 Jun 2024 19:07:23 -0500 Subject: [PATCH 05/30] Update symtab.go -- fixed inline Id logic for go v1.20+ --- debug/gosym/symtab.go | 89 +++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index 12189ee..ef6f7eb 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -207,21 +207,23 @@ type inlinedCall_v120 struct { const ( size_inlinedCall_v116 = 24 - size_inlinedCall_v120 = 20 + size_inlinedCall_v120 = 16 FUNCID_MAX = 22 // funcID maximum value ) type InlinedCall struct { - func_name string - parent_name string - parentPc int32 - parentEntry int32 - data []byte + Funcname string + ParentName string + CallingPc uint64 + ParentEntry uint64 + Data []byte } func isValidFuncID(data []byte) bool { - if data[0] > FUNCID_MAX { + // TODO -- currently only accepts "FuncIDNormal" + // We may want to include other types. + if data[0] != 0 { return false } @@ -248,8 +250,8 @@ func isValidPC(data []byte, f *Func) (bool, int32) { fmt.Println(err) return false, -1 } - pc_address = uint64(pc) + f.Entry + //fmt.Printf("checking pc 0x%x (addr 0x%x) within 0x%x - 0x%x\n", pc, pc_address, f.Entry, f.End) if (pc_address <= f.End ) && (pc_address >= f.Entry) { return true, pc } @@ -297,63 +299,66 @@ func (f *Func) iterateInline_v120(Gofunc uint64, tree []byte) []InlinedCall { // check there are enough bytes for an inlinedCall struct off := 0 - for off < (len(tree) - size_inlinedCall_v120) { + // 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]) { - // verify calling PC exists within parent func bounds - is_valid_pc, pc := isValidPC(elt_raw[4:8], f) - if is_valid_pc { - // resolve name - is_valid_fname, fname := isValidFuncName(elt_raw[4:8], f) - if is_valid_fname { - // create InlinedCall object - // add obj to InlineList - fmt.Printf("got valid fname, %s, pc %x\n", fname, pc) - } - } + if !isValidFuncID(elt_raw[ : 4]) { + break } - // (define runtime_inlinedCall struct type) - // for each elt: - // get funcnametab + offset - // verify the result falls within funcnametab - // if verify - // check which inlineCall struct to use based on version - // save funcname - // call PC - // line var - // create object and add to array + // 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 } // TODO -- ret value isn't meaningful. remove or use. -func (f *Func) CheckInline(Gofunc uint64, filedata []byte) bool { +func (f *Func) CheckInline(Gofunc uint64, filedata []byte) []InlinedCall { - Gofunc = Gofunc - 0x400000 - pcdata_InlIndex, funcdata_InlTree := f.hasInline() - if pcdata_InlIndex == 0 && funcdata_InlTree == 0 { - return false + // TODO -- check if a) gofunc is always absolute wrt to preferred address + // and b) what the preferred load address is + baseAddress := uint64(0x400000) + Gofunc = Gofunc - baseAddress + pcdataInlIndex, funcdataInlTree := f.hasInline() + if pcdataInlIndex == 0 && funcdataInlTree == 0 { + return nil } - fmt.Printf("Gofunc 0x%x, tree 0x%x\n", Gofunc, funcdata_InlTree) + //fmt.Printf("Gofunc 0x%x, tree 0x%x\n", Gofunc, funcdata_InlTree) - treeBase := Gofunc + uint64(funcdata_InlTree) - fmt.Printf("Total len: 0x%x, treebase: 0x%x\n", len(filedata), treeBase) + treeBase := Gofunc + uint64(funcdataInlTree) + //fmt.Printf("Total len: 0x%x, treebase: 0x%x\n", len(filedata), treeBase) // TODO -- should be filedata[treeBase : treeEnd] // .... not sure how to calc end of table? tree := filedata[treeBase:] // get size of inlined struct based on version if f.LineTable.Version > ver118 { - f.iterateInline_v120(Gofunc, tree) + return f.iterateInline_v120(Gofunc, tree) } else { - f.iterateInline_v116(Gofunc, tree) + return f.iterateInline_v116(Gofunc, tree) } - return true } // An Obj represents a collection of functions in a symbol table. From 7c3536ddba4cb061619ace900261adcc4f0f8c33 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 5 Jun 2024 19:19:07 -0500 Subject: [PATCH 06/30] fixing size for InlinedCall struct v1.16-1.18 --- debug/gosym/symtab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index ef6f7eb..6ebc0df 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -206,7 +206,7 @@ type inlinedCall_v120 struct { } const ( - size_inlinedCall_v116 = 24 + size_inlinedCall_v116 = 20 size_inlinedCall_v120 = 16 FUNCID_MAX = 22 // funcID maximum value ) From 7b2141521de1405cb02232d317c4d8b4a78fa7c5 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Thu, 6 Jun 2024 11:24:37 -0500 Subject: [PATCH 07/30] Update symtab.go. Func description comment fixed --- debug/gosym/symtab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index 6ebc0df..bfe5a0d 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -333,7 +333,7 @@ func (f *Func) iterateInline_v120(Gofunc uint64, tree []byte) []InlinedCall { return inlineList } -// TODO -- ret value isn't meaningful. remove or use. +// return array of inlined functions inside f or nil func (f *Func) CheckInline(Gofunc uint64, filedata []byte) []InlinedCall { // TODO -- check if a) gofunc is always absolute wrt to preferred address From 7dcddfc1314c8741ee6c9d5e03d3b667a3dbdfaa Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Thu, 6 Jun 2024 11:52:43 -0500 Subject: [PATCH 08/30] Create inlinedFunctions.md -- high-ish level write up on how we currently find inlined functions --- doc/inlinedFunctions.md | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 doc/inlinedFunctions.md diff --git a/doc/inlinedFunctions.md b/doc/inlinedFunctions.md new file mode 100644 index 0000000..ec21f88 --- /dev/null +++ b/doc/inlinedFunctions.md @@ -0,0 +1,49 @@ +# 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]`. + + *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. + +## Resolving a single runtime__inlinedCall + + + +## Known issues + +### 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`. + +### 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) From 41e39abd8cabc5b103d14cd35117861fec7e070e Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Thu, 6 Jun 2024 12:06:24 -0500 Subject: [PATCH 09/30] adding more references --- doc/inlinedFunctions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/inlinedFunctions.md b/doc/inlinedFunctions.md index ec21f88..667ab68 100644 --- a/doc/inlinedFunctions.md +++ b/doc/inlinedFunctions.md @@ -47,3 +47,5 @@ This info might be helpful in determining how many functions were inlined into ` ## 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) From 911a0445851a62ffe62c9b12c7eba1dd5e2366a5 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Thu, 6 Jun 2024 12:43:45 -0500 Subject: [PATCH 10/30] added description of how we validate inline call data --- doc/inlinedFunctions.md | 55 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/doc/inlinedFunctions.md b/doc/inlinedFunctions.md index 667ab68..9d284f5 100644 --- a/doc/inlinedFunctions.md +++ b/doc/inlinedFunctions.md @@ -16,16 +16,55 @@ The documentation implies that each function that contains inlined functions wil 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]`. +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. -## Resolving a single runtime__inlinedCall +## Validating inline tree entries + +We iterate from the InlineTreeAddress, 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. 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 -## Known issues +### 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`. @@ -34,6 +73,10 @@ process inline call data until we hit invalid data. If two inline trees for func 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]`. @@ -46,6 +89,6 @@ This info might be helpful in determining how many functions were inlined into ` ## 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) +* [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) From 35b5d92f777d49ebf541b89f0db808ce898317de Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Thu, 6 Jun 2024 12:45:50 -0500 Subject: [PATCH 11/30] Update inlinedFunctions.md --- doc/inlinedFunctions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/inlinedFunctions.md b/doc/inlinedFunctions.md index 9d284f5..9a65746 100644 --- a/doc/inlinedFunctions.md +++ b/doc/inlinedFunctions.md @@ -16,14 +16,14 @@ The documentation implies that each function that contains inlined functions wil 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. +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 from the InlineTreeAddress, 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. Return results. +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. From 189e50ebc3a08e0505e59e2953338a97b0d908a2 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Fri, 7 Jun 2024 11:27:52 -0500 Subject: [PATCH 12/30] adding exported Gofunc to all ModuleData definitions --- objfile/internals.go | 9 +++++++++ 1 file changed, 9 insertions(+) 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 From f5dc310d26be90a91f6cf7dc0e9dcdd59c567fe3 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Fri, 7 Jun 2024 11:38:24 -0500 Subject: [PATCH 13/30] assigning Gofunc --- objfile/objfile.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/objfile/objfile.go b/objfile/objfile.go index dfb5ba1..a95c529 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -351,6 +351,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 +413,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 +472,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 +534,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 +593,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 +655,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 +692,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 +732,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 +841,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 +881,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 +932,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 +972,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 } } From ad709c6a0d09cca80b683c7b9d7b4ce9e2a61c27 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 10 Jun 2024 13:43:40 -0500 Subject: [PATCH 14/30] moving InlinedList to field in Func -- main.go --- main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main.go b/main.go index 548cab9..2983a07 100644 --- a/main.go +++ b/main.go @@ -288,9 +288,6 @@ restartParseWithRealTextBase: } } - // TODO -- move var above so we don't have to re-read the whole file - fileData, _ := os.ReadFile(fileName) - // TODO -- use error or remove for _, elem := range finalTab.ParsedPclntab.Funcs { if isStdPackage(elem.PackageName()) { if printStdPkgs { @@ -307,7 +304,7 @@ restartParseWithRealTextBase: End: elem.End, PackageName: elem.PackageName(), FullName: elem.Name, - InlinedList: elem.CheckInline(moduleData.Gofunc, fileData), + InlinedList: elem.InlinedList, }) } } From de0de0f390227b85ada17cb7133c1108ddac3893 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 10 Jun 2024 14:00:30 -0500 Subject: [PATCH 15/30] moving InlinedCall struct def --- debug/gosym/symtab.go | 73 ++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index bfe5a0d..560b7d5 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -128,6 +128,42 @@ 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 ( + 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 @@ -138,12 +174,13 @@ type Func struct { FrameSize int LineTable *LineTable FuncData funcData + InlineList []InlinedCall Obj *Obj } const ( PCDATA_InlTreeIndex = 2 - FUNCDATA_InlTree = 3 + FUNCDATA_InlTree = 3 ) func (f *Func) hasInline() (uint32, uint32) { @@ -185,40 +222,6 @@ func (f *Func) hasInline() (uint32, uint32) { return pcdata_InlIndex, funcdata_InlTree } -// 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 ( - size_inlinedCall_v116 = 20 - size_inlinedCall_v120 = 16 - FUNCID_MAX = 22 // funcID maximum value -) - -type InlinedCall struct { - Funcname string - ParentName string - CallingPc uint64 - ParentEntry uint64 - Data []byte -} - func isValidFuncID(data []byte) bool { // TODO -- currently only accepts "FuncIDNormal" From f767801d07d12d6a9ee93c09f2a39b133f185751 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 10 Jun 2024 14:01:32 -0500 Subject: [PATCH 16/30] typo fix --- debug/gosym/symtab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index 560b7d5..ba0cd6e 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -174,7 +174,7 @@ type Func struct { FrameSize int LineTable *LineTable FuncData funcData - InlineList []InlinedCall + InlinedList []InlinedCall Obj *Obj } From a116cfd197b81a64e97900093e13486d46885ff3 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 10 Jun 2024 15:16:22 -0500 Subject: [PATCH 17/30] adding Gofunc var in main --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 2983a07..f0634a4 100644 --- a/main.go +++ b/main.go @@ -189,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) } @@ -248,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 } From 1309938e6219818bbf6c84313eafb81a17849b20 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 10 Jun 2024 15:52:14 -0500 Subject: [PATCH 18/30] adding Gofunc field to Pclntabcandidate struct and as param to New table() -- objfile.go --- objfile/objfile.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/objfile/objfile.go b/objfile/objfile.go index a95c529..b94c0a4 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -33,6 +33,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 +130,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 +212,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,11 +252,16 @@ 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.Gofunc + } + + parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart, candidate.Gofunc), versionOverride) if err != nil || parsedTable.Go12line == nil { continue } + // the first good one happens to be correct more often than the last candidate.ParsedPclntab = parsedTable finalCandidates = append(finalCandidates, candidate) From e11a030731f1ae10eadca28847290bf4a9b2358d Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 10 Jun 2024 15:57:55 -0500 Subject: [PATCH 19/30] typo fix --- objfile/objfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objfile/objfile.go b/objfile/objfile.go index b94c0a4..971ad77 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -253,10 +253,10 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known } if knownGofuncVA != 0 { - candidate.Gofunc + candidate.GofuncVA } - parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart, candidate.Gofunc), versionOverride) + parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart, candidate.GofuncVA), versionOverride) if err != nil || parsedTable.Go12line == nil { continue } From cf6ae24cd2d669ea23eb47afae7b70b7c50076b6 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 24 Jun 2024 13:58:33 -0500 Subject: [PATCH 20/30] Update pclntab.go --- debug/gosym/pclntab.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/debug/gosym/pclntab.go b/debug/gosym/pclntab.go index 6966888..30dece0 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. @@ -367,6 +368,7 @@ func (t *LineTable) go12Funcs() []Func { f.LineTable = t f.FrameSize = int(info.deferreturn()) f.FuncData = info + //f.GetInlinedCalls(info.data, t.GofuncVA) syms[i] = Sym{ Value: f.Entry, Type: 'T', From 015e848ea8f367e2a03076a3b588b20f1ea1d7ce Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 24 Jun 2024 13:59:16 -0500 Subject: [PATCH 21/30] Update symtab.go --- debug/gosym/symtab.go | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index ba0cd6e..dacea47 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -150,6 +150,7 @@ type inlinedCall_v120 struct { } const ( + MAX_TREE_SIZE = 4096 size_inlinedCall_v116 = 20 size_inlinedCall_v120 = 16 FUNCID_MAX = 22 // funcID maximum value @@ -183,12 +184,12 @@ const ( FUNCDATA_InlTree = 3 ) -func (f *Func) hasInline() (uint32, uint32) { +func (f *Func) HasInline() (uint32, uint32) { npcdata := int(f.FuncData.Num_pcdata()) nfuncdata := int(f.FuncData.Num_funcdata()) // check the relevant indices exist - if (npcdata <= PCDATA_InlTreeIndex) || (nfuncdata <= FUNCDATA_InlTree) { + if (npcdata < PCDATA_InlTreeIndex) && (nfuncdata < FUNCDATA_InlTree) { return 0, 0 } @@ -254,7 +255,6 @@ func isValidPC(data []byte, f *Func) (bool, int32) { return false, -1 } pc_address = uint64(pc) + f.Entry - //fmt.Printf("checking pc 0x%x (addr 0x%x) within 0x%x - 0x%x\n", pc, pc_address, f.Entry, f.End) if (pc_address <= f.End ) && (pc_address >= f.Entry) { return true, pc } @@ -283,7 +283,6 @@ func isValidFuncName(data []byte, f *Func) (bool, string) { break } i += 1 - // TODO -- add check that we're not running off the end of the table } name := string(funcNameTable[nameOff : i ]) @@ -292,14 +291,15 @@ func isValidFuncName(data []byte, f *Func) (bool, string) { return false, "" } -func (f *Func) iterateInline_v116(Gofunc uint64, tree []byte) []InlinedCall { +func (f *Func) iterateInline_v116(tree []byte) []InlinedCall { var inlineList []InlinedCall + fmt.Println("\tinside version116. BAD.") return inlineList } -func (f *Func) iterateInline_v120(Gofunc uint64, tree []byte) []InlinedCall { +func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { var inlineList []InlinedCall - + fmt.Println("\t iterating...") // check there are enough bytes for an inlinedCall struct off := 0 // iterate until we hit invalid data @@ -322,6 +322,7 @@ func (f *Func) iterateInline_v120(Gofunc uint64, tree []byte) []InlinedCall { if !is_valid_fname { break } + fmt.Printf("\t inlined func %s (parent %s)\n", fname, f.Name) // create InlinedCall object inlineList = append(inlineList, InlinedCall { Funcname: fname, @@ -337,30 +338,14 @@ func (f *Func) iterateInline_v120(Gofunc uint64, tree []byte) []InlinedCall { } // return array of inlined functions inside f or nil -func (f *Func) CheckInline(Gofunc uint64, filedata []byte) []InlinedCall { +func (f *Func) GetInlinedCalls(data []byte) []InlinedCall { - // TODO -- check if a) gofunc is always absolute wrt to preferred address - // and b) what the preferred load address is - baseAddress := uint64(0x400000) - Gofunc = Gofunc - baseAddress - pcdataInlIndex, funcdataInlTree := f.hasInline() - if pcdataInlIndex == 0 && funcdataInlTree == 0 { - return nil - } - - //fmt.Printf("Gofunc 0x%x, tree 0x%x\n", Gofunc, funcdata_InlTree) - - treeBase := Gofunc + uint64(funcdataInlTree) - //fmt.Printf("Total len: 0x%x, treebase: 0x%x\n", len(filedata), treeBase) - // TODO -- should be filedata[treeBase : treeEnd] - // .... not sure how to calc end of table? - tree := filedata[treeBase:] - + fmt.Println("\tgetting inlined data, version ", f.LineTable.Version) // get size of inlined struct based on version - if f.LineTable.Version > ver118 { - return f.iterateInline_v120(Gofunc, tree) + if f.LineTable.Version >= ver118 { + return f.iterateInline_v120(data) } else { - return f.iterateInline_v116(Gofunc, tree) + return f.iterateInline_v116(data) } } From 46c3b58cf043e5e91cfce90884f0fd2edd34bfc6 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 24 Jun 2024 14:00:15 -0500 Subject: [PATCH 22/30] Update objfile.go --- objfile/objfile.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/objfile/objfile.go b/objfile/objfile.go index 971ad77..ddb18a8 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 @@ -253,7 +257,7 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known } if knownGofuncVA != 0 { - candidate.GofuncVA + candidate.GofuncVA = knownGofuncVA } parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart, candidate.GofuncVA), versionOverride) @@ -261,9 +265,37 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known continue } - // the first good one happens to be correct more often than the last candidate.ParsedPclntab = parsedTable + // add in inline func resolution + for _, fn := range candidate.ParsedPclntab.Funcs { + // calc inline data VAs + pcd_InlIndex, fnd_InlTree := fn.HasInline() + if pcd_InlIndex == 0 && fnd_InlTree == 0 { + // no inline tree data for this function + continue + } + fmt.Println("func ", fn.Name) + + // 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 + inlineCallList := fn.GetInlinedCalls(treeBytes) + fmt.Printf("\t got %d bytes with %d entries\n", len(treeBytes), len(inlineCallList)) + + for _, elt := range inlineCallList { + fmt.Println("\tadding inlined ", elt.Funcname) + fn.InlinedList = append(fn.InlinedList, elt) + } + } finalCandidates = append(finalCandidates, candidate) atLeastOneGood = true } From 52b00a96d4ce51fd523a87707db74ade004f120f Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 24 Jun 2024 14:00:59 -0500 Subject: [PATCH 23/30] Update disasm.go --- objfile/disasm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } From 39b22a5db8c8a619b09ed5750ea9bd27eb48f7ef Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Mon, 24 Jun 2024 14:01:36 -0500 Subject: [PATCH 24/30] Update main.go --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index f0634a4..c26cf4b 100644 --- a/main.go +++ b/main.go @@ -308,6 +308,7 @@ restartParseWithRealTextBase: FullName: elem.Name, InlinedList: elem.InlinedList, }) + fmt.Printf("%s -- inlined list # %d\n", elem.Name, len(elem.InlinedList)) } } return extractMetadata, nil From 86761c0563181497e18934cdfb244dcd0becb800 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Tue, 25 Jun 2024 16:31:37 -0500 Subject: [PATCH 25/30] Update pclntab.go -- copying inline data to InlinedList correctly --- debug/gosym/pclntab.go | 1 - 1 file changed, 1 deletion(-) diff --git a/debug/gosym/pclntab.go b/debug/gosym/pclntab.go index 30dece0..f0890f0 100644 --- a/debug/gosym/pclntab.go +++ b/debug/gosym/pclntab.go @@ -368,7 +368,6 @@ func (t *LineTable) go12Funcs() []Func { f.LineTable = t f.FrameSize = int(info.deferreturn()) f.FuncData = info - //f.GetInlinedCalls(info.data, t.GofuncVA) syms[i] = Sym{ Value: f.Entry, Type: 'T', From 190dbd8c266bf299e9e15de21dd3cc3e2ac5e1ed Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Tue, 25 Jun 2024 16:37:01 -0500 Subject: [PATCH 26/30] Update symtab.go -- correctly adding inlined data to list --- debug/gosym/symtab.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index dacea47..e13a423 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -299,7 +299,7 @@ func (f *Func) iterateInline_v116(tree []byte) []InlinedCall { func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { var inlineList []InlinedCall - fmt.Println("\t iterating...") + // check there are enough bytes for an inlinedCall struct off := 0 // iterate until we hit invalid data @@ -322,7 +322,6 @@ func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { if !is_valid_fname { break } - fmt.Printf("\t inlined func %s (parent %s)\n", fname, f.Name) // create InlinedCall object inlineList = append(inlineList, InlinedCall { Funcname: fname, @@ -338,14 +337,24 @@ func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { } // return array of inlined functions inside f or nil -func (f *Func) GetInlinedCalls(data []byte) []InlinedCall { - - fmt.Println("\tgetting inlined data, version ", f.LineTable.Version) +func (f *Func) GetInlinedCalls(data []byte) { + var inlList []InlinedCall + // get size of inlined struct based on version if f.LineTable.Version >= ver118 { - return f.iterateInline_v120(data) + inlList = f.iterateInline_v120(data) } else { - return f.iterateInline_v116(data) + 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, + }) } } From 1018a1f717ce25ae7fb1af78d00c97a73f55ead5 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Tue, 25 Jun 2024 16:37:53 -0500 Subject: [PATCH 27/30] Update objfile.go -- adding inlined data to candidate Func list correctly --- objfile/objfile.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/objfile/objfile.go b/objfile/objfile.go index ddb18a8..a5f54c9 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -267,15 +267,16 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known // the first good one happens to be correct more often than the last candidate.ParsedPclntab = parsedTable + // add in inline func resolution - for _, fn := range candidate.ParsedPclntab.Funcs { + // we do it here for access to the Entry object + for i, fn := range candidate.ParsedPclntab.Funcs { // calc inline data VAs pcd_InlIndex, fnd_InlTree := fn.HasInline() if pcd_InlIndex == 0 && fnd_InlTree == 0 { // no inline tree data for this function continue } - fmt.Println("func ", fn.Name) // calc where tree starts treeBaseVA := candidate.GofuncVA + uint64(fnd_InlTree) @@ -288,13 +289,7 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known } // save results for each function - inlineCallList := fn.GetInlinedCalls(treeBytes) - fmt.Printf("\t got %d bytes with %d entries\n", len(treeBytes), len(inlineCallList)) - - for _, elt := range inlineCallList { - fmt.Println("\tadding inlined ", elt.Funcname) - fn.InlinedList = append(fn.InlinedList, elt) - } + candidate.ParsedPclntab.Funcs[i].GetInlinedCalls(treeBytes) } finalCandidates = append(finalCandidates, candidate) atLeastOneGood = true From 6c347c1abcfb35651ee5fb5dd9bdaf7ba75ccf1f Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Tue, 25 Jun 2024 16:38:32 -0500 Subject: [PATCH 28/30] Update main.go - removing excess print statements --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index c26cf4b..f0634a4 100644 --- a/main.go +++ b/main.go @@ -308,7 +308,6 @@ restartParseWithRealTextBase: FullName: elem.Name, InlinedList: elem.InlinedList, }) - fmt.Printf("%s -- inlined list # %d\n", elem.Name, len(elem.InlinedList)) } } return extractMetadata, nil From 31e5ef1054522f098da51dace350faef473b6749 Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 26 Jun 2024 16:27:12 -0500 Subject: [PATCH 29/30] Update symtab.go - only calculating offset into runtime.__func.funcdata[] fixes 2 issues that lead to out of bounds indexing. pcdata array may be null and we don't use any of it's values; fix no longer indexes into this array. also we do <= check instead of < when verifying there are enough elements in funcData array. --- debug/gosym/symtab.go | 177 +++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 89 deletions(-) diff --git a/debug/gosym/symtab.go b/debug/gosym/symtab.go index e13a423..60bf0a5 100644 --- a/debug/gosym/symtab.go +++ b/debug/gosym/symtab.go @@ -128,69 +128,68 @@ 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 + 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 + funcId uint8 + _pad [3]uint8 + nameOff int32 + parentPc int32 + startLine int32 } const ( - MAX_TREE_SIZE = 4096 + MAX_TREE_SIZE = 4096 size_inlinedCall_v116 = 20 - size_inlinedCall_v120 = 16 - FUNCID_MAX = 22 // funcID maximum value + 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 + 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 - FuncData funcData + 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 + Obj *Obj } -const ( - PCDATA_InlTreeIndex = 2 - FUNCDATA_InlTree = 3 +const ( + PCDATA_InlTreeIndex = 2 + FUNCDATA_InlTree = 3 ) -func (f *Func) HasInline() (uint32, uint32) { +func (f *Func) HasInline() uint32 { npcdata := int(f.FuncData.Num_pcdata()) - nfuncdata := int(f.FuncData.Num_funcdata()) + nfuncdata := int(f.FuncData.Num_funcdata()) - // check the relevant indices exist - if (npcdata < PCDATA_InlTreeIndex) && (nfuncdata < FUNCDATA_InlTree) { - return 0, 0 + // check the relevant index exists + if nfuncdata <= FUNCDATA_InlTree { + return 0xffff } // get the size of runtime_func actual fields @@ -199,35 +198,35 @@ func (f *Func) HasInline() (uint32, uint32) { sz0 = 4 } - func_hdr_size := sz0 + (4*10) // sz of first elt + size of remaining elts - pcdata_size := 4 * npcdata // elts in pcdata[npcdata] are 4 bytes each - funcdata_size := 4 * nfuncdata // ^ditto - - func_pseudofields := f.FuncData.data[func_hdr_size:] // chop off actual fields - pcdata_raw := func_pseudofields[:pcdata_size] // isolate just the pcdata table bytes - funcdata_raw := func_pseudofields[pcdata_size:] // ^ditto for funcdata table bytes + // 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.Errorf("wanted %d bytes for uint32_t funcdata[nfuncdata], got %d\n", funcdata_size, len(funcdata_raw)) - return 0,0 + fmt.Printf("wanted %d bytes for uint32_t funcdata[nfuncdata], got %d\n", funcdata_size, len(funcdata_raw)) + return 0xffff } - // get the actual inline offsets - pcdata_InlIndex := f.LineTable.Binary.Uint32(pcdata_raw[4*PCDATA_InlTreeIndex:]) + // get the actual inline data value funcdata_InlTree := f.LineTable.Binary.Uint32(funcdata_raw[4*FUNCDATA_InlTree:]) - - // check if these indices are ^uint32(0) - if pcdata_InlIndex == ^uint32(0) || funcdata_InlTree == ^uint32(0) { - return 0, 0 + + // check if the value is valid + if funcdata_InlTree == ^uint32(0) { + return 0xffff } - - return pcdata_InlIndex, funcdata_InlTree + + return funcdata_InlTree } func isValidFuncID(data []byte) bool { - - // TODO -- currently only accepts "FuncIDNormal" - // We may want to include other types. - if data[0] != 0 { + + // TODO -- currently only accepts "FuncIDNormal" + // We may want to include other types. + if data[0] != 0 { return false } @@ -238,26 +237,26 @@ func isValidFuncID(data []byte) bool { } 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 + 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 + return false, -1 } pc_address = uint64(pc) + f.Entry - if (pc_address <= f.End ) && (pc_address >= f.Entry) { + if (pc_address <= f.End) && (pc_address >= f.Entry) { return true, pc - } + } return false, -1 } @@ -265,16 +264,16 @@ func isValidPC(data []byte, f *Func) (bool, int32) { // 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 + 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 + funcNameTable := f.LineTable.funcnametab if nameOff < int32(len(funcNameTable)) { i := nameOff for i < int32(len(funcNameTable)) { @@ -283,10 +282,10 @@ func isValidFuncName(data []byte, f *Func) (bool, string) { break } i += 1 - } + } - name := string(funcNameTable[nameOff : i ]) - return true, name + name := string(funcNameTable[nameOff:i]) + return true, name } return false, "" } @@ -302,33 +301,33 @@ func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { // check there are enough bytes for an inlinedCall struct off := 0 - // iterate until we hit invalid data + // iterate until we hit invalid data // that indicates we've read this function's entire inline tree - for (len(tree) - off >= size_inlinedCall_v120) { + for len(tree)-off >= size_inlinedCall_v120 { // get elt bytes - elt_raw := tree[ off : off+size_inlinedCall_v120] + elt_raw := tree[off : off+size_inlinedCall_v120] // verify funcId and padding look normal - if !isValidFuncID(elt_raw[ : 4]) { + if !isValidFuncID(elt_raw[:4]) { break } // verify calling PC exists within parent func bounds - is_valid_pc, pc := isValidPC(elt_raw[8:12], f) + is_valid_pc, pc := isValidPC(elt_raw[8:12], f) if !is_valid_pc { break } - // resolve name + // 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, + 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 @@ -338,7 +337,7 @@ func (f *Func) iterateInline_v120(tree []byte) []InlinedCall { // return array of inlined functions inside f or nil func (f *Func) GetInlinedCalls(data []byte) { - var inlList []InlinedCall + var inlList []InlinedCall // get size of inlined struct based on version if f.LineTable.Version >= ver118 { @@ -349,12 +348,12 @@ func (f *Func) GetInlinedCalls(data []byte) { 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, - }) + Funcname: elt.Funcname, + ParentName: elt.ParentName, + CallingPc: elt.CallingPc, + ParentEntry: elt.ParentEntry, + Data: elt.Data, + }) } } From 5e05d76ebac2e88922af5256ef1806dd13aa3fdb Mon Sep 17 00:00:00 2001 From: brigadier-general Date: Wed, 26 Jun 2024 16:28:13 -0500 Subject: [PATCH 30/30] Update objfile.go - OOB indexing fix --- objfile/objfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objfile/objfile.go b/objfile/objfile.go index a5f54c9..83d31c8 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -272,8 +272,8 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known // we do it here for access to the Entry object for i, fn := range candidate.ParsedPclntab.Funcs { // calc inline data VAs - pcd_InlIndex, fnd_InlTree := fn.HasInline() - if pcd_InlIndex == 0 && fnd_InlTree == 0 { + fnd_InlTree := fn.HasInline() + if fnd_InlTree == 0xffff { // no inline tree data for this function continue }