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
debug/elf: add SHT_GNU_VERDEF section parsing #63952
Comments
This proposal has been added to the active column of the proposals project |
This API diff is nice and small and seems reasonable for the benefit:
Are there any remaining concerns about this proposal? |
Could you explain what the meanings of |
They come from readelf.c If we take a look at some output from it: $ readelf -sW /usr/lib/x86_64-linux-gnu/libtiffxx.so | tail -4
23: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (3)
24: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LIBTIFFXX_4.0
25: 0000000000001890 169 FUNC GLOBAL DEFAULT 15 _Z14TIFFStreamOpenPKcPSo@@LIBTIFFXX_4.0
26: 0000000000001940 19 FUNC GLOBAL DEFAULT 15 _Z14TIFFStreamOpenPKcPSi@@LIBTIFFXX_4.0
// Versioned symbol info
const (
SymUndefined byte = 0
SymHidden byte = 1
SymPublic byte = 2
) Comes from: https://github.com/bminor/binutils-gdb/blob/master/binutils/readelf.c#L363 /* Versioned symbol info. */
enum versioned_symbol_info
{
symbol_undefined,
symbol_hidden,
symbol_public
}; https://github.com/bminor/binutils-gdb/blob/master/binutils/readelf.c#L13764 if (version_string)
{
if (sym_info == symbol_undefined)
printf ("@%s (%d)", version_string, vna_other);
else
printf (sym_info == symbol_hidden ? "@%s" : "@@%s",
version_string);
} In the case of So in the actual implementation in // Versioned symbol info
const (
SymUndefined byte = 0
SymHidden byte = 1
SymPublic byte = 2
)
type verdef struct {
Other uint16
Name string
}
type verneed struct {
File string
Other uint16
Name string
}
// gnuVersionInit parses the GNU version tables
// for use by calls to gnuVersion.
func (f *File) gnuVersionInit(str []byte) bool {
// Accumulate verdef information.
var def []verdef
vd := f.SectionByType(SHT_GNU_VERDEF)
if vd != nil {
d, _ := vd.Data()
i := 0
for {
if i+20 > len(d) {
break
}
other := f.ByteOrder.Uint16(d[i : i+2])
//flags := f.ByteOrder.Uint16(d[i+2 : i+4])
ndx := int(f.ByteOrder.Uint16(d[i+4 : i+6]))
cnt := f.ByteOrder.Uint16(d[i+6 : i+8])
// hash := f.ByteOrder.Uint32(d[i+8 : i+12])
aux := f.ByteOrder.Uint32(d[i+12 : i+16])
next := f.ByteOrder.Uint32(d[i+16 : i+20])
var name string
j := i + int(aux)
for c := 0; c < int(cnt); c++ {
if j+8 > len(d) {
break
}
vname := f.ByteOrder.Uint32(d[j : j+4])
vnext := f.ByteOrder.Uint32(d[j+4 : j+8])
name, _ = getString(str, int(vname))
if c == 0 {
if ndx >= len(def) {
a := make([]verdef, ndx)
copy(a, def)
def = a
}
def[ndx-1] = verdef{other, name}
}
j += int(vnext)
}
if next == 0 {
break
}
i += int(next)
}
}
// Accumulate verneed information.
var need []verneed
vn := f.SectionByType(SHT_GNU_VERNEED)
if vn != nil {
d, _ := vn.Data()
i := 0
for {
if i+16 > len(d) {
break
}
vers := f.ByteOrder.Uint16(d[i : i+2])
if vers != 1 {
break
}
cnt := f.ByteOrder.Uint16(d[i+2 : i+4])
fileoff := f.ByteOrder.Uint32(d[i+4 : i+8])
aux := f.ByteOrder.Uint32(d[i+8 : i+12])
next := f.ByteOrder.Uint32(d[i+12 : i+16])
file, _ := getString(str, int(fileoff))
var name string
j := i + int(aux)
for c := 0; c < int(cnt); c++ {
if j+16 > len(d) {
break
}
// hash := f.ByteOrder.Uint32(d[j : j+4])
// flags := f.ByteOrder.Uint16(d[j+4 : j+6])
other := f.ByteOrder.Uint16(d[j+6 : j+8])
nameoff := f.ByteOrder.Uint32(d[j+8 : j+12])
next := f.ByteOrder.Uint32(d[j+12 : j+16])
name, _ = getString(str, int(nameoff))
ndx := int(other)
if ndx >= len(need) {
a := make([]verneed, 2*(ndx+1))
copy(a, need)
need = a
}
need[ndx] = verneed{file, other, name}
if next == 0 {
break
}
j += int(next)
}
if next == 0 {
break
}
i += int(next)
}
}
// Versym parallels symbol table, indexing into verneed.
vs := f.SectionByType(SHT_GNU_VERSYM)
if vs == nil {
return false
}
d, _ := vs.Data()
f.gnuVerdef = def
f.gnuNeed = need
f.gnuVersym = d
return true
}
// gnuVersion adds Library and Version information to sym,
// which came from offset i of the symbol table.
func (f *File) gnuVersion(i int) (library string, version string, info byte, other byte) {
// Each entry is two bytes; skip undef entry at beginning.
i = (i + 1) * 2
if i >= len(f.gnuVersym) {
return
}
s := f.gnuVersym[i:]
if len(s) < 2 {
return
}
j := int(f.ByteOrder.Uint16(s))
if j >= 2 && j < len(f.gnuNeed) {
n := &f.gnuNeed[j]
if n.File != "" {
return n.File, n.Name, SymHidden, byte(n.Other)
}
}
var verInfo byte = SymUndefined
if j&0x8000 != 0 {
verInfo = SymHidden
} else {
verInfo = SymPublic
}
j = (j & 0x7fff) - 1
if j >= 1 && j < len(f.gnuVerdef) {
v := &f.gnuVerdef[j]
return "", v.Name, verInfo, byte(v.Other)
}
return
} The https://github.com/bminor/binutils-gdb/blob/master/include/elf/common.h#L1306 /* This flag appears in a Versym structure. It means that the symbol
is hidden, and is only visible with an explicit version number.
This is a GNU extension. */
#define VERSYM_HIDDEN 0x8000
/* This is the mask for the rest of the Versym information. */
#define VERSYM_VERSION 0x7fff |
It does seem strange for Sym* constants to go into a VerInfo field. Perhaps the names should be made closer? |
|
@rsc would you prefer something like this? |
I know you know all this, but just to reprise: There are three sections that contain version information: The The The The flags in Anyhow, the point is, the part of the version information that is symbol-specific is the So how about: Add a Add a // DynamicVersion is a version defined by a dynamic object.
type DynamicVersion struct {
Version uint16 // Version of data structure.
Flags DynamicVersionFlags
Index uint16
Name string
Deps []string
} We define flags for the type DynamicVersionFlags uint16
const (
VER_FLG_BASE DynamicVersionFlags = 1
VER_FLG_WEAK DynamicVersionFlags = 2
VER_FLG_INFO DynamicVersionFlags = 4
) Add a type DynamicVersionNeed struct {
Version uint16 // version of data structure
Name string // shared library name
Needs []DynamicVersionDep
}
type DynamicVersionDep struct {
Flags DynamicVersionFlags
Other uint16
Dep string // Name of required version
} I think this approach will let us extract all the information from the dynamic object, and let people write tools to do whatever they want. |
@ianlancetaylor thanks for your feedback and additional info. While your approach does appear to work I don't see the need to separate the version data from To be sure I wrote a test which will compare the output of //go:build linux
package main
import (
"bufio"
"debug/elf"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"testing"
)
type SymbolInfo struct {
Num int
Value string
Size string
Type string
Bind string
Vis string
Ndx string
Name string
}
func TestSymbols(t *testing.T) {
folderPath := "/usr/lib/x86_64-linux-gnu"
searchPattern := ".so"
files, err := searchFiles(folderPath, searchPattern)
if err != nil {
t.Errorf("Error %v", err)
return
}
t.Logf("Found %d files", len(files))
for _, file := range files {
symbols, err := parseSymbols(file)
if err != nil {
continue
}
f, err := elf.Open(file)
if err != nil {
continue
}
var checkSymbols = make(map[string][]elf.Symbol)
checkSymbols[".dynsym"], _ = f.DynamicSymbols()
checkSymbols[".symtab"], _ = f.Symbols()
for tableName, tableSymbols := range symbols {
if len(tableSymbols) != len(checkSymbols[tableName]) {
t.Errorf("expected %d got %d\n", len(tableSymbols), len(checkSymbols[tableName]))
}
for index, symbol := range tableSymbols {
symbolCheck := checkSymbols[tableName][index]
value := fmt.Sprintf("%016x", symbolCheck.Value)
size := fmt.Sprintf("%d", symbolCheck.Size)
if strings.HasPrefix(symbol.Size, "0x") {
size = fmt.Sprintf("0x%x", symbolCheck.Size)
}
type_ := parseType(int(symbolCheck.Info & 0xf))
bind := parseBind(int(symbolCheck.Info >> 4))
vis := parseVisibility(int(symbolCheck.Other))
ndx := parseNdx(int(symbolCheck.Section))
name := getSymbolName(symbolCheck, AddLibrary, AddVersion)
if value != symbol.Value {
t.Errorf("expected %v got %v\n", symbol.Value, value)
}
if size != symbol.Size {
t.Errorf("expected %x got %x\n", symbol.Size, size)
}
if type_ != symbol.Type {
t.Errorf("expected %v got %v\n", symbol.Type, type_)
}
if bind != symbol.Bind {
t.Errorf("expected %x got %v\n", symbol.Bind, bind)
}
if vis != symbol.Vis {
t.Errorf("expected %v got %v\n", symbol.Vis, vis)
}
if ndx != symbol.Ndx {
t.Errorf("expected %v got %v\n", symbol.Ndx, ndx)
}
if name != symbol.Name {
t.Errorf("expected %v got %v\n", symbol.Name, name)
}
}
}
}
}
func searchFiles(folderPath, searchPattern string) ([]string, error) {
var files []string
file, err := os.Open(folderPath)
if err != nil {
return nil, err
}
defer file.Close()
fileInfos, err := file.Readdir(-1)
if err != nil {
return nil, err
}
for _, fileInfo := range fileInfos {
if !fileInfo.IsDir() && strings.Contains(fileInfo.Name(), searchPattern) {
files = append(files, folderPath+"/"+fileInfo.Name())
}
}
return files, nil
}
func parseSymbols(filePath string) (map[string][]SymbolInfo, error) {
cmd := exec.Command("readelf", "-sW", filePath)
output, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(strings.NewReader(string(output)))
symbols := make(map[string][]SymbolInfo)
var tableName string
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Symbol table") {
startPos := strings.Index(line, "'")
endPos := strings.LastIndex(line, "'")
tableName = line[startPos+1 : endPos]
continue
}
if strings.HasPrefix(line, " Num:") {
scanner.Scan()
continue
}
fields := strings.Fields(line)
if len(fields) < 7 {
continue
}
name := ""
if len(fields) == 8 {
name = fields[7]
}
symbol := SymbolInfo{
Num: len(symbols[tableName]),
Value: fields[1],
Size: fields[2],
Type: fields[3],
Bind: fields[4],
Vis: fields[5],
Ndx: fields[6],
Name: name,
}
if len(fields) == 9 {
symbol.Name = fields[7] + " " + fields[8]
}
symbols[tableName] = append(symbols[tableName], symbol)
}
return symbols, nil
}
func parseType(typeInt int) string {
typeTable := map[int]string{
0: "NOTYPE",
1: "OBJECT",
2: "FUNC",
3: "SECTION",
4: "FILE",
5: "COMMON",
6: "TLS",
10: "IFUNC",
13: "LOPROC",
15: "HIPROC",
}
return typeTable[typeInt]
}
func parseBind(bindInt int) string {
bindTable := map[int]string{
0: "LOCAL",
1: "GLOBAL",
2: "WEAK",
10: "UNIQUE",
11: "UNIQUE",
12: "UNIQUE",
}
return bindTable[bindInt]
}
func parseVisibility(visInt int) string {
visTable := map[int]string{
0: "DEFAULT",
1: "INTERNAL",
2: "HIDDEN",
3: "PROTECTED",
}
return visTable[visInt]
}
func parseNdx(ndxInt int) string {
ndxTable := map[int]string{
0: "UND",
0xff00: "BEFORE",
0xff01: "AFTER",
0xff1f: "HIPROC",
0xfff1: "ABS",
0xfff2: "COMMON",
0xffff: "HIRESERVE",
}
if val, ok := ndxTable[ndxInt]; ok {
return val
}
return strconv.FormatInt(int64(ndxInt), 10)
} $ go test ./cmd/profile -v
=== RUN TestSymbols
main_test.go:37: Found 3979 files
--- PASS: TestSymbols (40.48s)
PASS
ok github.com/benbaker76/libbpf-tools/cmd/profile 40.488s |
@benbaker76 I don't doubt that your approach works for your purposes. But your approach doesn't provide any mechanism for reading the other version data, like which versions requires which other versions. And I think it essentially duplicates the same information across a bunch of different |
@ianlancetaylor I think you've hit the nail on the head when you said "We've already locked ourselves into representing that as the I'm not entirely sure what particular version info is missing in my changes apart from the But as you suggest we should include these flags and I would do that by adding another byte called
So those are the changes I'd make but happy to go with your method as they do make better sense going forward especially if we were designing the parser from scratch and didn't have to worry about backward compatibility. I'd expect this isn't a heavily traveled part of the standard library anyway. |
@ianlancetaylor I'll hopefully get some time over the weekend to implement your suggestions. |
@ianlancetaylor here's my implementation based on your suggestions. // A File represents an open ELF file.
type File struct {
FileHeader
Sections []*Section
Progs []*Prog
closer io.Closer
dynVers []DynamicVersion
dynVerNeeds []DynamicVersionNeed
gnuVersym []byte
}
...
// A Symbol represents an entry in an ELF symbol table section
type Symbol struct {
Name string
Info, Other byte
VersionIndex int32
Section SectionIndex
Value, Size uint64
// Version and Library are present only for the dynamic symbol
// table.
Version string
Library string
}
...
const (
VER_NDX_LOCAL int32 = 0 // Symbol has local scope
VER_NDX_GLOBAL int32 = 1 // Symbol has global scope
VERSYM_VERSION int32 = 0x7fff // Version index mask
VERSYM_HIDDEN int32 = 0x8000 // Symbol is hidden
)
type DynamicVersionFlags uint16
const (
VER_FLG_BASE DynamicVersionFlags = 1 // Version definition of the file
VER_FLG_WEAK DynamicVersionFlags = 2 // Weak version identifier
VER_FLG_INFO DynamicVersionFlags = 4 // Reference exists for informational purposes
)
// DynamicVersion is a version defined by a dynamic object
type DynamicVersion struct {
Version uint16 // Version of data structure
Flags DynamicVersionFlags
Index uint16 // Version index
Deps []string // Dependencies
}
type DynamicVersionNeed struct {
Version uint16 // Version of data structure
Name string // Shared library name
Needs []DynamicVersionDep // Dependencies
}
type DynamicVersionDep struct {
Flags DynamicVersionFlags
Other uint16 // Version index
Dep string // Name of required version
}
// dynamicVersions returns version information for a dynamic object
func (f *File) dynamicVersions(str []byte) bool {
if f.dynVers != nil {
// Already initialized
return true
}
// Accumulate verdef information.
vd := f.SectionByType(SHT_GNU_VERDEF)
if vd == nil {
return false
}
d, _ := vd.Data()
var dynVers []DynamicVersion
i := 0
for {
if i+20 > len(d) {
break
}
version := f.ByteOrder.Uint16(d[i : i+2])
flags := DynamicVersionFlags(f.ByteOrder.Uint16(d[i+2 : i+4]))
ndx := f.ByteOrder.Uint16(d[i+4 : i+6])
cnt := f.ByteOrder.Uint16(d[i+6 : i+8])
aux := f.ByteOrder.Uint32(d[i+12 : i+16])
next := f.ByteOrder.Uint32(d[i+16 : i+20])
var depName string
var deps []string
j := i + int(aux)
for c := 0; c < int(cnt); c++ {
if j+8 > len(d) {
break
}
vname := f.ByteOrder.Uint32(d[j : j+4])
vnext := f.ByteOrder.Uint32(d[j+4 : j+8])
depName, _ = getString(str, int(vname))
deps = append(deps, depName)
j += int(vnext)
}
dynVers = append(dynVers, DynamicVersion{
Version: version,
Flags: flags,
Index: ndx,
Deps: deps,
})
if next == 0 {
break
}
i += int(next)
}
f.dynVers = dynVers
return true
}
// DynamicVersions returns version information for a dynamic object
func (f *File) DynamicVersions() (*[]DynamicVersion, error) {
if f.dynVers == nil {
return nil, errors.New("DynamicVersions: not initialized")
}
return &f.dynVers, nil
}
// dynamicVersionNeeds returns version dependencies for a dynamic object
func (f *File) dynamicVersionNeeds(str []byte) bool {
if f.dynVerNeeds != nil {
// Already initialized
return true
}
// Accumulate verneed information.
vn := f.SectionByType(SHT_GNU_VERNEED)
if vn == nil {
return false
}
d, _ := vn.Data()
var dynVerNeeds []DynamicVersionNeed
i := 0
for {
if i+16 > len(d) {
break
}
vers := f.ByteOrder.Uint16(d[i : i+2])
if vers != 1 {
break
}
cnt := f.ByteOrder.Uint16(d[i+2 : i+4])
fileoff := f.ByteOrder.Uint32(d[i+4 : i+8])
aux := f.ByteOrder.Uint32(d[i+8 : i+12])
next := f.ByteOrder.Uint32(d[i+12 : i+16])
file, _ := getString(str, int(fileoff))
var deps []DynamicVersionDep
j := i + int(aux)
for c := 0; c < int(cnt); c++ {
if j+16 > len(d) {
break
}
flags := DynamicVersionFlags(f.ByteOrder.Uint16(d[j+4 : j+6]))
other := f.ByteOrder.Uint16(d[j+6 : j+8])
nameoff := f.ByteOrder.Uint32(d[j+8 : j+12])
next := f.ByteOrder.Uint32(d[j+12 : j+16])
depName, _ := getString(str, int(nameoff))
deps = append(deps, DynamicVersionDep{
Flags: flags,
Other: other,
Dep: depName,
})
if next == 0 {
break
}
j += int(next)
}
dynVerNeeds = append(dynVerNeeds, DynamicVersionNeed{
Version: vers,
Name: file,
Needs: deps,
})
if next == 0 {
break
}
i += int(next)
}
f.dynVerNeeds = dynVerNeeds
return true
}
// DynamicVersionNeeds returns version dependencies for a dynamic object
func (f *File) DynamicVersionNeeds() (*[]DynamicVersionNeed, error) {
if f.dynVerNeeds == nil {
return nil, errors.New("DynamicVersionNeeds: not initialized")
}
return &f.dynVerNeeds, nil
}
// gnuVersionInit parses the GNU version tables
// for use by calls to gnuVersion.
func (f *File) gnuVersionInit(str []byte) bool {
// Versym parallels symbol table, indexing into verneed.
vs := f.SectionByType(SHT_GNU_VERSYM)
if vs == nil {
return false
}
d, _ := vs.Data()
f.dynamicVersions(str)
f.dynamicVersionNeeds(str)
f.gnuVersym = d
return true
}
// gnuVersion adds Library and Version information to sym,
// which came from offset i of the symbol table.
func (f *File) gnuVersion(i int) (library string, version string, versionIndex int32) {
// Each entry is two bytes; skip undef entry at beginning.
i = (i + 1) * 2
if i >= len(f.gnuVersym) {
return "", "", -1
}
s := f.gnuVersym[i:]
if len(s) < 2 {
return "", "", -1
}
j := int32(f.ByteOrder.Uint16(s))
var ndx = (j & VERSYM_VERSION)
if ndx == VER_NDX_LOCAL || ndx == VER_NDX_GLOBAL {
return "", "", j
}
for _, v := range f.dynVerNeeds {
for _, n := range v.Needs {
if n.Other == uint16(ndx) {
return v.Name, n.Dep, j | VERSYM_HIDDEN
}
}
}
for _, v := range f.dynVers {
if v.Index == uint16(ndx) {
if len(v.Deps) > 0 {
return "", v.Deps[0], j
}
}
}
return "", "", -1
} |
@benbaker76 can you post the full API diff for the revised version? (Run all.bash and the API check should fail, like in #63952 (comment); that's the output I'm asking for.) |
Thanks @rsc here's the API diff
NOTE: I had to update the tests to pass with the addition of the |
I'm not sure we need to export |
@ianlancetaylor I do use So here's the latest API diff:
|
What do you use |
Here are a couple of functions that I'm using these constants. IMO it's really not necessary to have them exposed by API.
|
I see, thanks. I wonder if we should separate the Any opinion? |
@ianlancetaylor without adding another field we could use getters and setters along with a private
|
In this approach I think But a bigger issue is that I don't think anything else in debug/elf uses methods like that. It would be better to avoid two different ways of getting |
@ianlancetaylor I'm not a huge fan of the approach either. Another option is to move
And another option is to create a new
|
Another option is to move version out of
|
Unfortunately we can't reasonably move I think that adding type Symbol struct {
Name string
Info, Other byte
IsHidden bool
VersionIndex int32
Section SectionIndex
Value, Size uint64
// Version and Library are present only for the dynamic symbol
// table.
Version string
Library string
} |
@ianlancetaylor Yeah I thought as much. Okay let's go with
|
Do you think we should make |
If |
We can still use -1 since bit 15 is not used anymore a value of 0xFFFF can denote no version index. |
We can't use |
int16 then so it's clearer that it can be -1? Same range as before (0-32767). |
Good point, |
Okay here we are now.
|
There's one more issue we may want to address and that's the We could make these flags and include
Right now it's dealt with like this:
Which could be changed to something like this:
And instead of |
Works for me. Thanks. |
And here's the latest API diff. Are we all okay with the naming of VerFlag*?
|
It seems like the two File methods should return slices not pointers to slices. Otherwise this seems fine. |
Thanks for the feedback. Here's the new API diff
|
Based on the discussion above, this proposal seems like a likely accept. The API diff is in #63952 (comment). |
No change in consensus, so accepted. 🎉 The API diff is in #63952 (comment). |
I'm porting @brendangregg's eBPF profiling application profile.py to Golang. I'm attempting to use the
debug/elf
package for retrieving debug symbol info but have stumbled upon a limitation.As a demonstration I've picked a library called
libtiffxx.so
as it's relatively small and clearly demontrates the issues I have with the package.Let's take a look at the
.gnu.version*
sections contained in the library:$ readelf -SW /usr/lib/x86_64-linux-gnu/libtiffxx.so | grep .gnu.version [ 6] .gnu.version VERSYM 0000000000000882 000882 000036 02 A 4 0 2 [ 7] .gnu.version_d VERDEF 00000000000008b8 0008b8 000038 00 A 5 2 8 [ 8] .gnu.version_r VERNEED 00000000000008f0 0008f0 000090 00 A 5 3 8
There's important debug symbol information contained in the
.gnu.version_d
(SHT_GNU_VERDEF) section which is currently not parsed by the package. Let's look at the last four functions in the.dynsym
section.$ readelf -sW /usr/lib/x86_64-linux-gnu/libtiffxx.so | tail -4 23: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (3) 24: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LIBTIFFXX_4.0 25: 0000000000001890 169 FUNC GLOBAL DEFAULT 15 _Z14TIFFStreamOpenPKcPSo@@LIBTIFFXX_4.0 26: 0000000000001940 19 FUNC GLOBAL DEFAULT 15 _Z14TIFFStreamOpenPKcPSi@@LIBTIFFXX_4.0
The
debug/elf
package can retreive the function name, but what about the@
or@@
, the library name after it or even the number in the brackets? In my proposal and my current implementation I provide a way to access this important debug symbol information.If we take a look at the dynamic symbol output of these four functions in the current implementation of
debug/elf
we have the following output:In my new implemenation we get this output:
We now have the
Version
entry as is often retrieved fromSHT_GNU_VERNEED
but in the last three entries can only be obtained from theSHT_GNU_VERDEF
section. We also have two newSymbol
members calledVerInfo
andVerOther
.So what are these new values and where do they come from? Using binutil's
readelf.c
as a reference we can find out where this data is located and how to extract it.VerInfo
in this case refers to the number in the brackets. In the case of_ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (3)
the number 3 comes fromVerOther
which is the version number. If it's1
we don't display it in the brackets. The single@
comes fromVerInfo
which can be one of three values:If the value is
SymHidden
it will be@
and if it'sSymPublic
it will be@@
. This is taken fromreadelf.c
So I have implemented the code to do the necessary parsing of the
SHT_GNU_VERDEF
section and am writing this proposal so I can submit my changes to Gerrit for review by the Golang team.Currently I have made changes to the following files in
src/debug/elf
:file_test.go
file.go
symbols_test.go
testdata/libtiffxx.so
(should be renamed?)As I mentioned I use
libtiffxx.so
as my testdata ELF binary because it's relatively small (14.4 kB) and it demonstrates the new data from which my new branch can parse.My changes currently pass all tests except for the
API check
Thanks for taking the time to review my proposal.
The text was updated successfully, but these errors were encountered: