Closed RyanGlScott closed 2 years ago
A smaller example, courtesy of this blog post:
foo.2.c
:
void foo2(int x, int y) {}
void foo1(int x) {}
#define V_1_2 "MYSTUFF_1.2"
#define V_1_1 "MYSTUFF_1.1"
#define SYMVER( s ) \
__asm__(".symver " s )
SYMVER( "foo1,foo@" V_1_1 );
SYMVER( "foo2,foo@@" V_1_2 );
symver.2.map
MYSTUFF_1.2 {
global:
foo;
};
MYSTUFF_1.1 {
global:
foo;
local: *;
};
$ gcc foo.2.c -Wl,--version-script=symver.2.map -fpic -o libfoo.2.so -Wl,-soname,libfoo.so -shared
$ readelf --dyn-syms libfoo.2.so
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __cxa_finalize
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000001102 10 FUNC GLOBAL DEFAULT 11 foo@MYSTUFF_1.1
6: 00000000000010f5 13 FUNC GLOBAL DEFAULT 11 foo@@MYSTUFF_1.2
7: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS MYSTUFF_1.1
8: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS MYSTUFF_1.2
Notice the two foo
symbols, both of which live in .gnu.version_d
. If you replace /lib/x86_64-linux-gnu/libz.so.1.2.11
with libfoo.2.so
in the test harness above, you'll get:
, VersionLocal
__cxa_finalize, VersionLocal
_ITM_registerTMCloneTable, VersionLocal
_ITM_deregisterTMCloneTable, VersionLocal
__gmon_start__, VersionLocal
foo, Symbol foo has unresolvable version requirement index 32771.
foo, Symbol foo has unresolvable version requirement index 2.
MYSTUFF_1.1, Symbol MYSTUFF_1.1 has unresolvable version requirement index 3.
MYSTUFF_1.2, Symbol MYSTUFF_1.2 has unresolvable version requirement index 2.
Unfortunately, this example reveals that my proposed fix above is inadequate, since it will find the version for the latter foo
but not the former:
, VersionLocal
__cxa_finalize, VersionLocal
_ITM_registerTMCloneTable, VersionLocal
_ITM_deregisterTMCloneTable, VersionLocal
__gmon_start__, VersionLocal
foo, Symbol foo has unresolvable version requirement index 32771.
foo, VersionSpecific: VersionId {verFile = "libfoo.so", verName = "MYSTUFF_1.2"}
MYSTUFF_1.1, VersionSpecific: VersionId {verFile = "libfoo.so", verName = "MYSTUFF_1.1"}
MYSTUFF_1.2, VersionSpecific: VersionId {verFile = "libfoo.so", verName = "MYSTUFF_1.2"}
I'm entirely unclear on how index 32771 arises—more investigation is required.
Argh. It turns out that 32771 isn't really the index. Rather, it's the bitwise-ORed combination of 3 (the actual index we want) and VERSYM_VERSION
, or 0x7fff. It turns out that there is a GNU extension that allows hidden symbols, which you can test by masking off the bits in VERSYM_HIDDEN
. Sure enough, 32771 is 0x8003 is hexadecimal, so this is a hidden symbol. (Finding documentation for this was... challenging.)
In order to fix this, then, I think we'd need to mask off VERSYM_VERSION
in the implementation of dynSymEntry
.
Here is a test harness program which loads
/lib/x86_64-linux-gnu/libz.so.1.2.11
, retrieves its dynamic symbol table, and prints each symbol table entry along with its version information:Unfortunately, GitHub won't let me attach
/lib/x86_64-linux-gnu/libz.so.1.2.11
, but what makes this particular.so
file so interesting is its output:There are quite a few results where
elf-edit
claims that a symbol has anunresolvable version requirement index
.readelf
, on the other hand, disagrees with this assessment, as it is able to find version numbers for each of these problematic symbols:What accounts for this discrepancy? I believe it has to do with the way that
dynSymEntry
is implemented. When looking up symbol versions, it will only consult the required symbol version definitions in the.gnu.version_r
section (by way of itsVersionReqMap
argument)./lib/x86_64-linux-gnu/libz.so.1.2.11
, on the other hand, doesn't just have a.gnu.version_r
section—it also has symbol version definitions in a.gnu.version_d
section. (See here for a more in-depth explanation of.gnu.version_d
and.gnu.version_r
.)In this example, the version information we need lives in the
.gnu_version_d
section. As a proof of concept, if you tweak this program to consult.gnu.version_d
:Then it will successfully discover versions for all symbols:
I propose that
elf-edit
take.gnu.version_d
into account somehow indynSymEntry
. I'm not entirely sure whether it's more correct to first look in.gnu.version_r
and then.gnu.version_d
or vice versa.