-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gosym can provide the wrong address for funcs in cgo go binary #76
Comments
I think I implemented a fix for this in redress. We should be be able to just take the start of text address from the moduledata structure. |
Maybe the same logic can also be applied to pclntab |
I think I have an idea for how we can solve this. First we need to find the So we read the offset where TEXT and ETEXT should be, check if the values falls within the Do you have a test binary that can be used for testing the implementation? |
My testdata placed at https://github.com/Zxilly/go-testdata. But I think the offset may not a fixed value. As the golang team member said:
The value of offset can change a lot while the devs static link some c library. |
The moduledata's text field is where Go thinks it starts. https://github.com/golang/go/blob/653abefddf2c627d5d1805d35e55c5a01eb32c6e/src/runtime/symtab.go#L320 from the runtime. The debug: https://github.com/golang/go/blob/653abefddf2c627d5d1805d35e55c5a01eb32c6e/src/debug/gosym/pclntab.go#L58 |
I would first find the pclntab based on its magic bytes. Get the virtual address of that offset. Search for the offset in the binary to find the moduledata since it is the first field in the structure. This is pretty much how these two structures are found in PE files already. |
Validating moduledata is easy if we find it first. We can use the However, if we find pnlntab first, we have to build gosym.Table twice because we didn't get the accurate pc base for it during the first search. These are my suggestions. |
We can create a |
I will rewrite the PR associated with the moduledata search based implementation. Please let me know if you have any ideas. |
We don't need to parse the pclntab twice, we just need the address of the structure so it can be used to find the moduledata structure. After we have the moduledata, we can parse the pclntab with the correct text start. |
Can you add more explain about I found the moduledata defind in the golang source like type moduledata struct {
sys.NotInHeap // Only in static data
pcHeader *pcHeader
funcnametab []byte
cutab []uint32
filetab []byte
pctab []byte
pclntable []byte
ftab []functab
findfunctab uintptr
minpc, maxpc uintptr
text, etext uintptr
noptrdata, enoptrdata uintptr
data, edata uintptr
bss, ebss uintptr
noptrbss, enoptrbss uintptr
covctrs, ecovctrs uintptr
end, gcdata, gcbss uintptr
types, etypes uintptr
rodata uintptr
gofunc uintptr // go.func.*
textsectmap []textsect
typelinks []int32 // offsets from types
itablinks []*itab
ptab []ptabEntry
pluginpath string
pkghashes []modulehash
// This slice records the initializing tasks that need to be
// done to start up the program. It is built by the linker.
inittasks []*initTask
modulename string
modulehashes []modulehash
hasmain uint8 // 1 if module contains the main function, 0 otherwise
gcdatamask, gcbssmask bitvector
typemap map[typeOff]*_type // offset to *_rtype in previous module
bad bool // module failed to load and should be ignored
next *moduledata
} Is only this part persist in the binary? text, etext uintptr
noptrdata, enoptrdata uintptr
data, edata uintptr
bss, ebss uintptr
noptrbss, enoptrbss uintptr
covctrs, ecovctrs uintptr
end, gcdata, gcbss uintptr
types, etypes uintptr
rodata uintptr
gofunc uintptr // go.func.* |
The whole structure is present in the binary. Check the different structures in the func findModuledata(f fileHandler) ([]byte, error) {
_, secData, err := f.getSectionData(f.moduledataSection())
if err != nil {
return nil, err
}
/// Get the virtual address of the PCLNTAB.
tabAddr, _, err := f.getPCLNTABData()
if err != nil {
return nil, err
}
// Search for moduledata using the address to the PCLNTAB. The match will be hit on `*pcHeader` which is the first field.
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, &tabAddr)
if err != nil {
return nil, err
}
off := bytes.Index(secData, buf.Bytes()[:intSize32])
if off == -1 {
return nil, errors.New("could not find moduledata")
}
// TODO: Verify that hit is correct.
return secData[off : off+0x300], nil
} For PE files gore searches the func searchSectionForTab(secData []byte) ([]byte, error) {
// First check for the current magic used. If this fails, it could be
// an older version. So check for the old header.
MAGIC_LOOP:
for _, magic := range [][]byte{pclntab120magic, pclntab118magic, pclntab116magic, pclntab12magic} {
off := bytes.LastIndex(secData, magic)
if off == -1 {
continue // Try other magic.
}
for off != -1 {
if off != 0 {
buf := secData[off:]
if len(buf) < 16 || buf[4] != 0 || buf[5] != 0 ||
(buf[6] != 1 && buf[6] != 2 && buf[6] != 4) || // pc quantum
(buf[7] != 4 && buf[7] != 8) { // pointer size
// Header doesn't match.
if off-1 <= 0 {
continue MAGIC_LOOP
}
off = bytes.LastIndex(secData[:off-1], magic)
continue
}
// Header match
return secData[off:], nil
}
break
}
}
return nil, ErrNoPCLNTab
} The address of the PCLNTAB is calculated using: I maybe can get a chance to code this up this weekend. Otherwise, if you want take a stab at it, please go ahead. Essentially, I would add this logic to ELF and MachO files as a fallback if the named sections don't exist. |
I can port this logic to elf and macho, but before we can do that we need to merge the #77 |
As described in golang/go#65232,
gosym.NewLineTable
should be used with the value of symbolruntime.text
but not just use the start of.text
segment, which was used by gore right now.I have no idea how to implement a search for
runtime.text
in the stripped binary, but at least we can show a warning likethe offset in func can be incorrect in cgo binary
right now.The text was updated successfully, but these errors were encountered: