Skip to content

Commit c357a27

Browse files
committed
Add version requirement collector and tests
`ELFVersionNeededData` should produce a list of `ELFVersionNeededEntry` just as `ELFVersionData` produces a list of `ELFVersionEntry`. Export both and add docs + tests.
1 parent 4ef64df commit c357a27

File tree

2 files changed

+93
-8
lines changed

2 files changed

+93
-8
lines changed

src/ELF/ELFVersion.jl

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export ELFVersionData
1+
export ELFVersionData, ELFVersionNeededData, ELFHash
22

33
# Special ELF version data structures
44
@io struct ELFVerDef{H <: ELFHandle}
@@ -37,6 +37,20 @@ struct ELFVersionEntry{H <: ELFHandle}
3737
names::Vector{String}
3838
end
3939

40+
struct ELFVersionNeededEntry{H <: ELFHandle}
41+
ver_need::ELFVerNeed{H}
42+
auxes::Vector{ELFVernAux}
43+
names::Vector{String}
44+
end
45+
46+
"""
47+
Collect all version definitions from .gnu.version_d. This section contains a
48+
sequence of verdef structs `vd`, each of which owns exactly `vd.vd_cnt` verdaux
49+
structs which we convert to names. The first name is generally the only
50+
important one to the given `vd`; it is the version being defined, and
51+
corresponds to `vd.vd_hash`. If present, a second verdaux usually notes the
52+
parent version (e.g. `names = ["GLIBCXX_3.4.7", "GLIBCXX_3.4.6"]`)
53+
"""
4054
function ELFVersionData(oh::H) where {H <: ELFHandle}
4155
s = findfirst(Sections(oh), ".gnu.version_d")
4256
strtab = StrTab(findfirst(Sections(oh), ".dynstr"))
@@ -74,11 +88,53 @@ function ELFVersionData(oh::H) where {H <: ELFHandle}
7488
end
7589

7690
"""
77-
Hash function used to create vd_hash from vda_name, or vna_hash from vna_name
91+
Collect all version requirements from .gnu.version_r. This section is
92+
structurally similar to the version definition section, but the primary
93+
"verneed" struct corresponds to one shared library, and the auxiliary struct
94+
corresponds to a version.
95+
"""
96+
function ELFVersionNeededData(oh::H) where {H <: ELFHandle}
97+
s = findfirst(Sections(oh), ".gnu.version_r")
98+
strtab = StrTab(findfirst(Sections(oh), ".dynstr"))
99+
(isnothing(s) || isnothing(strtab)) && return ELFVersionNeededEntry[]
100+
101+
seek(oh, section_offset(s))
102+
verneeds = ELFVersionNeededEntry[]
103+
while true
104+
vn_pos = position(oh)
105+
vn = unpack(oh, ELFVerNeed{H})
106+
auxes = ELFVernAux[]
107+
names = String[]
108+
aux_offset = 0
109+
for aux_idx in 1:vn.vn_cnt
110+
seek(oh, vn_pos + vn.vn_aux + aux_offset)
111+
aux = unpack(oh, ELFVernAux{H})
112+
name = strtab_lookup(strtab, aux.vna_name)
113+
push!(auxes, aux)
114+
push!(names, name)
115+
aux_offset += aux.vna_next
116+
end
117+
push!(verneeds, ELFVersionNeededEntry(vn, auxes, names))
118+
119+
if vn.vn_next == 0
120+
break
121+
end
122+
seek(oh, vn_pos + vn.vn_next)
123+
end
124+
125+
return verneeds
126+
end
127+
128+
"""
129+
See https://en.wikipedia.org/wiki/PJW_hash_function
130+
131+
Hash function used to create vd_hash from vda_name, or vna_hash from vna_name.
132+
Stops at the first null byte.
78133
"""
79134
function ELFHash(v::Vector{UInt8})
80135
h = UInt32(0)
81136
for b in v
137+
(b == 0) && break;
82138
h = (h << 4) + b
83139
hi = h & 0xf0000000
84140
if (hi != 0)

test/runtests.jl

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,19 +210,48 @@ test_libfoo_and_fooifier("./win64/fooifier.exe", "./win64/libfoo.dll")
210210

211211
# Ensure that ELF version stuff works
212212
@testset "ELF Version Info Parsing" begin
213+
using ObjectFile.ELF
214+
215+
# Assuming the version structs in the file are correct, test that we read
216+
# them correctly (and calculate hashes correctly).
217+
function check_verdef(v::ELF.ELFVersionEntry)
218+
@test v.ver_def.vd_version == 1
219+
@test v.ver_def.vd_cnt == length(v.names)
220+
if length(v.names) > 0
221+
@test v.ver_def.vd_hash == ELFHash(Vector{UInt8}(v.names[1]))
222+
end
223+
end
224+
function check_verneed(v::ELF.ELFVersionNeededEntry)
225+
@test v.ver_need.vn_version == 1
226+
@test v.ver_need.vn_cnt == length(v.auxes) == length(v.names)
227+
for i in 1:length(v.names)
228+
@test v.auxes[i].vna_hash == ELFHash(Vector{UInt8}(v.names[i]))
229+
end
230+
end
231+
213232
libstdcxx_path = "./linux64/libstdc++.so.6"
214233

215234
# Extract all pieces of `.gnu.version_d` from libstdc++.so, find the `GLIBCXX_*`
216235
# symbols, and use the maximum version of that to find the GLIBCXX ABI version number
217-
version_symbols = readmeta(libstdcxx_path) do ohs
236+
readmeta(libstdcxx_path) do ohs
218237
oh = only(ohs)
219-
unique(vcat((x -> x.names).(ObjectFile.ELF.ELFVersionData(oh))...))
238+
verdef_symbols = unique(vcat((x -> x.names).(ELFVersionData(oh))...))
239+
verdef_symbols = filter(x -> startswith(x, "GLIBCXX_"), verdef_symbols)
240+
max_version = maximum([VersionNumber(split(v, "_")[2]) for v in verdef_symbols])
241+
@test max_version == v"3.4.25"
220242
end
221-
version_symbols = filter(x -> startswith(x, "GLIBCXX_"), version_symbols)
222-
max_version = maximum([VersionNumber(split(v, "_")[2]) for v in version_symbols])
223-
@test max_version == v"3.4.25"
224-
end
225243

244+
for p in ["./linux32/fooifier", "./linux32/libfoo.so",
245+
"./linux64/fooifier", "./linux64/libfoo.so",
246+
"./linux64/libstdc++.so.6"]
247+
readmeta(p) do ohs
248+
oh = only(ohs)
249+
foreach(check_verdef, ELFVersionData(oh))
250+
foreach(check_verneed, ELFVersionNeededData(oh))
251+
end
252+
end
253+
254+
end
226255

227256
# Ensure that these tricksy win32 files work
228257
@testset "git win32 problems" begin

0 commit comments

Comments
 (0)